diff --git a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift index 015a10ce45..939725a331 100644 --- a/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift +++ b/Tests/LottieMetalTest/LottieSwift/Sources/Private/Utility/Extensions/CGFloatExtensions.swift @@ -25,11 +25,15 @@ extension CGFloat { } func isInRangeOrEqual(_ from: CGFloat, _ to: CGFloat) -> Bool { - from <= self && self <= to + let from = Float(from) + let to = Float(to) + return from <= Float(self) && Float(self) <= to } func isInRange(_ from: CGFloat, _ to: CGFloat) -> Bool { - from < self && self < to + let from = Float(from) + let to = Float(to) + return from < Float(self) && Float(self) < to } func cubicBezierInterpolate(_ P0: CGPoint, _ P1: CGPoint, _ P2: CGPoint, _ P3: CGPoint) -> CGFloat { diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h index 28b5e69fb9..32a02bfc8a 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/Canvas.h @@ -6,66 +6,10 @@ #include #include #include +#include namespace lottieRendering { -struct Color { - double r; - double g; - double b; - double a; - - Color(double r_, double g_, double b_, double a_) : - r(r_), g(g_), b(b_), a(a_) { - } - - bool operator==(Color const &rhs) const { - if (r != rhs.r) { - return false; - } - if (g != rhs.g) { - return false; - } - if (b != rhs.b) { - return false; - } - if (a != rhs.a) { - return false; - } - return true; - } - - bool operator!=(Color const &rhs) const { - return !(*this == rhs); - } -}; - -enum class BlendMode { - Normal, - DestinationIn, - DestinationOut -}; - -enum class FillRule: int { - None = 0, - NonZeroWinding = 1, - EvenOdd = 2 -}; - -enum class LineCap: int { - None = 0, - Butt = 1, - Round = 2, - Square = 3 -}; - -enum class LineJoin: int { - None = 0, - Miter = 1, - Round = 2, - Bevel = 3 -}; - class Image { public: virtual ~Image() = default; @@ -73,25 +17,45 @@ public: class Gradient { public: - Gradient(std::vector const &colors, std::vector const &locations) : + Gradient(std::vector const &colors, std::vector const &locations) : _colors(colors), _locations(locations) { assert(_colors.size() == _locations.size()); } - std::vector const &colors() const { + std::vector const &colors() const { return _colors; } - std::vector const &locations() const { + std::vector const &locations() const { return _locations; } private: - std::vector _colors; - std::vector _locations; + std::vector _colors; + std::vector _locations; }; +enum class BlendMode { + Normal, + DestinationIn, + DestinationOut +}; + +enum class PathCommandType { + MoveTo, + LineTo, + CurveTo, + Close +}; + +typedef struct { + PathCommandType type; + CGPoint points[4]; +} PathCommand; + +typedef std::function)> CanvasPathEnumerator; + class Canvas { public: virtual ~Canvas() = default; @@ -104,20 +68,20 @@ public: virtual void saveState() = 0; virtual void restoreState() = 0; - virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) = 0; - virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; - virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) = 0; + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) = 0; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; - virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) = 0; - virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; - virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) = 0; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) = 0; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) = 0; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) = 0; - virtual void fill(lottie::CGRect const &rect, Color const &fillColor) = 0; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) = 0; virtual void setBlendMode(BlendMode blendMode) = 0; - virtual void setAlpha(double alpha) = 0; + virtual void setAlpha(float alpha) = 0; - virtual void concatenate(lottie::CATransform3D const &transform) = 0; + virtual void concatenate(lottie::Transform2D const &transform) = 0; virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) = 0; }; diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h index fc572306cd..db8064458a 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.h @@ -29,18 +29,18 @@ public: virtual void saveState() override; virtual void restoreState() override; - virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; - virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override; + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; - virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; - virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; - virtual void fill(lottie::CGRect const &rect, Color const &fillColor) override; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; virtual void setBlendMode(BlendMode blendMode) override; - virtual void setAlpha(double alpha) override; - virtual void concatenate(lottie::CATransform3D const &transform) override; + virtual void setAlpha(float alpha) override; + virtual void concatenate(lottie::Transform2D const &transform) override; virtual std::shared_ptr makeImage() const; virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm index 5a915888c6..38f95beb72 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/CoreGraphicsCanvasImpl.mm @@ -1,7 +1,5 @@ #include "CoreGraphicsCanvasImpl.h" - - namespace lottieRendering { namespace { @@ -13,6 +11,52 @@ int alignUp(int size, int align) { return (size + alignmentMask) & ~alignmentMask; } +bool addEnumeratedPath(CGContextRef context, CanvasPathEnumerator const &enumeratePath) { + bool isEmpty = true; + + enumeratePath([&](PathCommand const &command) { + switch (command.type) { + case PathCommandType::MoveTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextMoveToPoint(context, command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::LineTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextAddLineToPoint(context, command.points[0].x, command.points[0].y); + break; + } + case PathCommandType::CurveTo: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextAddCurveToPoint(context, command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y); + break; + } + case PathCommandType::Close: { + if (isEmpty) { + isEmpty = false; + CGContextBeginPath(context); + } + CGContextClosePath(context); + break; + } + default: { + break; + } + } + }); + + return !isEmpty; +} + } ImageImpl::ImageImpl(::CGImageRef image) { @@ -89,11 +133,10 @@ void CanvasImpl::restoreState() { CGContextRestoreGState(_context); } -void CanvasImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); +void CanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { + if (!addEnumeratedPath(_context, enumeratePath)) { + return; + } CGFloat components[4] = { color.r, color.g, color.b, color.a }; CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); @@ -101,7 +144,7 @@ void CanvasImpl::fillPath(std::shared_ptr const &path, FillRule CFRelease(nativeColor); switch (fillRule) { - case FillRule::EvenOdd: { + case lottie::FillRule::EvenOdd: { CGContextEOFillPath(_context); break; } @@ -112,15 +155,16 @@ void CanvasImpl::fillPath(std::shared_ptr const &path, FillRule } } -void CanvasImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { +void CanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { CGContextSaveGState(_context); - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); + + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } switch (fillRule) { - case FillRule::EvenOdd: { + case lottie::FillRule::EvenOdd: { CGContextEOClip(_context); break; } @@ -142,7 +186,12 @@ void CanvasImpl::linearGradientFillPath(std::shared_ptr const &p assert(gradient.colors().size() == gradient.locations().size()); - CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); if (nativeGradient) { CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CFRelease(nativeGradient); @@ -152,15 +201,16 @@ void CanvasImpl::linearGradientFillPath(std::shared_ptr const &p CGContextRestoreGState(_context); } -void CanvasImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) { +void CanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { CGContextSaveGState(_context); - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); + + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } switch (fillRule) { - case FillRule::EvenOdd: { + case lottie::FillRule::EvenOdd: { CGContextEOClip(_context); break; } @@ -182,7 +232,12 @@ void CanvasImpl::radialGradientFillPath(std::shared_ptr const &p assert(gradient.colors().size() == gradient.locations().size()); - CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); if (nativeGradient) { CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CFRelease(nativeGradient); @@ -192,11 +247,10 @@ void CanvasImpl::radialGradientFillPath(std::shared_ptr const &p CGContextRestoreGState(_context); } -void CanvasImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); +void CanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { + if (!addEnumeratedPath(_context, enumeratePath)) { + return; + } CGFloat components[4] = { color.r, color.g, color.b, color.a }; CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); @@ -206,15 +260,15 @@ void CanvasImpl::strokePath(std::shared_ptr const &path, double CGContextSetLineWidth(_context, lineWidth); switch (lineJoin) { - case LineJoin::Miter: { + case lottie::LineJoin::Miter: { CGContextSetLineJoin(_context, kCGLineJoinMiter); break; } - case LineJoin::Round: { + case lottie::LineJoin::Round: { CGContextSetLineJoin(_context, kCGLineJoinRound); break; } - case LineJoin::Bevel: { + case lottie::LineJoin::Bevel: { CGContextSetLineJoin(_context, kCGLineJoinBevel); break; } @@ -225,15 +279,15 @@ void CanvasImpl::strokePath(std::shared_ptr const &path, double } switch (lineCap) { - case LineCap::Butt: { + case lottie::LineCap::Butt: { CGContextSetLineCap(_context, kCGLineCapButt); break; } - case LineCap::Round: { + case lottie::LineCap::Round: { CGContextSetLineCap(_context, kCGLineCapRound); break; } - case LineCap::Square: { + case lottie::LineCap::Square: { CGContextSetLineCap(_context, kCGLineCapSquare); break; } @@ -244,30 +298,34 @@ void CanvasImpl::strokePath(std::shared_ptr const &path, double } if (!dashPattern.empty()) { - CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); } CGContextStrokePath(_context); } -void CanvasImpl::linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { +void CanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { CGContextSaveGState(_context); - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } CGContextSetLineWidth(_context, lineWidth); switch (lineJoin) { - case LineJoin::Miter: { + case lottie::LineJoin::Miter: { CGContextSetLineJoin(_context, kCGLineJoinMiter); break; } - case LineJoin::Round: { + case lottie::LineJoin::Round: { CGContextSetLineJoin(_context, kCGLineJoinRound); break; } - case LineJoin::Bevel: { + case lottie::LineJoin::Bevel: { CGContextSetLineJoin(_context, kCGLineJoinBevel); break; } @@ -278,15 +336,15 @@ void CanvasImpl::linearGradientStrokePath(std::shared_ptr const } switch (lineCap) { - case LineCap::Butt: { + case lottie::LineCap::Butt: { CGContextSetLineCap(_context, kCGLineCapButt); break; } - case LineCap::Round: { + case lottie::LineCap::Round: { CGContextSetLineCap(_context, kCGLineCapRound); break; } - case LineCap::Square: { + case lottie::LineCap::Square: { CGContextSetLineCap(_context, kCGLineCapSquare); break; } @@ -297,7 +355,11 @@ void CanvasImpl::linearGradientStrokePath(std::shared_ptr const } if (!dashPattern.empty()) { - CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); } CGContextReplacePathWithStrokedPath(_context); @@ -315,7 +377,12 @@ void CanvasImpl::linearGradientStrokePath(std::shared_ptr const assert(gradient.colors().size() == gradient.locations().size()); - CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); if (nativeGradient) { CGContextDrawLinearGradient(_context, nativeGradient, CGPointMake(start.x, start.y), CGPointMake(end.x, end.y), kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CFRelease(nativeGradient); @@ -325,25 +392,25 @@ void CanvasImpl::linearGradientStrokePath(std::shared_ptr const CGContextRestoreGState(_context); } -void CanvasImpl::radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) { +void CanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { CGContextSaveGState(_context); - CGContextBeginPath(_context); - lottie::CGPathCocoaImpl::withNativePath(path, [context = _context](CGPathRef nativePath) { - CGContextAddPath(context, nativePath); - }); + if (!addEnumeratedPath(_context, enumeratePath)) { + CGContextRestoreGState(_context); + return; + } CGContextSetLineWidth(_context, lineWidth); switch (lineJoin) { - case LineJoin::Miter: { + case lottie::LineJoin::Miter: { CGContextSetLineJoin(_context, kCGLineJoinMiter); break; } - case LineJoin::Round: { + case lottie::LineJoin::Round: { CGContextSetLineJoin(_context, kCGLineJoinRound); break; } - case LineJoin::Bevel: { + case lottie::LineJoin::Bevel: { CGContextSetLineJoin(_context, kCGLineJoinBevel); break; } @@ -354,15 +421,15 @@ void CanvasImpl::radialGradientStrokePath(std::shared_ptr const } switch (lineCap) { - case LineCap::Butt: { + case lottie::LineCap::Butt: { CGContextSetLineCap(_context, kCGLineCapButt); break; } - case LineCap::Round: { + case lottie::LineCap::Round: { CGContextSetLineCap(_context, kCGLineCapRound); break; } - case LineCap::Square: { + case lottie::LineCap::Square: { CGContextSetLineCap(_context, kCGLineCapSquare); break; } @@ -373,7 +440,11 @@ void CanvasImpl::radialGradientStrokePath(std::shared_ptr const } if (!dashPattern.empty()) { - CGContextSetLineDash(_context, dashPhase, dashPattern.data(), dashPattern.size()); + std::vector mappedDashPattern; + for (const auto value : dashPattern) { + mappedDashPattern.push_back(value); + } + CGContextSetLineDash(_context, dashPhase, mappedDashPattern.data(), mappedDashPattern.size()); } CGContextReplacePathWithStrokedPath(_context); @@ -391,7 +462,12 @@ void CanvasImpl::radialGradientStrokePath(std::shared_ptr const assert(gradient.colors().size() == gradient.locations().size()); - CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), gradient.locations().data(), gradient.locations().size()); + std::vector locations; + for (const auto location : gradient.locations()) { + locations.push_back(location); + } + + CGGradientRef nativeGradient = CGGradientCreateWithColorComponents(CGBitmapContextGetColorSpace(_topContext), components.data(), locations.data(), locations.size()); if (nativeGradient) { CGContextDrawRadialGradient(_context, nativeGradient, CGPointMake(startCenter.x, startCenter.y), startRadius, CGPointMake(endCenter.x, endCenter.y), endRadius, kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation); CFRelease(nativeGradient); @@ -401,7 +477,7 @@ void CanvasImpl::radialGradientStrokePath(std::shared_ptr const CGContextRestoreGState(_context); } -void CanvasImpl::fill(lottie::CGRect const &rect, Color const &fillColor) { +void CanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { CGFloat components[4] = { fillColor.r, fillColor.g, fillColor.b, fillColor.a }; CGColorRef nativeColor = CGColorCreate(CGBitmapContextGetColorSpace(_topContext), components); CGContextSetFillColorWithColor(_context, nativeColor); @@ -429,11 +505,11 @@ void CanvasImpl::setBlendMode(BlendMode blendMode) { CGContextSetBlendMode(_context, nativeMode); } -void CanvasImpl::setAlpha(double alpha) { +void CanvasImpl::setAlpha(float alpha) { CGContextSetAlpha(_context, alpha); } -void CanvasImpl::concatenate(lottie::CATransform3D const &transform) { +void CanvasImpl::concatenate(lottie::Transform2D const &transform) { CGContextConcatCTM(_context, CATransform3DGetAffineTransform(nativeTransform(transform))); } diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h new file mode 100644 index 0000000000..56918703ae --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.h @@ -0,0 +1,47 @@ +#ifndef NullCanvasImpl_h +#define NullCanvasImpl_h + +#include "Canvas.h" + +namespace lottieRendering { + +class NullCanvasImpl: public Canvas { +public: + NullCanvasImpl(int width, int height); + virtual ~NullCanvasImpl(); + + virtual int width() const override; + virtual int height() const override; + + virtual std::shared_ptr makeLayer(int width, int height) override; + + virtual void saveState() override; + virtual void restoreState() override; + + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; + + virtual void setBlendMode(BlendMode blendMode) override; + + virtual void setAlpha(float alpha) override; + + virtual void concatenate(lottie::Transform2D const &transform) override; + + virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; + + void flush(); + +private: + float _width = 0.0f; + float _height = 0.0f; + lottie::Transform2D _transform; +}; + +} + +#endif diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm new file mode 100644 index 0000000000..6030201180 --- /dev/null +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/NullCanvasImpl.mm @@ -0,0 +1,81 @@ +#include "NullCanvasImpl.h" + +namespace lottieRendering { + +namespace { + +void addEnumeratedPath(CanvasPathEnumerator const &enumeratePath) { + enumeratePath([&](PathCommand const &command) { + }); +} + +} + +NullCanvasImpl::NullCanvasImpl(int width, int height) : +_width(width), _height(height), _transform(lottie::Transform2D::identity()) { +} + +NullCanvasImpl::~NullCanvasImpl() { +} + +int NullCanvasImpl::width() const { + return _width; +} + +int NullCanvasImpl::height() const { + return _height; +} + +std::shared_ptr NullCanvasImpl::makeLayer(int width, int height) { + return std::make_shared(width, height); +} + +void NullCanvasImpl::saveState() { +} + +void NullCanvasImpl::restoreState() { +} + +void NullCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { + addEnumeratedPath(enumeratePath); +} + +void NullCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { +} + +void NullCanvasImpl::setBlendMode(BlendMode blendMode) { +} + +void NullCanvasImpl::setAlpha(float alpha) { +} + +void NullCanvasImpl::concatenate(lottie::Transform2D const &transform) { +} + +void NullCanvasImpl::draw(std::shared_ptr const &other, lottie::CGRect const &rect) { +} + +void NullCanvasImpl::flush() { +} + +} diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm index 90dd8f4c1e..c71328ef45 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/SoftwareLottieRenderer.mm @@ -3,24 +3,29 @@ #import "Canvas.h" #import "CoreGraphicsCanvasImpl.h" #import "ThorVGCanvasImpl.h" +#import "NullCanvasImpl.h" #include namespace { +static constexpr float minVisibleAlpha = 0.5f / 255.0f; + +static constexpr float minGlobalRectCalculationSize = 200.0f; + struct TransformedPath { lottie::BezierPath path; - lottie::CATransform3D transform; + lottie::Transform2D transform; - TransformedPath(lottie::BezierPath const &path_, lottie::CATransform3D const &transform_) : + TransformedPath(lottie::BezierPath const &path_, lottie::Transform2D const &transform_) : path(path_), transform(transform_) { } }; -static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr item, size_t subItemLimit, lottie::CATransform3D const &parentTransform, bool skipApplyTransform) { +static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { //TODO:remove skipApplyTransform - lottie::CATransform3D effectiveTransform = parentTransform; + lottie::Transform2D effectiveTransform = parentTransform; if (!skipApplyTransform && item->isGroup) { effectiveTransform = item->transform * effectiveTransform; } @@ -29,13 +34,17 @@ static lottie::CGRect collectPathBoundingBoxes(std::shared_ptrpath) { - boundingBox = item->pathBoundingBox.applyingTransform(effectiveTransform); + if (item->path->needsBoundsRecalculation) { + item->path->bounds = lottie::bezierPathsBoundingBoxParallel(bezierPathsBoundingBoxContext, item->path->path); + item->path->needsBoundsRecalculation = false; + } + boundingBox = item->path->bounds.applyingTransform(effectiveTransform); } for (size_t i = 0; i < maxSubitem; i++) { auto &subItem = item->subItems[i]; - lottie::CGRect subItemBoundingBox = collectPathBoundingBoxes(subItem, INT32_MAX, effectiveTransform, false); + lottie::CGRect subItemBoundingBox = collectPathBoundingBoxes(subItem, INT32_MAX, effectiveTransform, false, bezierPathsBoundingBoxContext); if (boundingBox.empty()) { boundingBox = subItemBoundingBox; @@ -47,11 +56,9 @@ static lottie::CGRect collectPathBoundingBoxes(std::shared_ptr collectPaths(std::shared_ptr item, size_t subItemLimit, lottie::CATransform3D const &parentTransform, bool skipApplyTransform) { - std::vector mappedPaths; - +static void enumeratePaths(std::shared_ptr item, size_t subItemLimit, lottie::Transform2D const &parentTransform, bool skipApplyTransform, std::function const &onPath) { //TODO:remove skipApplyTransform - lottie::CATransform3D effectiveTransform = parentTransform; + lottie::Transform2D effectiveTransform = parentTransform; if (!skipApplyTransform && item->isGroup) { effectiveTransform = item->transform * effectiveTransform; } @@ -59,44 +66,28 @@ static std::vector collectPaths(std::shared_ptrsubItems.size(), subItemLimit); if (item->path) { - mappedPaths.emplace_back(item->path.value(), effectiveTransform); + onPath(item->path->path, effectiveTransform); } - assert(!item->trimParams); for (size_t i = 0; i < maxSubitem; i++) { auto &subItem = item->subItems[i]; - auto subItemPaths = collectPaths(subItem, INT32_MAX, effectiveTransform, false); - - for (auto &path : subItemPaths) { - mappedPaths.emplace_back(path.path, path.transform); - } + enumeratePaths(subItem, INT32_MAX, effectiveTransform, false, onPath); } - - return mappedPaths; } } namespace lottie { -static void processRenderContentItem(std::shared_ptr const &contentItem, Vector2D const &globalSize, CATransform3D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { +static std::optional getRenderContentItemGlobalRect(std::shared_ptr const &contentItem, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { auto currentTransform = parentTransform; - - CATransform3D localTransform = contentItem->transform; + Transform2D localTransform = contentItem->transform; currentTransform = localTransform * currentTransform; - if (!currentTransform.isInvertible()) { - contentItem->renderData.isValid = false; - return; - } - std::optional globalRect; - - int drawContentDescendants = 0; - for (const auto &shadingVariant : contentItem->shadings) { - lottie::CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, lottie::CATransform3D::identity(), true); + lottie::CGRect shapeBounds = collectPathBoundingBoxes(contentItem, shadingVariant->subItemLimit, lottie::Transform2D::identity(), true, bezierPathsBoundingBoxContext); if (shadingVariant->stroke) { shapeBounds = shapeBounds.insetBy(-shadingVariant->stroke->lineWidth / 2.0, -shadingVariant->stroke->lineWidth / 2.0); @@ -105,8 +96,6 @@ static void processRenderContentItem(std::shared_ptr continue; } - drawContentDescendants += 1; - CGRect shapeGlobalBounds = shapeBounds.applyingTransform(currentTransform); if (globalRect) { globalRect = globalRect->unionWith(shapeGlobalBounds); @@ -115,101 +104,46 @@ static void processRenderContentItem(std::shared_ptr } } - if (contentItem->isGroup) { - for (auto it = contentItem->subItems.rbegin(); it != contentItem->subItems.rend(); it++) { - const auto &subItem = *it; - processRenderContentItem(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); - - if (subItem->renderData.isValid) { - drawContentDescendants += subItem->renderData.drawContentDescendants; - if (globalRect) { - globalRect = globalRect->unionWith(subItem->renderData.globalRect); - } else { - globalRect = subItem->renderData.globalRect; - } + for (const auto &subItem : contentItem->subItems) { + auto subGlobalRect = getRenderContentItemGlobalRect(subItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); + if (subGlobalRect) { + if (globalRect) { + globalRect = globalRect->unionWith(subGlobalRect.value()); + } else { + globalRect = subGlobalRect.value(); } } + } + + if (globalRect) { + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) + ); + return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); } else { - for (const auto &subItem : contentItem->subItems) { - subItem->renderData.isValid = false; - } + return std::nullopt; } - - if (!globalRect) { - contentItem->renderData.isValid = false; - return; - } - - CGRect integralGlobalRect( - std::floor(globalRect->x), - std::floor(globalRect->y), - std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), - std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) - ); - - if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) { - contentItem->renderData.isValid = false; - return; - } - if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) { - contentItem->renderData.isValid = false; - return; - } - - contentItem->renderData.isValid = true; - - contentItem->renderData.layer._bounds = CGRect(0.0, 0.0, 0.0, 0.0); - contentItem->renderData.layer._position = Vector2D(0.0, 0.0); - contentItem->renderData.layer._transform = contentItem->transform; - contentItem->renderData.layer._opacity = contentItem->alpha; - contentItem->renderData.layer._masksToBounds = false; - contentItem->renderData.layer._isHidden = false; - - contentItem->renderData.globalRect = integralGlobalRect; - contentItem->renderData.globalTransform = currentTransform; - contentItem->renderData.drawContentDescendants = drawContentDescendants; - contentItem->renderData.isInvertedMatte = false; } -static void processRenderTree(std::shared_ptr const &node, Vector2D const &globalSize, CATransform3D const &parentTransform, bool isInvertedMask, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { - if (node->isHidden() || node->alpha() == 0.0f) { - node->renderData.isValid = false; - return; - } - - if (node->masksToBounds()) { - if (node->bounds().empty()) { - node->renderData.isValid = false; - return; - } +static std::optional getRenderNodeGlobalRect(std::shared_ptr const &node, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, bool isInvertedMatte, BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + if (node->isHidden() || node->alpha() < minVisibleAlpha) { + return std::nullopt; } auto currentTransform = parentTransform; - - Vector2D localTranslation(node->position().x + -node->bounds().x, node->position().y + -node->bounds().y); - CATransform3D localTransform = node->transform(); - localTransform = localTransform.translated(localTranslation); - + Transform2D localTransform = node->transform(); currentTransform = localTransform * currentTransform; - if (!currentTransform.isInvertible()) { - node->renderData.isValid = false; - return; - } - - int drawContentDescendants = 0; std::optional globalRect; if (node->_contentItem) { - processRenderContentItem(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); - if (node->_contentItem->renderData.isValid) { - drawContentDescendants += node->_contentItem->renderData.drawContentDescendants; - globalRect = node->_contentItem->renderData.globalRect; - } + globalRect = getRenderContentItemGlobalRect(node->_contentItem, globalSize, currentTransform, bezierPathsBoundingBoxContext); } - bool isInvertedMatte = isInvertedMask; if (isInvertedMatte) { - CGRect globalBounds = node->bounds().applyingTransform(currentTransform); + CGRect globalBounds = CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform); if (globalRect) { globalRect = globalRect->unionWith(globalBounds); } else { @@ -217,119 +151,81 @@ static void processRenderTree(std::shared_ptr const &node, Vecto } } - for (const auto &item : node->subnodes()) { - processRenderTree(item, globalSize, currentTransform, false, bezierPathsBoundingBoxContext); - if (item->renderData.isValid) { - drawContentDescendants += item->renderData.drawContentDescendants; - + for (const auto &subNode : node->subnodes()) { + auto subGlobalRect = getRenderNodeGlobalRect(subNode, globalSize, currentTransform, false, bezierPathsBoundingBoxContext); + if (subGlobalRect) { if (globalRect) { - globalRect = globalRect->unionWith(item->renderData.globalRect); + globalRect = globalRect->unionWith(subGlobalRect.value()); } else { - globalRect = item->renderData.globalRect; + globalRect = subGlobalRect.value(); } } } - if (!globalRect) { - node->renderData.isValid = false; - return; + if (globalRect) { + CGRect integralGlobalRect( + std::floor(globalRect->x), + std::floor(globalRect->y), + std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), + std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) + ); + return integralGlobalRect.intersection(CGRect(0.0, 0.0, globalSize.x, globalSize.y)); + } else { + return std::nullopt; } - - CGRect integralGlobalRect( - std::floor(globalRect->x), - std::floor(globalRect->y), - std::ceil(globalRect->width + globalRect->x - floor(globalRect->x)), - std::ceil(globalRect->height + globalRect->y - floor(globalRect->y)) - ); - - if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(integralGlobalRect)) { - node->renderData.isValid = false; - return; - } - - bool masksToBounds = node->masksToBounds(); - if (masksToBounds) { - CGRect effectiveGlobalBounds = node->bounds().applyingTransform(currentTransform); - if (effectiveGlobalBounds.contains(CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { - masksToBounds = false; - } - } - - if (node->mask()) { - processRenderTree(node->mask(), globalSize, currentTransform, node->invertMask(), bezierPathsBoundingBoxContext); - if (node->mask()->renderData.isValid) { - if (!node->mask()->renderData.globalRect.intersects(integralGlobalRect)) { - node->renderData.isValid = false; - return; - } - } else { - node->renderData.isValid = false; - return; - } - } - - if (integralGlobalRect.width <= 0.0 || integralGlobalRect.height <= 0.0) { - node->renderData.isValid = false; - return; - } - - node->renderData.isValid = true; - - node->renderData.layer._bounds = node->bounds(); - node->renderData.layer._position = node->position(); - node->renderData.layer._transform = node->transform(); - node->renderData.layer._opacity = node->alpha(); - node->renderData.layer._masksToBounds = masksToBounds; - node->renderData.layer._isHidden = node->isHidden(); - - node->renderData.globalRect = integralGlobalRect; - node->renderData.globalTransform = currentTransform; - node->renderData.drawContentDescendants = drawContentDescendants; - node->renderData.isInvertedMatte = isInvertedMatte; } } namespace { -static void drawLottieContentItem(std::shared_ptr parentContext, std::shared_ptr item, double parentAlpha) { - if (!item->renderData.isValid) { - return; - } +static void drawLottieContentItem(std::shared_ptr const &parentContext, std::shared_ptr item, float parentAlpha, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + auto currentTransform = parentTransform; + lottie::Transform2D localTransform = item->transform; + currentTransform = localTransform * currentTransform; - float normalizedOpacity = item->renderData.layer.opacity(); - double layerAlpha = ((double)normalizedOpacity) * parentAlpha; + float normalizedOpacity = item->alpha; + float layerAlpha = ((float)normalizedOpacity) * parentAlpha; - if (item->renderData.layer.isHidden() || normalizedOpacity == 0.0f) { + if (normalizedOpacity == 0.0f) { return; } parentContext->saveState(); - std::shared_ptr currentContext; + std::shared_ptr const *currentContext; std::shared_ptr tempContext; bool needsTempContext = false; - needsTempContext = layerAlpha != 1.0 && item->renderData.drawContentDescendants > 1; + needsTempContext = layerAlpha != 1.0 && item->drawContentCount > 1; + std::optional globalRect; if (needsTempContext) { - auto tempContextValue = parentContext->makeLayer((int)(item->renderData.globalRect.width), (int)(item->renderData.globalRect.height)); + if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { + globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); + } else { + globalRect = lottie::getRenderContentItemGlobalRect(item, globalSize, parentTransform, bezierPathsBoundingBoxContext); + } + if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { + parentContext->restoreState(); + return; + } + + auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); tempContext = tempContextValue; - currentContext = tempContextValue; - currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.globalRect.x, -item->renderData.globalRect.y))); + currentContext = &tempContext; + (*currentContext)->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); - currentContext->saveState(); - currentContext->concatenate(item->renderData.globalTransform); + (*currentContext)->saveState(); + (*currentContext)->concatenate(currentTransform); } else { - currentContext = parentContext; + currentContext = &parentContext; } - parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(item->renderData.layer.position().x, item->renderData.layer.position().y))); - parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-item->renderData.layer.bounds().x, -item->renderData.layer.bounds().y))); - parentContext->concatenate(item->renderData.layer.transform()); + parentContext->concatenate(item->transform); - double renderAlpha = 1.0; + float renderAlpha = 1.0; if (tempContext) { renderAlpha = 1.0; } else { @@ -337,92 +233,122 @@ static void drawLottieContentItem(std::shared_ptr paren } for (const auto &shading : item->shadings) { - std::vector itemPaths; + lottieRendering::CanvasPathEnumerator iteratePaths; if (shading->explicitPath) { - itemPaths = shading->explicitPath.value(); - } else { - auto rawPaths = collectPaths(item, shading->subItemLimit, lottie::CATransform3D::identity(), true); - for (const auto &rawPath : rawPaths) { - itemPaths.push_back(rawPath.path.copyUsingTransform(rawPath.transform)); - } - } - - if (itemPaths.empty()) { - continue; - } - - std::shared_ptr path = lottie::CGPath::makePath(); - - const auto iterate = [&](LottiePathItem const *pathItem) { - switch (pathItem->type) { - case LottiePathItemTypeMoveTo: { - path->moveTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y)); - break; - } - case LottiePathItemTypeLineTo: { - path->addLineTo(lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y)); - break; - } - case LottiePathItemTypeCurveTo: { - path->addCurveTo(lottie::Vector2D(pathItem->points[2].x, pathItem->points[2].y), lottie::Vector2D(pathItem->points[0].x, pathItem->points[0].y), lottie::Vector2D(pathItem->points[1].x, pathItem->points[1].y)); - break; - } - case LottiePathItemTypeClose: { - path->closeSubpath(); - break; - } - default: { - break; - } - } - }; - - LottiePathItem pathItem; - for (const auto &path : itemPaths) { - std::optional previousElement; - for (const auto &element : path.elements()) { - if (previousElement.has_value()) { - if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { - pathItem.type = LottiePathItemTypeLineTo; - pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - iterate(&pathItem); - } else { - pathItem.type = LottiePathItemTypeCurveTo; - pathItem.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - pathItem.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); - pathItem.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); - iterate(&pathItem); + auto itemPaths = shading->explicitPath.value(); + iteratePaths = [itemPaths = itemPaths](std::function iterate) -> void { + lottieRendering::PathCommand pathCommand; + for (const auto &path : itemPaths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); } - } else { - pathItem.type = LottiePathItemTypeMoveTo; - pathItem.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); - iterate(&pathItem); } - previousElement = element; - } - if (path.closed().value_or(true)) { - pathItem.type = LottiePathItemTypeClose; - iterate(&pathItem); - } + }; + } else { + iteratePaths = [&](std::function iterate) { + enumeratePaths(item, shading->subItemLimit, lottie::Transform2D::identity(), true, [&](lottie::BezierPath const &sourcePath, lottie::Transform2D const &transform) { + auto path = sourcePath.copyUsingTransform(transform); + + lottieRendering::PathCommand pathCommand; + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); + } + }); + }; } + /*auto iteratePaths = [&](std::function iterate) -> void { + lottieRendering::PathCommand pathCommand; + for (const auto &path : itemPaths) { + std::optional previousElement; + for (const auto &element : path.elements()) { + if (previousElement.has_value()) { + if (previousElement->vertex.outTangentRelative().isZero() && element.vertex.inTangentRelative().isZero()) { + pathCommand.type = lottieRendering::PathCommandType::LineTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } else { + pathCommand.type = lottieRendering::PathCommandType::CurveTo; + pathCommand.points[2] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + pathCommand.points[1] = CGPointMake(element.vertex.inTangent.x, element.vertex.inTangent.y); + pathCommand.points[0] = CGPointMake(previousElement->vertex.outTangent.x, previousElement->vertex.outTangent.y); + iterate(pathCommand); + } + } else { + pathCommand.type = lottieRendering::PathCommandType::MoveTo; + pathCommand.points[0] = CGPointMake(element.vertex.point.x, element.vertex.point.y); + iterate(pathCommand); + } + previousElement = element; + } + if (path.closed().value_or(true)) { + pathCommand.type = lottieRendering::PathCommandType::Close; + iterate(pathCommand); + } + } + };*/ + if (shading->stroke) { if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->stroke->shading.get(); if (solidShading->opacity != 0.0) { - lottieRendering::LineJoin lineJoin = lottieRendering::LineJoin::Bevel; + lottie::LineJoin lineJoin = lottie::LineJoin::Bevel; switch (shading->stroke->lineJoin) { case lottie::LineJoin::Bevel: { - lineJoin = lottieRendering::LineJoin::Bevel; + lineJoin = lottie::LineJoin::Bevel; break; } case lottie::LineJoin::Round: { - lineJoin = lottieRendering::LineJoin::Round; + lineJoin = lottie::LineJoin::Round; break; } case lottie::LineJoin::Miter: { - lineJoin = lottieRendering::LineJoin::Miter; + lineJoin = lottie::LineJoin::Miter; break; } default: { @@ -430,18 +356,18 @@ static void drawLottieContentItem(std::shared_ptr paren } } - lottieRendering::LineCap lineCap = lottieRendering::LineCap::Square; + lottie::LineCap lineCap = lottie::LineCap::Square; switch (shading->stroke->lineCap) { case lottie::LineCap::Butt: { - lineCap = lottieRendering::LineCap::Butt; + lineCap = lottie::LineCap::Butt; break; } case lottie::LineCap::Round: { - lineCap = lottieRendering::LineCap::Round; + lineCap = lottie::LineCap::Round; break; } case lottie::LineCap::Square: { - lineCap = lottieRendering::LineCap::Square; + lineCap = lottie::LineCap::Square; break; } default: { @@ -449,25 +375,25 @@ static void drawLottieContentItem(std::shared_ptr paren } } - std::vector dashPattern; + std::vector dashPattern; if (!shading->stroke->dashPattern.empty()) { dashPattern = shading->stroke->dashPattern; } - currentContext->strokePath(path, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); + (*currentContext)->strokePath(iteratePaths, shading->stroke->lineWidth, lineJoin, lineCap, shading->stroke->dashPhase, dashPattern, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); } else if (shading->stroke->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { //TODO:gradient stroke } } } else if (shading->fill) { - lottieRendering::FillRule rule = lottieRendering::FillRule::NonZeroWinding; + lottie::FillRule rule = lottie::FillRule::NonZeroWinding; switch (shading->fill->rule) { case lottie::FillRule::EvenOdd: { - rule = lottieRendering::FillRule::EvenOdd; + rule = lottie::FillRule::EvenOdd; break; } case lottie::FillRule::NonZeroWinding: { - rule = lottieRendering::FillRule::NonZeroWinding; + rule = lottie::FillRule::NonZeroWinding; break; } default: { @@ -478,16 +404,16 @@ static void drawLottieContentItem(std::shared_ptr paren if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Solid) { lottie::RenderTreeNodeContentItem::SolidShading *solidShading = (lottie::RenderTreeNodeContentItem::SolidShading *)shading->fill->shading.get(); if (solidShading->opacity != 0.0) { - currentContext->fillPath(path, rule, lottieRendering::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); + (*currentContext)->fillPath(iteratePaths, rule, lottie::Color(solidShading->color.r, solidShading->color.g, solidShading->color.b, solidShading->color.a * solidShading->opacity * renderAlpha)); } } else if (shading->fill->shading->type() == lottie::RenderTreeNodeContentItem::ShadingType::Gradient) { lottie::RenderTreeNodeContentItem::GradientShading *gradientShading = (lottie::RenderTreeNodeContentItem::GradientShading *)shading->fill->shading.get(); if (gradientShading->opacity != 0.0) { - std::vector colors; - std::vector locations; + std::vector colors; + std::vector locations; for (const auto &color : gradientShading->colors) { - colors.push_back(lottieRendering::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha)); + colors.push_back(lottie::Color(color.r, color.g, color.b, color.a * gradientShading->opacity * renderAlpha)); } locations = gradientShading->locations; @@ -497,11 +423,11 @@ static void drawLottieContentItem(std::shared_ptr paren switch (gradientShading->gradientType) { case lottie::GradientType::Linear: { - currentContext->linearGradientFillPath(path, rule, gradient, start, end); + (*currentContext)->linearGradientFillPath(iteratePaths, rule, gradient, start, end); break; } case lottie::GradientType::Radial: { - currentContext->radialGradientFillPath(path, rule, gradient, start, 0.0, start, start.distanceTo(end)); + (*currentContext)->radialGradientFillPath(iteratePaths, rule, gradient, start, 0.0, start, start.distanceTo(end)); break; } default: { @@ -515,98 +441,117 @@ static void drawLottieContentItem(std::shared_ptr paren for (auto it = item->subItems.rbegin(); it != item->subItems.rend(); it++) { const auto &subItem = *it; - drawLottieContentItem(currentContext, subItem, renderAlpha); + drawLottieContentItem(*currentContext, subItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); } if (tempContext) { tempContext->restoreState(); - parentContext->concatenate(item->renderData.globalTransform.inverted()); + parentContext->concatenate(currentTransform.inverted()); parentContext->setAlpha(layerAlpha); - parentContext->draw(tempContext, item->renderData.globalRect); + parentContext->draw(tempContext, globalRect.value()); parentContext->setAlpha(1.0); } parentContext->restoreState(); } -static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr parentContext, lottie::Vector2D const &globalSize, double parentAlpha) { - if (!node->renderData.isValid) { - return; - } - float normalizedOpacity = node->renderData.layer.opacity(); - double layerAlpha = ((double)normalizedOpacity) * parentAlpha; +static void renderLottieRenderNode(std::shared_ptr node, std::shared_ptr const &parentContext, lottie::Vector2D const &globalSize, lottie::Transform2D const &parentTransform, float parentAlpha, bool isInvertedMatte, lottie::BezierPathsBoundingBoxContext &bezierPathsBoundingBoxContext) { + float normalizedOpacity = node->alpha(); + float layerAlpha = ((float)normalizedOpacity) * parentAlpha; - if (node->renderData.layer.isHidden() || normalizedOpacity == 0.0f) { + if (node->isHidden() || normalizedOpacity < minVisibleAlpha) { return; } - parentContext->saveState(); + auto currentTransform = parentTransform; + lottie::Transform2D localTransform = node->transform(); + currentTransform = localTransform * currentTransform; std::shared_ptr maskContext; std::shared_ptr currentContext; std::shared_ptr tempContext; - bool needsTempContext = false; - if (node->mask() && node->mask()->renderData.isValid) { - needsTempContext = true; - } else { - needsTempContext = layerAlpha != 1.0 || node->renderData.layer.masksToBounds(); + bool masksToBounds = node->masksToBounds(); + if (masksToBounds) { + lottie::CGRect effectiveGlobalBounds = lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y).applyingTransform(currentTransform); + if (effectiveGlobalBounds.width <= 0.0f || effectiveGlobalBounds.height <= 0.0f) { + return; + } + if (effectiveGlobalBounds.contains(lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y))) { + masksToBounds = false; + } } + parentContext->saveState(); + + bool needsTempContext = false; + if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { + needsTempContext = true; + } else { + needsTempContext = layerAlpha != 1.0 || masksToBounds; + } + + std::optional globalRect; if (needsTempContext) { - if ((node->mask() && node->mask()->renderData.isValid) || node->renderData.layer.masksToBounds()) { - auto maskBackingStorage = parentContext->makeLayer((int)(node->renderData.globalRect.width), (int)(node->renderData.globalRect.height)); + if (globalSize.x <= minGlobalRectCalculationSize && globalSize.y <= minGlobalRectCalculationSize) { + globalRect = lottie::CGRect(0.0, 0.0, globalSize.x, globalSize.y); + } else { + globalRect = lottie::getRenderNodeGlobalRect(node, globalSize, parentTransform, false, bezierPathsBoundingBoxContext); + } + if (!globalRect || globalRect->width <= 0.0f || globalRect->height <= 0.0f) { + parentContext->restoreState(); + return; + } + + if ((node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) || masksToBounds) { + auto maskBackingStorage = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); - maskBackingStorage->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.globalRect.x, -node->renderData.globalRect.y))); - maskBackingStorage->concatenate(node->renderData.globalTransform); + maskBackingStorage->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); + maskBackingStorage->concatenate(currentTransform); - if (node->renderData.layer.masksToBounds()) { - maskBackingStorage->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(1.0, 1.0, 1.0, 1.0)); + if (masksToBounds) { + maskBackingStorage->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(1.0f, 1.0f, 1.0f, 1.0f)); } - if (node->mask() && node->mask()->renderData.isValid) { - renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, 1.0); + if (node->mask() && !node->mask()->isHidden() && node->mask()->alpha() >= minVisibleAlpha) { + renderLottieRenderNode(node->mask(), maskBackingStorage, globalSize, currentTransform, 1.0, node->invertMask(), bezierPathsBoundingBoxContext); } maskContext = maskBackingStorage; } - auto tempContextValue = parentContext->makeLayer((int)(node->renderData.globalRect.width), (int)(node->renderData.globalRect.height)); + auto tempContextValue = parentContext->makeLayer((int)(globalRect->width), (int)(globalRect->height)); tempContext = tempContextValue; currentContext = tempContextValue; - currentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.globalRect.x, -node->renderData.globalRect.y))); + currentContext->concatenate(lottie::Transform2D::identity().translated(lottie::Vector2D(-globalRect->x, -globalRect->y))); currentContext->saveState(); - currentContext->concatenate(node->renderData.globalTransform); + currentContext->concatenate(currentTransform); } else { currentContext = parentContext; } - parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(node->renderData.layer.position().x, node->renderData.layer.position().y))); - parentContext->concatenate(lottie::CATransform3D::identity().translated(lottie::Vector2D(-node->renderData.layer.bounds().x, -node->renderData.layer.bounds().y))); - parentContext->concatenate(node->renderData.layer.transform()); + parentContext->concatenate(node->transform()); - double renderAlpha = 1.0; + float renderAlpha = 1.0f; if (tempContext) { - renderAlpha = 1.0; + renderAlpha = 1.0f; } else { renderAlpha = layerAlpha; } if (node->_contentItem) { - drawLottieContentItem(currentContext, node->_contentItem, renderAlpha); + drawLottieContentItem(currentContext, node->_contentItem, renderAlpha, globalSize, currentTransform, bezierPathsBoundingBoxContext); } - if (node->renderData.isInvertedMatte) { - currentContext->fill(lottie::CGRect(node->renderData.layer.bounds().x, node->renderData.layer.bounds().y, node->renderData.layer.bounds().width, node->renderData.layer.bounds().height), lottieRendering::Color(0.0, 0.0, 0.0, 1.0)); + if (isInvertedMatte) { + currentContext->fill(lottie::CGRect(0.0f, 0.0f, node->size().x, node->size().y), lottie::Color(0.0f, 0.0f, 0.0f, 1.0f)); currentContext->setBlendMode(lottieRendering::BlendMode::DestinationOut); } for (const auto &subnode : node->subnodes()) { - if (subnode->renderData.isValid) { - renderLottieRenderNode(subnode, currentContext, globalSize, renderAlpha); - } + renderLottieRenderNode(subnode, currentContext, globalSize, currentTransform, renderAlpha, false, bezierPathsBoundingBoxContext); } if (tempContext) { @@ -614,12 +559,12 @@ static void renderLottieRenderNode(std::shared_ptr node, if (maskContext) { tempContext->setBlendMode(lottieRendering::BlendMode::DestinationIn); - tempContext->draw(maskContext, lottie::CGRect(node->renderData.globalRect.x, node->renderData.globalRect.y, node->renderData.globalRect.width, node->renderData.globalRect.height)); + tempContext->draw(maskContext, lottie::CGRect(globalRect->x, globalRect->y, globalRect->width, globalRect->height)); } - parentContext->concatenate(node->renderData.globalTransform.inverted()); + parentContext->concatenate(currentTransform.inverted()); parentContext->setAlpha(layerAlpha); - parentContext->draw(tempContext, node->renderData.globalRect); + parentContext->draw(tempContext, globalRect.value()); } parentContext->restoreState(); @@ -657,30 +602,27 @@ CGRect getPathNativeBoundingBox(CGPathRef _Nonnull path) { return nil; } - if (!useReferenceRendering) { - return nil; - } - - processRenderTree(renderNode, lottie::Vector2D((int)size.width, (int)size.height), lottie::CATransform3D::identity().scaled(lottie::Vector2D(size.width / (double)animation.size.width, size.height / (double)animation.size.height)), false, *_bezierPathsBoundingBoxContext.get()); + lottie::Transform2D rootTransform = lottie::Transform2D::identity().scaled(lottie::Vector2D(size.width / (float)animation.size.width, size.height / (float)animation.size.height)); if (useReferenceRendering) { auto context = std::make_shared((int)size.width, (int)size.height); CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); - context->concatenate(lottie::CATransform3D::makeScale(scale.x, scale.y, 1.0)); + context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); - renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), 1.0); + renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); auto image = context->makeImage(); return [[UIImage alloc] initWithCGImage:std::static_pointer_cast(image)->nativeImage()]; } else { - /*auto context = std::make_shared((int)size.width, (int)size.height); + //auto context = std::make_shared((int)size.width, (int)size.height); + auto context = std::make_shared((int)size.width, (int)size.height); CGPoint scale = CGPointMake(size.width / (CGFloat)animation.size.width, size.height / (CGFloat)animation.size.height); - context->concatenate(lottie::CATransform3D::makeScale(scale.x, scale.y, 1.0)); + context->concatenate(lottie::Transform2D::makeScale(scale.x, scale.y)); - renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), 1.0);*/ + //renderLottieRenderNode(renderNode, context, lottie::Vector2D(context->width(), context->height()), rootTransform, 1.0, false, *_bezierPathsBoundingBoxContext.get()); return nil; } diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h index 6166b708a0..dbd676b5e9 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.h @@ -20,19 +20,19 @@ public: virtual void saveState() override; virtual void restoreState() override; - virtual void fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) override; - virtual void linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override; - virtual void strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) override; - virtual void linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; - virtual void radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) override; - virtual void fill(lottie::CGRect const &rect, Color const &fillColor) override; + virtual void fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) override; + virtual void linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottieRendering::Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) override; + virtual void linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) override; + virtual void radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) override; + virtual void fill(lottie::CGRect const &rect, lottie::Color const &fillColor) override; virtual void setBlendMode(BlendMode blendMode) override; - virtual void setAlpha(double alpha) override; + virtual void setAlpha(float alpha) override; - virtual void concatenate(lottie::CATransform3D const &transform) override; + virtual void concatenate(lottie::Transform2D const &transform) override; virtual void draw(std::shared_ptr const &other, lottie::CGRect const &rect) override; @@ -51,10 +51,9 @@ private: int _height = 0; std::unique_ptr _canvas; - //SkBlendMode _blendMode = SkBlendMode::kSrcOver; - double _alpha = 1.0; - lottie::CATransform3D _transform; - std::vector _stateStack; + float _alpha = 1.0; + lottie::Transform2D _transform; + std::vector _stateStack; int _bytesPerRow = 0; uint32_t *_backingData = nullptr; int _statsNumStrokes = 0; diff --git a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm index 0c81b5c8a7..4be6064df2 100644 --- a/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm +++ b/Tests/LottieMetalTest/SoftwareLottieRenderer/Sources/ThorVGCanvasImpl.mm @@ -4,22 +4,22 @@ namespace lottieRendering { namespace { -void tvgPath(std::shared_ptr const &path, tvg::Shape *shape) { - path->enumerate([shape](lottie::CGPathItem const &item) { - switch (item.type) { - case lottie::CGPathItem::Type::MoveTo: { - shape->moveTo(item.points[0].x, item.points[0].y); +void tvgPath(CanvasPathEnumerator const &enumeratePath, tvg::Shape *shape) { + enumeratePath([&](PathCommand const &command) { + switch (command.type) { + case PathCommandType::MoveTo: { + shape->moveTo(command.points[0].x, command.points[0].y); break; } - case lottie::CGPathItem::Type::LineTo: { - shape->lineTo(item.points[0].x, item.points[0].y); + case PathCommandType::LineTo: { + shape->lineTo(command.points[0].x, command.points[0].y); break; } - case lottie::CGPathItem::Type::CurveTo: { - shape->cubicTo(item.points[0].x, item.points[0].y, item.points[1].x, item.points[1].y, item.points[2].x, item.points[2].y); + case PathCommandType::CurveTo: { + shape->cubicTo(command.points[0].x, command.points[0].y, command.points[1].x, command.points[1].y, command.points[2].x, command.points[2].y); break; } - case lottie::CGPathItem::Type::Close: { + case PathCommandType::Close: { shape->close(); break; } @@ -27,7 +27,7 @@ void tvgPath(std::shared_ptr const &path, tvg::Shape *shape) { }); } -tvg::Matrix tvgTransform(lottie::CATransform3D const &transform) { +tvg::Matrix tvgTransform(lottie::Transform2D const &transform) { CGAffineTransform affineTransform = CATransform3DGetAffineTransform(lottie::nativeTransform(transform)); tvg::Matrix result; result.e11 = affineTransform.a; @@ -45,7 +45,7 @@ tvg::Matrix tvgTransform(lottie::CATransform3D const &transform) { } ThorVGCanvasImpl::ThorVGCanvasImpl(int width, int height) : -_width(width), _height(height), _transform(lottie::CATransform3D::identity()) { +_width(width), _height(height), _transform(lottie::Transform2D::identity()) { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ tvg::Initializer::init(0); @@ -89,21 +89,21 @@ void ThorVGCanvasImpl::restoreState() { _stateStack.pop_back(); } -void ThorVGCanvasImpl::fillPath(std::shared_ptr const &path, FillRule fillRule, Color const &color) { +void ThorVGCanvasImpl::fillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, lottie::Color const &color) { auto shape = tvg::Shape::gen(); - tvgPath(path, shape.get()); + tvgPath(enumeratePath, shape.get()); shape->transform(tvgTransform(_transform)); shape->fill((int)(color.r * 255.0), (int)(color.g * 255.0), (int)(color.b * 255.0), (int)(color.a * _alpha * 255.0)); - shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); _canvas->push(std::move(shape)); } -void ThorVGCanvasImpl::linearGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { +void ThorVGCanvasImpl::linearGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { auto shape = tvg::Shape::gen(); - tvgPath(path, shape.get()); + tvgPath(enumeratePath, shape.get()); shape->transform(tvgTransform(_transform)); @@ -124,14 +124,14 @@ void ThorVGCanvasImpl::linearGradientFillPath(std::shared_ptr co fill->colorStops(colors.data(), (uint32_t)colors.size()); shape->fill(std::move(fill)); - shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); _canvas->push(std::move(shape)); } -void ThorVGCanvasImpl::radialGradientFillPath(std::shared_ptr const &path, FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) { +void ThorVGCanvasImpl::radialGradientFillPath(CanvasPathEnumerator const &enumeratePath, lottie::FillRule fillRule, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { auto shape = tvg::Shape::gen(); - tvgPath(path, shape.get()); + tvgPath(enumeratePath, shape.get()); shape->transform(tvgTransform(_transform)); @@ -152,14 +152,14 @@ void ThorVGCanvasImpl::radialGradientFillPath(std::shared_ptr co fill->colorStops(colors.data(), (uint32_t)colors.size()); shape->fill(std::move(fill)); - shape->fill(fillRule == FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); + shape->fill(fillRule == lottie::FillRule::EvenOdd ? tvg::FillRule::EvenOdd : tvg::FillRule::Winding); _canvas->push(std::move(shape)); } -void ThorVGCanvasImpl::strokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Color const &color) { +void ThorVGCanvasImpl::strokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, lottie::Color const &color) { auto shape = tvg::Shape::gen(); - tvgPath(path, shape.get()); + tvgPath(enumeratePath, shape.get()); shape->transform(tvgTransform(_transform)); @@ -167,15 +167,15 @@ void ThorVGCanvasImpl::strokePath(std::shared_ptr const &path, d shape->strokeWidth(lineWidth); switch (lineJoin) { - case LineJoin::Miter: { + case lottie::LineJoin::Miter: { shape->strokeJoin(tvg::StrokeJoin::Miter); break; } - case LineJoin::Round: { + case lottie::LineJoin::Round: { shape->strokeJoin(tvg::StrokeJoin::Round); break; } - case LineJoin::Bevel: { + case lottie::LineJoin::Bevel: { shape->strokeJoin(tvg::StrokeJoin::Bevel); break; } @@ -186,15 +186,15 @@ void ThorVGCanvasImpl::strokePath(std::shared_ptr const &path, d } switch (lineCap) { - case LineCap::Butt: { + case lottie::LineCap::Butt: { shape->strokeCap(tvg::StrokeCap::Butt); break; } - case LineCap::Round: { + case lottie::LineCap::Round: { shape->strokeCap(tvg::StrokeCap::Round); break; } - case LineCap::Square: { + case lottie::LineCap::Square: { shape->strokeCap(tvg::StrokeCap::Square); break; } @@ -217,15 +217,13 @@ void ThorVGCanvasImpl::strokePath(std::shared_ptr const &path, d _canvas->push(std::move(shape)); } -void ThorVGCanvasImpl::linearGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { - assert(false); +void ThorVGCanvasImpl::linearGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &start, lottie::Vector2D const &end) { } -void ThorVGCanvasImpl::radialGradientStrokePath(std::shared_ptr const &path, double lineWidth, LineJoin lineJoin, LineCap lineCap, double dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, double startRadius, lottie::Vector2D const &endCenter, double endRadius) { - assert(false); +void ThorVGCanvasImpl::radialGradientStrokePath(CanvasPathEnumerator const &enumeratePath, float lineWidth, lottie::LineJoin lineJoin, lottie::LineCap lineCap, float dashPhase, std::vector const &dashPattern, Gradient const &gradient, lottie::Vector2D const &startCenter, float startRadius, lottie::Vector2D const &endCenter, float endRadius) { } -void ThorVGCanvasImpl::fill(lottie::CGRect const &rect, Color const &fillColor) { +void ThorVGCanvasImpl::fill(lottie::CGRect const &rect, lottie::Color const &fillColor) { auto shape = tvg::Shape::gen(); shape->appendRect(rect.x, rect.y, rect.width, rect.height, 0.0f, 0.0f); @@ -257,11 +255,11 @@ void ThorVGCanvasImpl::setBlendMode(BlendMode blendMode) { }*/ } -void ThorVGCanvasImpl::setAlpha(double alpha) { +void ThorVGCanvasImpl::setAlpha(float alpha) { _alpha = alpha; } -void ThorVGCanvasImpl::concatenate(lottie::CATransform3D const &transform) { +void ThorVGCanvasImpl::concatenate(lottie::Transform2D const &transform) { _transform = transform * _transform; /*_canvas->concat(SkM44( transform.m11, transform.m21, transform.m31, transform.m41, diff --git a/Tests/LottieMetalTest/Sources/ViewController.swift b/Tests/LottieMetalTest/Sources/ViewController.swift index ff9c7fe159..3fa91463c3 100644 --- a/Tests/LottieMetalTest/Sources/ViewController.swift +++ b/Tests/LottieMetalTest/Sources/ViewController.swift @@ -78,7 +78,7 @@ private final class ReferenceCompareTest { } var continueFromName: String? - //continueFromName = "778160933443732778.json" + //continueFromName = "35707580709863498.json" let _ = await processAnimationFolderAsync(basePath: bundlePath, path: "", stopOnFailure: true, process: { path, name, alwaysDraw in if let continueFromNameValue = continueFromName { @@ -119,7 +119,7 @@ public final class ViewController: UIViewController { self.view.layer.addSublayer(MetalEngine.shared.rootLayer) - if !"".isEmpty { + if "".isEmpty { if #available(iOS 13.0, *) { self.test = ReferenceCompareTest(view: self.view) } @@ -176,7 +176,7 @@ public final class ViewController: UIViewController { let updatesPerSecond = Double(numUpdates) / deltaTime startTime = timestamp numUpdates = 0 - print("updatesPerSecond: \(updatesPerSecond)") + print("Ours: updatesPerSecond: \(updatesPerSecond)") } } }.start() @@ -205,7 +205,7 @@ public final class ViewController: UIViewController { let updatesPerSecond = Double(numUpdates) / deltaTime startTime = timestamp numUpdates = 0 - print("updatesPerSecond: \(updatesPerSecond)") + print("Rlottie: updatesPerSecond: \(updatesPerSecond)") } } }.start() diff --git a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift index 6b17989a2e..634438cafa 100644 --- a/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift +++ b/submodules/AvatarVideoNode/Sources/AvatarVideoNode.swift @@ -27,7 +27,7 @@ public final class AvatarVideoNode: ASDisplayNode { private var fileDisposable = MetaDisposable() private var animationFile: TelegramMediaFile? - private var itemLayer: EmojiPagerContentComponent.View.ItemLayer? + private var itemLayer: EmojiKeyboardItemLayer? private var useAnimationNode = false private var animationNode: AnimatedStickerNode? private let stickerFetchedDisposable = MetaDisposable() @@ -101,7 +101,7 @@ public final class AvatarVideoNode: ASDisplayNode { let itemNativeFitSize = self.internalSize.width > 100.0 ? CGSize(width: 192.0, height: 192.0) : CGSize(width: 64.0, height: 64.0) let animationData = EntityKeyboardAnimationData(file: animationFile) - let itemLayer = EmojiPagerContentComponent.View.ItemLayer( + let itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index c895bc1284..b4d1d47116 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -179,6 +179,9 @@ final class ChatSendMessageContextScreenComponent: Component { private var animateOutToEmpty: Bool = false private var initializationDisplayLink: SharedDisplayLinkDriver.Link? + private var updateSourcePositionsDisplayLink: SharedDisplayLinkDriver.Link? + + private var stableSourceSendButtonFrame: CGRect? override init(frame: CGRect) { self.backgroundView = BlurredBackgroundView(color: .clear, enableBlur: true) @@ -273,6 +276,21 @@ final class ChatSendMessageContextScreenComponent: Component { let environment = environment[EnvironmentType.self].value + if let previousEnvironment = self.environment, previousEnvironment.inputHeight != 0.0, environment.inputHeight == 0.0 { + DispatchQueue.main.async { [weak self] in + guard let self, let component = self.component else { + return + } + let stableSourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + if self.stableSourceSendButtonFrame != stableSourceSendButtonFrame { + self.stableSourceSendButtonFrame = stableSourceSendButtonFrame + if !self.isUpdating { + self.state?.updated(transition: .spring(duration: 0.35)) + } + } + } + } + var transition = transition var transitionIsImmediate = transition.animation.isImmediate @@ -365,7 +383,19 @@ final class ChatSendMessageContextScreenComponent: Component { self.addSubview(sendButton) } - let sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + let sourceSendButtonFrame: CGRect + switch self.presentationAnimationState { + case .animatedOut: + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + self.stableSourceSendButtonFrame = sourceSendButtonFrame + default: + if let stableSourceSendButtonFrame = self.stableSourceSendButtonFrame { + sourceSendButtonFrame = stableSourceSendButtonFrame + } else { + sourceSendButtonFrame = convertFrame(component.sourceSendButton.bounds, from: component.sourceSendButton.view, to: self) + self.stableSourceSendButtonFrame = sourceSendButtonFrame + } + } let sendButtonScale: CGFloat switch self.presentationAnimationState { @@ -802,11 +832,14 @@ final class ChatSendMessageContextScreenComponent: Component { } let standaloneReactionAnimation: AnimatedStickerNode + var effectiveScale: CGFloat = 1.0 #if targetEnvironment(simulator) standaloneReactionAnimation = DirectAnimatedStickerNode() + effectiveScale = 1.4 #else if "".isEmpty { standaloneReactionAnimation = DirectAnimatedStickerNode() + effectiveScale = 1.4 } else { standaloneReactionAnimation = LottieMetalAnimatedStickerNode() } @@ -823,7 +856,7 @@ final class ChatSendMessageContextScreenComponent: Component { let pathPrefix = component.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(customEffectResource.id) let source = AnimatedStickerResourceSource(account: component.context.account, resource: customEffectResource, fitzModifier: nil) - standaloneReactionAnimation.setup(source: source, width: Int(effectSize.width), height: Int(effectSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) + standaloneReactionAnimation.setup(source: source, width: Int(effectSize.width * effectiveScale), height: Int(effectSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) standaloneReactionAnimation.completed = { [weak self, weak standaloneReactionAnimation] _ in guard let self else { return @@ -840,6 +873,8 @@ final class ChatSendMessageContextScreenComponent: Component { if !self.isUpdating { self.state?.updated(transition: .easeInOut(duration: 0.2)) } + + self.endEditing(true) }) })) } @@ -1011,6 +1046,13 @@ final class ChatSendMessageContextScreenComponent: Component { } } + if let standaloneReactionAnimation, let targetView = messageItemView.effectIconView { + let effectSize = CGSize(width: 380.0, height: 380.0) + var effectFrame = effectSize.centered(around: targetView.convert(targetView.bounds.center, to: self)) + effectFrame.origin.x -= effectFrame.width * 0.3 + transition.setFrame(view: standaloneReactionAnimation.view, frame: effectFrame) + } + if let reactionContextNode = self.reactionContextNode { let reactionContextY = environment.statusBarHeight let size = availableSize diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift index 3d629bea08..f47734863d 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackEmojisItem.swift @@ -122,8 +122,8 @@ final class StickerPackEmojisItemNode: GridItemNode { private var boundsChangeTrackerLayer = SimpleLayer() - private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] - private var visibleItemPlaceholderViews: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] + private var visibleItemPlaceholderViews: [EmojiKeyboardItemLayer.Key: EmojiPagerContentComponent.View.ItemPlaceholderView] = [:] private let containerNode: ASDisplayNode private let titleNode: ImmediateTextNode @@ -195,7 +195,7 @@ final class StickerPackEmojisItemNode: GridItemNode { func targetItem(at point: CGPoint) -> (TelegramMediaFile, CALayer)? { if let (item, _) = self.item(atPoint: point), let file = item.itemFile { - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: 0, itemId: .animation(.file(file.fileId)) ) @@ -237,7 +237,7 @@ final class StickerPackEmojisItemNode: GridItemNode { private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (EmojiPagerContentComponent.Item, CGRect)? { let localPoint = point - var closestItem: (key: EmojiPagerContentComponent.View.ItemLayer.Key, distance: CGFloat)? + var closestItem: (key: EmojiKeyboardItemLayer.Key, distance: CGFloat)? for (key, itemLayer) in self.visibleItemLayers { if extendedHitRange { @@ -308,7 +308,7 @@ final class StickerPackEmojisItemNode: GridItemNode { let animationRenderer = item.animationRenderer let theme = item.theme let items = item.items - var validIds = Set() + var validIds = Set() let itemLayout: ItemLayout if let current = self.itemLayout, current.width == self.size.width && current.itemsCount == items.count { @@ -322,7 +322,7 @@ final class StickerPackEmojisItemNode: GridItemNode { for index in 0 ..< items.count { let item = items[index] - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: 0, itemId: .animation(.file(item.file.fileId)) ) @@ -334,7 +334,7 @@ final class StickerPackEmojisItemNode: GridItemNode { var updateItemLayerPlaceholder = false var itemTransition = transition - let itemLayer: EmojiPagerContentComponent.View.ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.visibleItemLayers[itemId] { itemLayer = current } else { @@ -342,7 +342,7 @@ final class StickerPackEmojisItemNode: GridItemNode { itemTransition = .immediate let animationData = EntityKeyboardAnimationData(file: item.file) - itemLayer = EmojiPagerContentComponent.View.ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index a9ea0f9da1..3352d55bf5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -639,6 +639,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var replyRecognizer: ChatSwipeToReplyRecognizer? private var currentSwipeAction: ChatControllerInteractionSwipeAction? + private var fetchEffectDisposable: Disposable? + //private let debugNode: ASDisplayNode override public var visibility: ListViewItemNodeVisibility { @@ -839,6 +841,10 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + deinit { + self.fetchEffectDisposable?.dispose() + } override public func cancelInsertionAnimations() { self.shadowNode.layer.removeAllAnimations() @@ -5878,6 +5884,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI private var additionalAnimationNodes: [ChatMessageTransitionNode.DecorationItemNode] = [] private func playPremiumStickerAnimation(effect: AvailableMessageEffects.MessageEffect, force: Bool) { + guard let item = self.item else { + return + } if self.playedPremiumStickerAnimation && !force { return } @@ -5885,10 +5894,16 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if let effectAnimation = effect.effectAnimation { self.playEffectAnimation(resource: effectAnimation.resource, isStickerEffect: true) + if self.fetchEffectDisposable == nil { + self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectAnimation), resource: effectAnimation.resource).startStrict() + } } else { let effectSticker = effect.effectSticker if let effectFile = effectSticker.videoThumbnails.first { self.playEffectAnimation(resource: effectFile.resource, isStickerEffect: true) + if self.fetchEffectDisposable == nil { + self.fetchEffectDisposable = freeMediaFileResourceInteractiveFetched(account: item.context.account, userLocation: .other, fileReference: .standalone(media: effectSticker), resource: effectFile.resource).startStrict() + } } } } @@ -5935,17 +5950,20 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let pathPrefix = item.context.account.postbox.mediaBox.shortLivedResourceCachePathPrefix(resource.id) let additionalAnimationNode: AnimatedStickerNode + var effectiveScale: CGFloat = 1.0 #if targetEnvironment(simulator) additionalAnimationNode = DirectAnimatedStickerNode() + effectiveScale = 1.4 #else if "".isEmpty { additionalAnimationNode = DirectAnimatedStickerNode() + effectiveScale = 1.4 } else { additionalAnimationNode = LottieMetalAnimatedStickerNode() } #endif additionalAnimationNode.updateLayout(size: animationSize) - additionalAnimationNode.setup(source: source, width: Int(animationSize.width), height: Int(animationSize.height), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) + additionalAnimationNode.setup(source: source, width: Int(animationSize.width * effectiveScale), height: Int(animationSize.height * effectiveScale), playbackMode: .once, mode: .direct(cachePathPrefix: pathPrefix)) var animationFrame: CGRect if isStickerEffect { let offsetScale: CGFloat = 0.3 diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift new file mode 100644 index 0000000000..19ee0562ae --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift @@ -0,0 +1,477 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import MultiAnimationRenderer +import AnimationCache +import SwiftSignalKit +import TelegramCore +import AccountContext +import TelegramPresentationData +import EmojiTextAttachmentView +import EmojiStatusComponent + +final class EmojiKeyboardCloneItemLayer: SimpleLayer { +} + +public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { + public struct Key: Hashable { + var groupId: AnyHashable + var itemId: EmojiPagerContentComponent.ItemContent.Id + + public init( + groupId: AnyHashable, + itemId: EmojiPagerContentComponent.ItemContent.Id + ) { + self.groupId = groupId + self.itemId = itemId + } + } + + enum Badge: Equatable { + case premium + case locked + case featured + case text(String) + case customFile(TelegramMediaFile) + } + + public let item: EmojiPagerContentComponent.Item + private let context: AccountContext + + private var content: EmojiPagerContentComponent.ItemContent + private var theme: PresentationTheme? + + private let placeholderColor: UIColor + let pixelSize: CGSize + let pointSize: CGSize + private let size: CGSize + private var disposable: Disposable? + private var fetchDisposable: Disposable? + private var premiumBadgeView: PremiumBadgeView? + + private var iconLayer: SimpleLayer? + private var tintIconLayer: SimpleLayer? + + private(set) var tintContentLayer: SimpleLayer? + + private var badge: Badge? + private var validSize: CGSize? + + private var isInHierarchyValue: Bool = false + public var isVisibleForAnimations: Bool = false { + didSet { + if self.isVisibleForAnimations != oldValue { + self.updatePlayback() + } + } + } + public private(set) var displayPlaceholder: Bool = false + public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void + + weak var cloneLayer: EmojiKeyboardCloneItemLayer? { + didSet { + if let cloneLayer = self.cloneLayer { + cloneLayer.contents = self.contents + } + } + } + + override public var contents: Any? { + didSet { + self.onContentsUpdate() + if let cloneLayer = self.cloneLayer { + cloneLayer.contents = self.contents + } + } + } + + override public var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override public var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override public func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override public func removeAllAnimations() { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override public func removeAnimation(forKey: String) { + if let mirrorLayer = self.tintContentLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } + + public var onContentsUpdate: () -> Void = {} + public var onLoop: () -> Void = {} + + public init( + item: EmojiPagerContentComponent.Item, + context: AccountContext, + attemptSynchronousLoad: Bool, + content: EmojiPagerContentComponent.ItemContent, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + placeholderColor: UIColor, + blurredBadgeColor: UIColor, + accentIconColor: UIColor, + pointSize: CGSize, + onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void + ) { + self.item = item + self.context = context + self.content = content + self.placeholderColor = placeholderColor + self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder + + let scale = min(2.0, UIScreenScale) + let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale) + self.pixelSize = pixelSize + self.pointSize = pointSize + self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale) + + super.init() + + switch content { + case let .animation(animationData): + let loadAnimation: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + + strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, unique: false, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0, customColor: animationData.isTemplate ? .white : nil)) + } + + if attemptSynchronousLoad { + if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) { + self.updateDisplayPlaceholder(displayPlaceholder: true) + + self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in + if !isFinal { + if !success { + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + return + } + + Queue.mainQueue().async { + loadAnimation() + + if !success { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + }) + } else { + loadAnimation() + } + } else { + self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in + if !isFinal { + if !success { + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + return + } + + Queue.mainQueue().async { + loadAnimation() + + if !success { + guard let strongSelf = self else { + return + } + + strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) + } + } + }) + } + case let .staticEmoji(staticEmoji): + let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + let preScaleFactor: CGFloat = 1.0 + let scaledSize = CGSize(width: floor(size.width * preScaleFactor), height: floor(size.height * preScaleFactor)) + let scaleFactor = scaledSize.width / size.width + + context.scaleBy(x: 1.0 / scaleFactor, y: 1.0 / scaleFactor) + + let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black) + let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil) + UIGraphicsPushContext(context) + string.draw(at: CGPoint(x: floorToScreenPixels((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floorToScreenPixels((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY))) + UIGraphicsPopContext() + }) + self.contents = image?.cgImage + case let .icon(icon): + let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + switch icon { + case .premiumStar: + if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) { + let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case let .topic(title, color): + let colors = topicIconColors(for: color) + if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { + let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case .stop: + if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/NoIcon"), color: .white) { + let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + case .add: + break + } + + UIGraphicsPopContext() + })?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal) + self.contents = image?.cgImage + } + + if case .icon(.add) = content { + let tintContentLayer = SimpleLayer() + self.tintContentLayer = tintContentLayer + + let iconLayer = SimpleLayer() + self.iconLayer = iconLayer + self.addSublayer(iconLayer) + + let tintIconLayer = SimpleLayer() + self.tintIconLayer = tintIconLayer + tintContentLayer.addSublayer(tintIconLayer) + } + } + + override public init(layer: Any) { + guard let layer = layer as? EmojiKeyboardItemLayer else { + preconditionFailure() + } + + self.context = layer.context + self.item = layer.item + + self.content = layer.content + self.placeholderColor = layer.placeholderColor + self.size = layer.size + self.pixelSize = layer.pixelSize + self.pointSize = layer.pointSize + + self.onUpdateDisplayPlaceholder = { _, _ in } + + super.init(layer: layer) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.disposable?.dispose() + self.fetchDisposable?.dispose() + } + + public override func action(forKey event: String) -> CAAction? { + if event == kCAOnOrderIn { + self.isInHierarchyValue = true + } else if event == kCAOnOrderOut { + self.isInHierarchyValue = false + } + self.updatePlayback() + return nullAction + } + + func update( + content: EmojiPagerContentComponent.ItemContent, + theme: PresentationTheme + ) { + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + var contentUpdated = false + if self.content != content { + self.content = content + contentUpdated = true + } + + if themeUpdated || contentUpdated { + if case let .icon(icon) = content, case let .topic(title, color) = icon { + let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + let colors = topicIconColors(for: color) + if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { + let imageSize = image.size + image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) + } + + UIGraphicsPopContext() + }) + self.contents = image?.cgImage + } else if case .icon(.add) = content { + guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else { + return + } + func generateIcon(color: UIColor) -> UIImage? { + return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + UIGraphicsPushContext(context) + + context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) + context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) + context.setFillColor(color.cgColor) + + let plusSize = CGSize(width: 4.5, height: 31.5) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) + context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) + context.fillPath() + + UIGraphicsPopContext() + }) + } + + let needsVibrancy = !theme.overallDarkAppearance + let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + + iconLayer.contents = generateIcon(color: color)?.cgImage + tintIconLayer.contents = generateIcon(color: .white)?.cgImage + + tintIconLayer.isHidden = !needsVibrancy + } + } + } + + func update( + transition: Transition, + size: CGSize, + badge: Badge?, + blurredBadgeColor: UIColor, + blurredBadgeBackgroundColor: UIColor + ) { + if self.badge != badge || self.validSize != size { + self.badge = badge + self.validSize = size + + if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer { + transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size)) + transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size)) + } + + if let badge = badge { + var badgeTransition = transition + let premiumBadgeView: PremiumBadgeView + if let current = self.premiumBadgeView { + premiumBadgeView = current + } else { + badgeTransition = .immediate + premiumBadgeView = PremiumBadgeView(context: self.context) + self.premiumBadgeView = premiumBadgeView + self.addSublayer(premiumBadgeView.layer) + } + + let badgeDiameter = min(16.0, floor(size.height * 0.5)) + let badgeSize = CGSize(width: badgeDiameter, height: badgeDiameter) + badgeTransition.setFrame(view: premiumBadgeView, frame: CGRect(origin: CGPoint(x: size.width - badgeSize.width, y: size.height - badgeSize.height), size: badgeSize)) + premiumBadgeView.update(transition: badgeTransition, badge: badge, backgroundColor: blurredBadgeColor, size: badgeSize) + + self.blurredRepresentationBackgroundColor = blurredBadgeBackgroundColor + self.blurredRepresentationTarget = premiumBadgeView.contentLayer + } else { + if let premiumBadgeView = self.premiumBadgeView { + self.premiumBadgeView = nil + premiumBadgeView.removeFromSuperview() + + self.blurredRepresentationBackgroundColor = nil + self.blurredRepresentationTarget = nil + } + } + } + } + + private func updatePlayback() { + let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations + + self.shouldBeAnimating = shouldBePlaying + } + + public override func updateDisplayPlaceholder(displayPlaceholder: Bool) { + if self.displayPlaceholder == displayPlaceholder { + return + } + + self.displayPlaceholder = displayPlaceholder + self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0) + } + + public override func transitionToContents(_ contents: AnyObject, didLoop: Bool) { + self.contents = contents + + if self.displayPlaceholder { + self.displayPlaceholder = false + self.onUpdateDisplayPlaceholder(false, 0.2) + self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) + } + + if didLoop { + self.onLoop() + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index da9153aea9..157b94af2b 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -26,144 +26,6 @@ import EmojiStatusComponent import TelegramNotices import GenerateStickerPlaceholderImage -private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) -private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) -private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) - - -private final class WarpView: UIView { - private final class WarpPartView: UIView { - let cloneView: PortalView - - init?(contentView: PortalSourceView) { - guard let cloneView = PortalView(matchPosition: false) else { - return nil - } - self.cloneView = cloneView - - super.init(frame: CGRect()) - - self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) - - self.clipsToBounds = true - self.addSubview(cloneView.view) - contentView.addPortal(view: cloneView) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(containerSize: CGSize, rect: CGRect, transition: Transition) { - transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height))) - } - } - - let contentView: PortalSourceView - - private let clippingView: UIView - - private var warpViews: [WarpPartView] = [] - private let warpMaskContainer: UIView - private let warpMaskGradientLayer: SimpleGradientLayer - - override init(frame: CGRect) { - self.contentView = PortalSourceView() - self.clippingView = UIView() - - self.warpMaskContainer = UIView() - self.warpMaskGradientLayer = SimpleGradientLayer() - self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer - - super.init(frame: frame) - - self.clippingView.addSubview(self.contentView) - - self.clippingView.clipsToBounds = true - self.addSubview(self.clippingView) - self.addSubview(self.warpMaskContainer) - - for _ in 0 ..< 8 { - if let warpView = WarpPartView(contentView: self.contentView) { - self.warpViews.append(warpView) - self.warpMaskContainer.addSubview(warpView) - } - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) { - transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) - - let allItemsHeight = warpHeight * 0.5 - for i in 0 ..< self.warpViews.count { - let itemHeight = warpHeight / CGFloat(self.warpViews.count) - let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count) - let _ = itemHeight - - let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count) - let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5 - let endPoint = CGPoint(x: cos(alpha), y: sin(alpha)) - let prevAngle = alpha + da - let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle)) - var angle: CGFloat - angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x) - - let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y) - let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5 - let _ = itemLength - - var transform: CATransform3D - transform = CATransform3DIdentity - transform.m34 = 1.0 / 240.0 - - transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight) - transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0) - - let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength - let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength)) - transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0)) - transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength))) - transition.setTransform(view: self.warpViews[i], transform: transform) - self.warpViews[i].update(containerSize: size, rect: rect, transition: transition) - } - - let clippingTopInset: CGFloat = topInset - let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0)) - transition.setPosition(view: self.clippingView, position: frame.center) - transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size)) - self.clippingView.clipsToBounds = true - - transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight))) - - var locations: [NSNumber] = [] - var colors: [CGColor] = [] - let numStops = 6 - for i in 0 ..< numStops { - let step = CGFloat(i) / CGFloat(numStops - 1) - locations.append(step as NSNumber) - colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor) - } - - let gradientHeight: CGFloat = 6.0 - self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight) - self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) - - self.warpMaskGradientLayer.locations = locations - self.warpMaskGradientLayer.colors = colors - self.warpMaskGradientLayer.type = .axial - - transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight))) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return self.contentView.hitTest(point, with: event) - } -} - public struct EmojiComponentReactionItem: Equatable { public var reaction: MessageReaction.Reaction public var file: TelegramMediaFile @@ -260,2177 +122,6 @@ public final class EntityKeyboardAnimationData: Equatable { } } -public class PassthroughLayer: CALayer { - public var mirrorLayer: CALayer? - - override init() { - super.init() - } - - override init(layer: Any) { - super.init(layer: layer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override public var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override public var opacity: Float { - get { - return super.opacity - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.opacity = value - } - super.opacity = value - } - } - - override public var sublayerTransform: CATransform3D { - get { - return super.sublayerTransform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.sublayerTransform = value - } - super.sublayerTransform = value - } - } - - override public var transform: CATransform3D { - get { - return super.transform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.transform = value - } - super.transform = value - } - } - - override public func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override public func removeAllAnimations() { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override public func removeAnimation(forKey: String) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } -} - -open class PassthroughView: UIView { - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - public let passthroughView: UIView - - override public init(frame: CGRect) { - self.passthroughView = UIView() - - super.init(frame: frame) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} - -private class PassthroughShapeLayer: CAShapeLayer { - var mirrorLayer: CAShapeLayer? - - override init() { - super.init() - } - - override init(layer: Any) { - super.init(layer: layer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override var opacity: Float { - get { - return super.opacity - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.opacity = value - } - super.opacity = value - } - } - - override var sublayerTransform: CATransform3D { - get { - return super.sublayerTransform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.sublayerTransform = value - } - super.sublayerTransform = value - } - } - - override var transform: CATransform3D { - get { - return super.transform - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.transform = value - } - super.transform = value - } - } - - override var path: CGPath? { - get { - return super.path - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.path = value - } - super.path = value - } - } - - override var fillColor: CGColor? { - get { - return super.fillColor - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.fillColor = value - } - super.fillColor = value - } - } - - override var fillRule: CAShapeLayerFillRule { - get { - return super.fillRule - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.fillRule = value - } - super.fillRule = value - } - } - - override var strokeColor: CGColor? { - get { - return super.strokeColor - } set(value) { - /*if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeColor = value - }*/ - super.strokeColor = value - } - } - - override var strokeStart: CGFloat { - get { - return super.strokeStart - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeStart = value - } - super.strokeStart = value - } - } - - override var strokeEnd: CGFloat { - get { - return super.strokeEnd - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.strokeEnd = value - } - super.strokeEnd = value - } - } - - override var lineWidth: CGFloat { - get { - return super.lineWidth - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineWidth = value - } - super.lineWidth = value - } - } - - override var miterLimit: CGFloat { - get { - return super.miterLimit - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.miterLimit = value - } - super.miterLimit = value - } - } - - override var lineCap: CAShapeLayerLineCap { - get { - return super.lineCap - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineCap = value - } - super.lineCap = value - } - } - - override var lineJoin: CAShapeLayerLineJoin { - get { - return super.lineJoin - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineJoin = value - } - super.lineJoin = value - } - } - - override var lineDashPhase: CGFloat { - get { - return super.lineDashPhase - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineDashPhase = value - } - super.lineDashPhase = value - } - } - - override var lineDashPattern: [NSNumber]? { - get { - return super.lineDashPattern - } set(value) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.lineDashPattern = value - } - super.lineDashPattern = value - } - } - - override func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override func removeAllAnimations() { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override func removeAnimation(forKey: String) { - if let mirrorLayer = self.mirrorLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } -} - -private let itemBadgeTextFont: UIFont = { - return Font.regular(10.0) -}() - -private final class PremiumBadgeView: UIView { - private let context: AccountContext - - private var badge: EmojiPagerContentComponent.View.ItemLayer.Badge? - - let contentLayer: SimpleLayer - private let overlayColorLayer: SimpleLayer - private let iconLayer: SimpleLayer - private var customFileLayer: InlineFileIconLayer? - - init(context: AccountContext) { - self.context = context - - self.contentLayer = SimpleLayer() - self.contentLayer.contentsGravity = .resize - self.contentLayer.masksToBounds = true - - self.overlayColorLayer = SimpleLayer() - self.overlayColorLayer.masksToBounds = true - - self.iconLayer = SimpleLayer() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.contentLayer) - self.layer.addSublayer(self.overlayColorLayer) - self.layer.addSublayer(self.iconLayer) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(transition: Transition, badge: EmojiPagerContentComponent.View.ItemLayer.Badge, backgroundColor: UIColor, size: CGSize) { - if self.badge != badge { - self.badge = badge - - switch badge { - case .premium: - self.iconLayer.contents = premiumBadgeIcon?.cgImage - case .featured: - self.iconLayer.contents = featuredBadgeIcon?.cgImage - case .locked: - self.iconLayer.contents = lockedBadgeIcon?.cgImage - case let .text(text): - let string = NSAttributedString(string: text, font: itemBadgeTextFont) - let size = CGSize(width: 12.0, height: 12.0) - let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - let image = generateImage(size, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - string.draw(at: CGPoint(x: floor((size.width - stringBounds.width) * 0.5), y: floor((size.height - stringBounds.height) * 0.5))) - UIGraphicsPopContext() - }) - self.iconLayer.contents = image?.cgImage - case .customFile: - self.iconLayer.contents = nil - } - - if case let .customFile(customFile) = badge { - let customFileLayer: InlineFileIconLayer - if let current = self.customFileLayer { - customFileLayer = current - } else { - customFileLayer = InlineFileIconLayer( - context: self.context, - userLocation: .other, - attemptSynchronousLoad: false, - file: customFile, - cache: self.context.animationCache, - renderer: self.context.animationRenderer, - unique: false, - placeholderColor: .clear, - pointSize: CGSize(width: 18.0, height: 18.0), - dynamicColor: nil - ) - self.customFileLayer = customFileLayer - self.layer.addSublayer(customFileLayer) - } - let _ = customFileLayer - } else { - if let customFileLayer = self.customFileLayer { - self.customFileLayer = nil - customFileLayer.removeFromSuperlayer() - } - } - } - - let iconInset: CGFloat - switch badge { - case .premium: - iconInset = 2.0 - case .featured: - iconInset = 0.0 - case .locked: - iconInset = 0.0 - case .text, .customFile: - iconInset = 0.0 - } - - switch badge { - case .text, .customFile: - self.contentLayer.isHidden = true - self.overlayColorLayer.isHidden = true - default: - self.contentLayer.isHidden = false - self.overlayColorLayer.isHidden = false - } - - self.overlayColorLayer.backgroundColor = backgroundColor.cgColor - - transition.setFrame(layer: self.contentLayer, frame: CGRect(origin: CGPoint(), size: size)) - transition.setCornerRadius(layer: self.contentLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) - - transition.setFrame(layer: self.overlayColorLayer, frame: CGRect(origin: CGPoint(), size: size)) - transition.setCornerRadius(layer: self.overlayColorLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) - - transition.setFrame(layer: self.iconLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: iconInset, dy: iconInset)) - - if let customFileLayer = self.customFileLayer { - let iconSize = CGSize(width: 18.0, height: 18.0) - transition.setFrame(layer: customFileLayer, frame: CGRect(origin: CGPoint(), size: iconSize)) - } - } -} - -private final class GroupHeaderActionButton: UIButton { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerLayer: SimpleLayer - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - private let textLayer: SimpleLayer - private let tintTextLayer: SimpleLayer - private let pressed: () -> Void - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.tintContainerLayer = SimpleLayer() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.masksToBounds = true - - self.tintBackgroundLayer = SimpleLayer() - self.tintBackgroundLayer.masksToBounds = true - - self.textLayer = SimpleLayer() - self.tintTextLayer = SimpleLayer() - - super.init(frame: CGRect()) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer - - self.layer.addSublayer(self.backgroundLayer) - self.layer.addSublayer(self.textLayer) - - self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) - - self.tintContainerLayer.addSublayer(self.tintBackgroundLayer) - self.tintContainerLayer.addSublayer(self.tintTextLayer) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - @objc private func onPressed() { - self.pressed() - } - - override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.alpha = 0.6 - - return super.beginTracking(touch, with: event) - } - - override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.endTracking(touch, with: event) - } - - override func cancelTracking(with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.cancelTracking(with: event) - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.touchesCancelled(touches, with: event) - } - - func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize { - let textConstrainedWidth: CGFloat = 100.0 - - let needsVibrancy = !theme.overallDarkAppearance && compact - - let foregroundColor: UIColor - let backgroundColor: UIColor - - if compact { - foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - backgroundColor = foregroundColor.withMultipliedAlpha(0.2) - } else { - foregroundColor = theme.list.itemCheckColors.foregroundColor - backgroundColor = theme.list.itemCheckColors.fillColor - } - - self.backgroundLayer.backgroundColor = backgroundColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor - - self.tintContainerLayer.isHidden = !needsVibrancy - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0) - let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor) - let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - tintString.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize) - } - - let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0) - - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) - self.textLayer.frame = textFrame - self.tintTextLayer.frame = textFrame - - self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 - - self.tintBackgroundLayer.frame = self.backgroundLayer.frame - self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius - - return size - } -} - -private final class GroupHeaderLayer: UIView { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - private let actionPressed: () -> Void - private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void - - private let textLayer: SimpleLayer - private let tintTextLayer: SimpleLayer - - private var subtitleLayer: SimpleLayer? - private var tintSubtitleLayer: SimpleLayer? - private var lockIconLayer: SimpleLayer? - private var tintLockIconLayer: SimpleLayer? - private var badgeLayer: SimpleLayer? - private var tintBadgeLayer: SimpleLayer? - private(set) var clearIconLayer: SimpleLayer? - private var tintClearIconLayer: SimpleLayer? - private var separatorLayer: SimpleLayer? - private var tintSeparatorLayer: SimpleLayer? - private var actionButton: GroupHeaderActionButton? - - private var groupEmbeddedView: GroupEmbeddedView? - - private var theme: PresentationTheme? - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - - let tintContentLayer: SimpleLayer - - init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { - self.actionPressed = actionPressed - self.performItemAction = performItemAction - - self.textLayer = SimpleLayer() - self.tintTextLayer = SimpleLayer() - - self.tintContentLayer = SimpleLayer() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.textLayer) - self.tintContentLayer.addSublayer(self.tintTextLayer) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update( - context: AccountContext, - theme: PresentationTheme, - forceNeedsVibrancy: Bool, - layoutType: EmojiPagerContentComponent.ItemLayoutType, - hasTopSeparator: Bool, - actionButtonTitle: String?, - actionButtonIsCompact: Bool, - title: String, - subtitle: String?, - badge: String?, - isPremiumLocked: Bool, - hasClear: Bool, - embeddedItems: [EmojiPagerContentComponent.Item]?, - isStickers: Bool, - constrainedSize: CGSize, - insets: UIEdgeInsets, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - attemptSynchronousLoad: Bool - ) -> (size: CGSize, centralContentWidth: CGFloat) { - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - } - - let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy - - let textOffsetY: CGFloat - if hasTopSeparator { - textOffsetY = 9.0 - } else { - textOffsetY = 0.0 - } - - let subtitleColor: UIColor - if theme.overallDarkAppearance && forceNeedsVibrancy { - subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2) - } else { - subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - } - - let color: UIColor - let needsTintText: Bool - if subtitle != nil { - color = theme.chat.inputPanel.primaryTextColor - needsTintText = false - } else { - color = subtitleColor - needsTintText = true - } - - let titleHorizontalOffset: CGFloat - if isPremiumLocked { - titleHorizontalOffset = 10.0 + 2.0 - } else { - titleHorizontalOffset = 0.0 - } - - var actionButtonSize: CGSize? - if let actionButtonTitle = actionButtonTitle { - let actionButton: GroupHeaderActionButton - if let current = self.actionButton { - actionButton = current - } else { - actionButton = GroupHeaderActionButton(pressed: self.actionPressed) - self.actionButton = actionButton - self.addSubview(actionButton) - self.tintContentLayer.addSublayer(actionButton.tintContainerLayer) - } - - actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact) - } else { - if let actionButton = self.actionButton { - self.actionButton = nil - actionButton.removeFromSuperview() - } - } - - var clearSize: CGSize = .zero - var clearWidth: CGFloat = 0.0 - if hasClear { - var updateImage = themeUpdated - - let clearIconLayer: SimpleLayer - if let current = self.clearIconLayer { - clearIconLayer = current - } else { - updateImage = true - clearIconLayer = SimpleLayer() - self.clearIconLayer = clearIconLayer - self.layer.addSublayer(clearIconLayer) - } - let tintClearIconLayer: SimpleLayer - if let current = self.tintClearIconLayer { - tintClearIconLayer = current - } else { - updateImage = true - tintClearIconLayer = SimpleLayer() - self.tintClearIconLayer = tintClearIconLayer - self.tintContentLayer.addSublayer(tintClearIconLayer) - } - - tintClearIconLayer.isHidden = !needsVibrancy - - clearSize = clearIconLayer.bounds.size - if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) { - clearSize = image.size - clearIconLayer.contents = image.cgImage - } - if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) { - tintClearIconLayer.contents = image.cgImage - } - - tintClearIconLayer.frame = clearIconLayer.frame - clearWidth = 4.0 + clearSize.width - } else { - if let clearIconLayer = self.clearIconLayer { - self.clearIconLayer = nil - clearIconLayer.removeFromSuperlayer() - } - if let tintClearIconLayer = self.tintClearIconLayer { - self.tintClearIconLayer = nil - tintClearIconLayer.removeFromSuperlayer() - } - } - - var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0 - if let actionButtonSize = actionButtonSize { - if actionButtonIsCompact { - textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0 - } else { - textConstrainedWidth -= actionButtonSize.width + 10.0 - } - } - if clearWidth > 0.0 { - textConstrainedWidth -= clearWidth + 8.0 - } - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont - let stringValue: String - if subtitle == nil { - font = Font.medium(13.0) - stringValue = title.uppercased() - } else { - font = Font.semibold(16.0) - stringValue = title - } - let string = NSAttributedString(string: stringValue, font: font, textColor: color) - let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - //string.draw(in: stringBounds) - string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - //whiteString.draw(in: stringBounds) - whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - })?.cgImage - self.tintTextLayer.isHidden = !needsVibrancy - self.currentTextLayout = (title, color, textConstrainedWidth, textSize) - } - - var badgeSize: CGSize = .zero - if let badge { - func generateBadgeImage(color: UIColor) -> UIImage? { - let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0) - return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - context.setFillColor(color.cgColor) - context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath) - context.fillPath() - - context.setBlendMode(.clear) - - UIGraphicsPushContext(context) - - string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) - - UIGraphicsPopContext() - }) - } - - let badgeLayer: SimpleLayer - if let current = self.badgeLayer { - badgeLayer = current - } else { - badgeLayer = SimpleLayer() - self.badgeLayer = badgeLayer - self.layer.addSublayer(badgeLayer) - - if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) { - badgeLayer.contents = image.cgImage - badgeLayer.bounds = CGRect(origin: .zero, size: image.size) - } - } - badgeSize = badgeLayer.bounds.size - - let tintBadgeLayer: SimpleLayer - if let current = self.tintBadgeLayer { - tintBadgeLayer = current - } else { - tintBadgeLayer = SimpleLayer() - self.tintBadgeLayer = tintBadgeLayer - self.tintContentLayer.addSublayer(tintBadgeLayer) - - if let image = generateBadgeImage(color: .white) { - tintBadgeLayer.contents = image.cgImage - } - } - } else { - if let badgeLayer = self.badgeLayer { - self.badgeLayer = nil - badgeLayer.removeFromSuperlayer() - } - if let tintBadgeLayer = self.tintBadgeLayer { - self.tintBadgeLayer = nil - tintBadgeLayer.removeFromSuperlayer() - } - } - - let textFrame: CGRect - if subtitle == nil { - textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize) - } else { - textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) - } - self.textLayer.frame = textFrame - self.tintTextLayer.frame = textFrame - self.tintTextLayer.isHidden = !needsTintText - - if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer { - badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size) - tintBadgeLayer.frame = badgeLayer.frame - } - - if isPremiumLocked { - let lockIconLayer: SimpleLayer - if let current = self.lockIconLayer { - lockIconLayer = current - } else { - lockIconLayer = SimpleLayer() - self.lockIconLayer = lockIconLayer - self.layer.addSublayer(lockIconLayer) - } - if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) { - let imageSize = image.size - lockIconLayer.contents = image.cgImage - lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize) - } else { - lockIconLayer.contents = nil - } - - let tintLockIconLayer: SimpleLayer - if let current = self.tintLockIconLayer { - tintLockIconLayer = current - } else { - tintLockIconLayer = SimpleLayer() - self.tintLockIconLayer = tintLockIconLayer - self.tintContentLayer.addSublayer(tintLockIconLayer) - } - if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) { - tintLockIconLayer.contents = image.cgImage - tintLockIconLayer.frame = lockIconLayer.frame - tintLockIconLayer.isHidden = !needsVibrancy - } else { - tintLockIconLayer.contents = nil - } - } else { - if let lockIconLayer = self.lockIconLayer { - self.lockIconLayer = nil - lockIconLayer.removeFromSuperlayer() - } - if let tintLockIconLayer = self.tintLockIconLayer { - self.tintLockIconLayer = nil - tintLockIconLayer.removeFromSuperlayer() - } - } - - let subtitleSize: CGSize - if let subtitle = subtitle { - var updateSubtitleContents: UIImage? - var updateTintSubtitleContents: UIImage? - if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth { - subtitleSize = currentSubtitleLayout.size - } else { - let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor) - let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - }) - updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - whiteString.draw(in: stringBounds) - - UIGraphicsPopContext() - }) - self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize) - } - - let subtitleLayer: SimpleLayer - if let current = self.subtitleLayer { - subtitleLayer = current - } else { - subtitleLayer = SimpleLayer() - self.subtitleLayer = subtitleLayer - self.layer.addSublayer(subtitleLayer) - } - - if let updateSubtitleContents = updateSubtitleContents { - subtitleLayer.contents = updateSubtitleContents.cgImage - } - - let tintSubtitleLayer: SimpleLayer - if let current = self.tintSubtitleLayer { - tintSubtitleLayer = current - } else { - tintSubtitleLayer = SimpleLayer() - self.tintSubtitleLayer = tintSubtitleLayer - self.tintContentLayer.addSublayer(tintSubtitleLayer) - } - tintSubtitleLayer.isHidden = !needsVibrancy - - if let updateTintSubtitleContents = updateTintSubtitleContents { - tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage - } - - let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize) - subtitleLayer.frame = subtitleFrame - tintSubtitleLayer.frame = subtitleFrame - } else { - subtitleSize = CGSize() - if let subtitleLayer = self.subtitleLayer { - self.subtitleLayer = nil - subtitleLayer.removeFromSuperlayer() - } - if let tintSubtitleLayer = self.tintSubtitleLayer { - self.tintSubtitleLayer = nil - tintSubtitleLayer.removeFromSuperlayer() - } - } - - self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize) - - var size: CGSize - size = CGSize(width: constrainedSize.width, height: constrainedSize.height) - - if let embeddedItems = embeddedItems { - let groupEmbeddedView: GroupEmbeddedView - if let current = self.groupEmbeddedView { - groupEmbeddedView = current - } else { - groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction) - self.groupEmbeddedView = groupEmbeddedView - self.addSubview(groupEmbeddedView) - } - - let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0) - groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize) - groupEmbeddedView.update( - context: context, - theme: theme, - insets: insets, - size: groupEmbeddedViewSize, - items: embeddedItems, - isStickers: isStickers, - cache: cache, - renderer: renderer, - attemptSynchronousLoad: attemptSynchronousLoad - ) - } else { - if let groupEmbeddedView = self.groupEmbeddedView { - self.groupEmbeddedView = nil - groupEmbeddedView.removeFromSuperview() - } - } - - if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton { - let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize) - actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size) - actionButton.center = actionButtonFrame.center - } - - if hasTopSeparator { - let separatorLayer: SimpleLayer - if let current = self.separatorLayer { - separatorLayer = current - } else { - separatorLayer = SimpleLayer() - self.separatorLayer = separatorLayer - self.layer.addSublayer(separatorLayer) - } - separatorLayer.backgroundColor = subtitleColor.cgColor - separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) - - let tintSeparatorLayer: SimpleLayer - if let current = self.tintSeparatorLayer { - tintSeparatorLayer = current - } else { - tintSeparatorLayer = SimpleLayer() - self.tintSeparatorLayer = tintSeparatorLayer - self.tintContentLayer.addSublayer(tintSeparatorLayer) - } - tintSeparatorLayer.backgroundColor = UIColor.white.cgColor - tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) - - tintSeparatorLayer.isHidden = !needsVibrancy - } else { - if let separatorLayer = self.separatorLayer { - self.separatorLayer = separatorLayer - separatorLayer.removeFromSuperlayer() - } - if let tintSeparatorLayer = self.tintSeparatorLayer { - self.tintSeparatorLayer = tintSeparatorLayer - tintSeparatorLayer.removeFromSuperlayer() - } - } - - return (size, titleHorizontalOffset + textSize.width + clearWidth) - } - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - return super.hitTest(point, with: event) - } - - func tapGesture(point: CGPoint) -> Bool { - if let groupEmbeddedView = self.groupEmbeddedView { - return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView)) - } else { - return false - } - } -} - -private final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, PagerExpandableScrollView { - private struct ItemLayout { - var itemSize: CGFloat - var itemSpacing: CGFloat - var sideInset: CGFloat - var itemCount: Int - var contentSize: CGSize - - init(height: CGFloat, sideInset: CGFloat, itemCount: Int) { - self.itemSize = 30.0 - self.itemSpacing = 20.0 - self.sideInset = sideInset - self.itemCount = itemCount - - self.contentSize = CGSize(width: self.sideInset * 2.0 + CGFloat(self.itemCount) * self.itemSize + CGFloat(self.itemCount - 1) * self.itemSpacing, height: height) - } - - func frame(at index: Int) -> CGRect { - return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize)) - } - - func visibleItems(for rect: CGRect) -> Range? { - let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0) - var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) - minVisibleIndex = max(0, minVisibleIndex) - var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) - maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1) - - if minVisibleIndex <= maxVisibleIndex { - return minVisibleIndex ..< (maxVisibleIndex + 1) - } else { - return nil - } - } - } - - private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void - - private var visibleItemLayers: [EmojiPagerContentComponent.View.ItemLayer.Key: EmojiPagerContentComponent.View.ItemLayer] = [:] - private var ignoreScrolling: Bool = false - - private var context: AccountContext? - private var theme: PresentationTheme? - private var cache: AnimationCache? - private var renderer: MultiAnimationRenderer? - private var currentInsets: UIEdgeInsets? - private var currentSize: CGSize? - private var items: [EmojiPagerContentComponent.Item]? - private var isStickers: Bool = false - - private var itemLayout: ItemLayout? - - init(performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { - self.performItemAction = performItemAction - - super.init(frame: CGRect()) - - self.delaysContentTouches = false - if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { - self.contentInsetAdjustmentBehavior = .never - } - if #available(iOS 13.0, *) { - self.automaticallyAdjustsScrollIndicatorInsets = false - } - self.showsVerticalScrollIndicator = true - self.showsHorizontalScrollIndicator = false - self.delegate = self - self.clipsToBounds = true - self.scrollsToTop = false - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func tapGesture(point: CGPoint) -> Bool { - guard let itemLayout = self.itemLayout else { - return false - } - - for (_, itemLayer) in self.visibleItemLayers { - if itemLayer.frame.inset(by: UIEdgeInsets(top: -6.0, left: -itemLayout.itemSpacing, bottom: -6.0, right: -itemLayout.itemSpacing)).contains(point) { - self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer) - return true - } - } - - return false - } - - func scrollViewDidScroll(_ scrollView: UIScrollView) { - if !self.ignoreScrolling { - self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: false) - } - } - - private func updateVisibleItems(transition: Transition, attemptSynchronousLoad: Bool) { - guard let context = self.context, let theme = self.theme, let itemLayout = self.itemLayout, let items = self.items, let cache = self.cache, let renderer = self.renderer else { - return - } - - var validIds = Set() - if let itemRange = itemLayout.visibleItems(for: self.bounds) { - for index in itemRange.lowerBound ..< itemRange.upperBound { - let item = items[index] - let itemId = EmojiPagerContentComponent.View.ItemLayer.Key( - groupId: AnyHashable(0), - itemId: item.content.id - ) - validIds.insert(itemId) - - let itemLayer: EmojiPagerContentComponent.View.ItemLayer - if let current = self.visibleItemLayers[itemId] { - itemLayer = current - } else { - itemLayer = EmojiPagerContentComponent.View.ItemLayer( - item: item, - context: context, - attemptSynchronousLoad: attemptSynchronousLoad, - content: item.content, - cache: cache, - renderer: renderer, - placeholderColor: .clear, - blurredBadgeColor: .clear, - accentIconColor: theme.list.itemAccentColor, - pointSize: CGSize(width: 32.0, height: 32.0), - onUpdateDisplayPlaceholder: { _, _ in - } - ) - self.visibleItemLayers[itemId] = itemLayer - self.layer.addSublayer(itemLayer) - } - - switch item.tintMode { - case let .custom(color): - itemLayer.layerTintColor = color.cgColor - case .accent: - itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor - case .primary: - itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor - case .none: - itemLayer.layerTintColor = nil - } - - let itemFrame = itemLayout.frame(at: index) - itemLayer.frame = itemFrame - - itemLayer.isVisibleForAnimations = self.isStickers ? context.sharedContext.energyUsageSettings.loopStickers : context.sharedContext.energyUsageSettings.loopEmoji - } - } - - var removedIds: [EmojiPagerContentComponent.View.ItemLayer.Key] = [] - for (id, itemLayer) in self.visibleItemLayers { - if !validIds.contains(id) { - removedIds.append(id) - itemLayer.removeFromSuperlayer() - } - } - for id in removedIds { - self.visibleItemLayers.removeValue(forKey: id) - } - } - - func update( - context: AccountContext, - theme: PresentationTheme, - insets: UIEdgeInsets, - size: CGSize, - items: [EmojiPagerContentComponent.Item], - isStickers: Bool, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - attemptSynchronousLoad: Bool - ) { - if self.theme === theme && self.currentInsets == insets && self.currentSize == size && self.items == items { - return - } - - self.context = context - self.theme = theme - self.currentInsets = insets - self.currentSize = size - self.items = items - self.isStickers = isStickers - self.cache = cache - self.renderer = renderer - - let itemLayout = ItemLayout(height: size.height, sideInset: insets.left, itemCount: items.count) - self.itemLayout = itemLayout - - self.ignoreScrolling = true - if itemLayout.contentSize != self.contentSize { - self.contentSize = itemLayout.contentSize - } - self.ignoreScrolling = false - - self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: attemptSynchronousLoad) - } -} - -private final class GroupExpandActionButton: UIButton { - override static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerLayer: SimpleLayer - - private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - private let textLayer: SimpleLayer - private let pressed: () -> Void - - init(pressed: @escaping () -> Void) { - self.pressed = pressed - - self.tintContainerLayer = SimpleLayer() - - self.backgroundLayer = SimpleLayer() - self.backgroundLayer.masksToBounds = true - - self.tintBackgroundLayer = SimpleLayer() - self.tintBackgroundLayer.masksToBounds = true - - self.textLayer = SimpleLayer() - - super.init(frame: CGRect()) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer - - self.layer.addSublayer(self.backgroundLayer) - - self.layer.addSublayer(self.textLayer) - - self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) - } - - required init(coder: NSCoder) { - preconditionFailure() - } - - @objc private func onPressed() { - self.pressed() - } - - override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.alpha = 0.6 - - return super.beginTracking(touch, with: event) - } - - override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.endTracking(touch, with: event) - } - - override func cancelTracking(with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.cancelTracking(with: event) - } - - override func touchesCancelled(_ touches: Set, with event: UIEvent?) { - let alpha = self.alpha - self.alpha = 1.0 - self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) - - super.touchesCancelled(touches, with: event) - } - - func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize { - let textConstrainedWidth: CGFloat = 100.0 - let color = theme.list.itemCheckColors.foregroundColor - - if useOpaqueTheme { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor - } else { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor - } - self.tintContainerLayer.backgroundColor = UIColor.white.cgColor - - let textSize: CGSize - if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { - textSize = currentTextLayout.size - } else { - let font: UIFont = Font.semibold(13.0) - let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color) - let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) - textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) - self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - UIGraphicsPushContext(context) - - string.draw(in: stringBounds) - - UIGraphicsPopContext() - })?.cgImage - self.currentTextLayout = (title, color, textConstrainedWidth, textSize) - } - - var sideInset: CGFloat = 10.0 - if textSize.width > 24.0 { - sideInset = 6.0 - } - let size = CGSize(width: textSize.width + sideInset * 2.0, height: 28.0) - - let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) - self.textLayer.frame = textFrame - - self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size) - self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 - self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0 - - return size - } -} - -public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { - private final class EmojiSearchTextField: UITextField { - override func textRect(forBounds bounds: CGRect) -> CGRect { - return bounds.integral - } - } - - private struct Params: Equatable { - var context: AccountContext - var theme: PresentationTheme - var forceNeedsVibrancy: Bool - var strings: PresentationStrings - var text: String - var useOpaqueTheme: Bool - var isActive: Bool - var hasPresetSearch: Bool - var textInputState: EmojiSearchSearchBarComponent.TextInputState - var searchState: EmojiPagerContentComponent.SearchState - var size: CGSize - var canFocus: Bool - var searchCategories: EmojiSearchCategories? - - static func ==(lhs: Params, rhs: Params) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.theme !== rhs.theme { - return false - } - if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy { - return false - } - if lhs.strings !== rhs.strings { - return false - } - if lhs.text != rhs.text { - return false - } - if lhs.useOpaqueTheme != rhs.useOpaqueTheme { - return false - } - if lhs.isActive != rhs.isActive { - return false - } - if lhs.hasPresetSearch != rhs.hasPresetSearch { - return false - } - if lhs.textInputState != rhs.textInputState { - return false - } - if lhs.searchState != rhs.searchState { - return false - } - if lhs.size != rhs.size { - return false - } - if lhs.canFocus != rhs.canFocus { - return false - } - if lhs.searchCategories != rhs.searchCategories { - return false - } - return true - } - } - - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - private let activated: (Bool) -> Void - private let deactivated: (Bool) -> Void - private let updateQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void - - let tintContainerView: UIView - - private let backgroundLayer: SimpleLayer - private let tintBackgroundLayer: SimpleLayer - - private let statusIcon = ComponentView() - - private let clearIconView: UIImageView - private let clearIconTintView: UIImageView - private let clearIconButton: HighlightTrackingButton - - private let cancelButtonTintTitle: ComponentView - private let cancelButtonTitle: ComponentView - private let cancelButton: HighlightTrackingButton - - private var placeholderContent = ComponentView() - - private var textFrame: CGRect? - private var textField: EmojiSearchTextField? - - private var tapRecognizer: UITapGestureRecognizer? - private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group? - - private var params: Params? - - public var wantsDisplayBelowKeyboard: Bool { - return self.textField != nil - } - - init(activated: @escaping (Bool) -> Void, deactivated: @escaping (Bool) -> Void, updateQuery: @escaping (EmojiPagerContentComponent.SearchQuery?) -> Void) { - self.activated = activated - self.deactivated = deactivated - self.updateQuery = updateQuery - - self.tintContainerView = UIView() - - self.backgroundLayer = SimpleLayer() - self.tintBackgroundLayer = SimpleLayer() - - self.clearIconView = UIImageView() - self.clearIconTintView = UIImageView() - self.clearIconButton = HighlightableButton() - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - self.cancelButtonTintTitle = ComponentView() - self.cancelButtonTitle = ComponentView() - self.cancelButton = HighlightTrackingButton() - - super.init(frame: CGRect()) - - self.layer.addSublayer(self.backgroundLayer) - self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer) - - self.addSubview(self.clearIconView) - self.tintContainerView.addSubview(self.clearIconTintView) - self.addSubview(self.clearIconButton) - - self.addSubview(self.cancelButton) - self.clipsToBounds = true - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer - - let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) - self.tapRecognizer = tapRecognizer - self.addGestureRecognizer(tapRecognizer) - - self.cancelButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTitleView.alpha = 0.4 - } - if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { - cancelButtonTintTitleView.layer.removeAnimation(forKey: "opacity") - cancelButtonTintTitleView.alpha = 0.4 - } - } else { - if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { - cancelButtonTitleView.alpha = 1.0 - cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { - cancelButtonTintTitleView.alpha = 1.0 - cancelButtonTintTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - } - self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) - - self.clearIconButton.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconView.alpha = 0.4 - strongSelf.clearIconTintView.layer.removeAnimation(forKey: "opacity") - strongSelf.clearIconTintView.alpha = 0.4 - } else { - strongSelf.clearIconView.alpha = 1.0 - strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - strongSelf.clearIconTintView.alpha = 1.0 - strongSelf.clearIconTintView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) - } - } - } - self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { - if case .ended = recognizer.state { - let location = recognizer.location(in: self) - if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil { - self.clearCategorySearch() - } else { - self.activateTextInput() - } - } - } - - func clearCategorySearch() { - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent : true) - } - } - - private func activateTextInput() { - guard let params = self.params else { - return - } - if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true { - let backgroundFrame = self.backgroundLayer.frame - let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) - - let textField = EmojiSearchTextField(frame: textFieldFrame) - textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance - textField.autocorrectionType = .no - textField.returnKeyType = .search - self.textField = textField - self.insertSubview(textField, belowSubview: self.clearIconView) - textField.delegate = self - textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) - } - - if params.canFocus { - self.currentPresetSearchTerm = nil - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - placeholderContentView.clearSelection(dispatchEvent: false) - } - } - - self.activated(true) - - self.textField?.becomeFirstResponder() - } - - @objc private func cancelPressed() { - self.currentPresetSearchTerm = nil - self.updateQuery(nil) - - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - let textField = self.textField - self.textField = nil - - self.deactivated(textField?.isFirstResponder ?? false) - - if let textField { - textField.resignFirstResponder() - textField.removeFromSuperview() - } - - /*self.tintTextView.view?.isHidden = false - self.textView.view?.isHidden = false*/ - } - - @objc private func clearPressed() { - self.currentPresetSearchTerm = nil - self.updateQuery(nil) - self.textField?.text = "" - - self.clearIconView.isHidden = true - self.clearIconTintView.isHidden = true - self.clearIconButton.isHidden = true - - /*self.tintTextView.view?.isHidden = false - self.textView.view?.isHidden = false*/ - } - - var isActive: Bool { - return self.textField?.isFirstResponder ?? false - } - - func deactivate() { - if let text = self.textField?.text, !text.isEmpty { - self.textField?.endEditing(true) - } else { - self.cancelPressed() - } - } - - public func textFieldDidBeginEditing(_ textField: UITextField) { - } - - public func textFieldDidEndEditing(_ textField: UITextField) { - } - - public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - textField.endEditing(true) - return false - } - - @objc private func textFieldChanged(_ textField: UITextField) { - self.update(transition: .immediate) - - let text = textField.text ?? "" - - var inputLanguage = textField.textInputMode?.primaryLanguage ?? "en" - if let range = inputLanguage.range(of: "-") { - inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) - } - if let range = inputLanguage.range(of: "_") { - inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) - } - - self.clearIconView.isHidden = text.isEmpty - self.clearIconTintView.isHidden = text.isEmpty - self.clearIconButton.isHidden = text.isEmpty - - self.currentPresetSearchTerm = nil - self.updateQuery(.text(value: text, language: inputLanguage)) - } - - private func update(transition: Transition) { - guard let params = self.params else { - return - } - self.params = nil - self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition) - } - - public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) { - let textInputState: EmojiSearchSearchBarComponent.TextInputState - if let textField = self.textField { - textInputState = .active(hasText: !(textField.text ?? "").isEmpty) - } else { - textInputState = .inactive - } - - let params = Params( - context: context, - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - text: text, - useOpaqueTheme: useOpaqueTheme, - isActive: isActive, - hasPresetSearch: self.currentPresetSearchTerm == nil, - textInputState: textInputState, - searchState: searchState, - size: size, - canFocus: canFocus, - searchCategories: searchCategories - ) - - if self.params == params { - return - } - - let isActiveWithText = isActive && self.currentPresetSearchTerm == nil - - if self.params?.theme !== theme { - /*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white) - - self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/ - - self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) - self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - - self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white) - } - - self.params = params - - let sideInset: CGFloat = 12.0 - let topInset: CGFloat = 8.0 - let inputHeight: CGFloat = 36.0 - - let sideTextInset: CGFloat = sideInset + 4.0 + 24.0 - - if theme.overallDarkAppearance && forceNeedsVibrancy { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor - self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor - } else if useOpaqueTheme { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor - } else { - self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor - self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor - } - - self.backgroundLayer.cornerRadius = inputHeight * 0.5 - self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5 - - let cancelColor: UIColor - if theme.overallDarkAppearance && forceNeedsVibrancy { - cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3) - } else { - cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - } - - let cancelTextSize = self.cancelButtonTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: cancelColor - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - let _ = self.cancelButtonTintTitle.update( - transition: .immediate, - component: AnyComponent(Text( - text: strings.Common_Cancel, - font: Font.regular(17.0), - color: .white - )), - environment: {}, - containerSize: CGSize(width: size.width - 32.0, height: 100.0) - ) - - let cancelButtonSpacing: CGFloat = 8.0 - - var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) - if isActiveWithText { - backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing - } - transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) - transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame) - - transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) - - let textX: CGFloat = backgroundFrame.minX + sideTextInset - let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) - self.textFrame = textFrame - - let statusContent: EmojiSearchStatusComponent.Content - switch searchState { - case .empty: - statusContent = .search - case .searching: - statusContent = .progress - case .active: - statusContent = .results - } - - let statusSize = CGSize(width: 24.0, height: 24.0) - let _ = self.statusIcon.update( - transition: transition, - component: AnyComponent(EmojiSearchStatusComponent( - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - useOpaqueTheme: useOpaqueTheme, - content: statusContent - )), - environment: {}, - containerSize: statusSize - ) - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize) - if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View { - if statusIconView.superview == nil { - self.addSubview(statusIconView) - self.tintContainerView.addSubview(statusIconView.tintContainerView) - } - - transition.setFrame(view: statusIconView, frame: iconFrame) - transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame) - } - - /*if let image = self.searchIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setBounds(view: self.searchIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.searchIconView, position: iconFrame.center) - transition.setBounds(view: self.searchIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.searchIconTintView, position: iconFrame.center) - transition.setScale(view: self.searchIconView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001) - transition.setAlpha(view: self.searchIconView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0) - transition.setScale(view: self.searchIconTintView, scale: self.currentPresetSearchTerm == nil ? 1.0 : 0.001) - transition.setAlpha(view: self.searchIconTintView, alpha: self.currentPresetSearchTerm == nil ? 1.0 : 0.0) - } - - if let image = self.backIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setBounds(view: self.backIconView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.backIconView, position: iconFrame.center) - transition.setBounds(view: self.backIconTintView, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - transition.setPosition(view: self.backIconTintView, position: iconFrame.center) - transition.setScale(view: self.backIconView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001) - transition.setAlpha(view: self.backIconView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0) - transition.setScale(view: self.backIconTintView, scale: self.currentPresetSearchTerm != nil ? 1.0 : 0.001) - transition.setAlpha(view: self.backIconTintView, alpha: self.currentPresetSearchTerm != nil ? 1.0 : 0.0) - }*/ - - let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height)) - let _ = self.placeholderContent.update( - transition: transition, - component: AnyComponent(EmojiSearchSearchBarComponent( - context: context, - theme: theme, - forceNeedsVibrancy: forceNeedsVibrancy, - strings: strings, - useOpaqueTheme: useOpaqueTheme, - textInputState: textInputState, - categories: searchCategories, - searchTermUpdated: { [weak self] term in - guard let self else { - return - } - var shouldChangeActivation = false - if (self.currentPresetSearchTerm == nil) != (term == nil) { - shouldChangeActivation = true - } - self.currentPresetSearchTerm = term - - if shouldChangeActivation { - if let term { - self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) - - self.updateQuery(.category(value: term)) - self.activated(false) - } else { - self.deactivated(self.textField?.isFirstResponder ?? false) - self.updateQuery(nil) - } - } else { - if let term { - self.updateQuery(.category(value: term)) - } else { - self.updateQuery(nil) - } - } - }, - activateTextInput: { [weak self] in - guard let self else { - return - } - self.activateTextInput() - } - )), - environment: {}, - containerSize: placeholderContentFrame.size - ) - if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { - if placeholderContentView.superview == nil { - self.addSubview(placeholderContentView) - self.tintContainerView.addSubview(placeholderContentView.tintContainerView) - } - transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) - transition.setFrame(view: placeholderContentView.tintContainerView, frame: placeholderContentFrame) - } - - /*if let searchCategories { - let suggestedItemsView: ComponentView - var suggestedItemsTransition = transition - if let current = self.suggestedItemsView { - suggestedItemsView = current - } else { - suggestedItemsTransition = .immediate - suggestedItemsView = ComponentView() - self.suggestedItemsView = suggestedItemsView - } - - let itemsX: CGFloat = textFrame.maxX + 8.0 - let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height)) - - if let suggestedItemsComponentView = suggestedItemsView.view { - if suggestedItemsComponentView.superview == nil { - self.addSubview(suggestedItemsComponentView) - } - suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame) - suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0) - } - } else { - if let suggestedItemsView = self.suggestedItemsView { - self.suggestedItemsView = nil - if let suggestedItemsComponentView = suggestedItemsView.view { - transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in - suggestedItemsComponentView?.removeFromSuperview() - }) - } - } - }*/ - - if let image = self.clearIconView.image { - let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) - transition.setFrame(view: self.clearIconView, frame: iconFrame) - transition.setFrame(view: self.clearIconTintView, frame: iconFrame) - transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) - } - - if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { - if cancelButtonTitleComponentView.superview == nil { - self.addSubview(cancelButtonTitleComponentView) - cancelButtonTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view { - if cancelButtonTintTitleComponentView.superview == nil { - self.tintContainerView.addSubview(cancelButtonTintTitleComponentView) - cancelButtonTintTitleComponentView.isUserInteractionEnabled = false - } - transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) - transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) - } - - var hasText = false - if let textField = self.textField { - textField.textColor = theme.contextMenu.primaryColor - transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) - - if let text = textField.text, !text.isEmpty { - hasText = true - } - } - let _ = hasText - - /*self.tintTextView.view?.isHidden = hasText - self.textView.view?.isHidden = hasText*/ - } -} - -private final class EmptySearchResultsView: UIView { - override public static var layerClass: AnyClass { - return PassthroughLayer.self - } - - let tintContainerView: UIView - let titleLabel: ComponentView - let titleTintLabel: ComponentView - let icon: ComponentView - - override init(frame: CGRect) { - self.tintContainerView = UIView() - - self.titleLabel = ComponentView() - self.titleTintLabel = ComponentView() - self.icon = ComponentView() - - super.init(frame: frame) - - (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) { - let titleColor: UIColor - if useOpaqueTheme { - titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor - } else { - titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor - } - - let iconSize: CGSize - if let file = file { - iconSize = self.icon.update( - transition: .immediate, - component: AnyComponent(EmojiStatusComponent( - context: context, - animationCache: context.animationCache, - animationRenderer: context.animationRenderer, - content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever), - isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji, - action: nil - )), - environment: {}, - containerSize: CGSize(width: 32.0, height: 32.0) - ) - } else { - iconSize = CGSize() - } - - let titleSize = self.titleLabel.update( - transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)), - environment: {}, - containerSize: CGSize(width: size.width, height: 100.0) - ) - let _ = self.titleTintLabel.update( - transition: .immediate, - component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)), - environment: {}, - containerSize: CGSize(width: size.width, height: 100.0) - ) - - let spacing: CGFloat = 4.0 - let contentHeight = iconSize.height + spacing + titleSize.height - let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0 - let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize) - - if let iconView = self.icon.view { - if iconView.superview == nil { - self.addSubview(iconView) - } - transition.setFrame(view: iconView, frame: iconFrame) - } - if let titleLabelView = self.titleLabel.view { - if titleLabelView.superview == nil { - self.addSubview(titleLabelView) - } - transition.setFrame(view: titleLabelView, frame: titleFrame) - } - if let titleTintLabelView = self.titleTintLabel.view { - if titleTintLabelView.superview == nil { - self.tintContainerView.addSubview(titleTintLabelView) - } - transition.setFrame(view: titleTintLabelView, frame: titleFrame) - } - } -} - public protocol EmojiContentPeekBehavior: AnyObject { func setGestureRecognizerEnabled(view: UIView, isEnabled: Bool, itemAtPoint: @escaping (CGPoint) -> (AnyHashable, CALayer, TelegramMediaFile)?) } @@ -3499,471 +1190,6 @@ public final class EmojiPagerContentComponent: Component { } } - final class CloneItemLayer: SimpleLayer { - } - - public final class ItemLayer: MultiAnimationRenderTarget { - public struct Key: Hashable { - var groupId: AnyHashable - var itemId: ItemContent.Id - - public init( - groupId: AnyHashable, - itemId: ItemContent.Id - ) { - self.groupId = groupId - self.itemId = itemId - } - } - - enum Badge: Equatable { - case premium - case locked - case featured - case text(String) - case customFile(TelegramMediaFile) - } - - public let item: Item - private let context: AccountContext - - private var content: ItemContent - private var theme: PresentationTheme? - - private let placeholderColor: UIColor - let pixelSize: CGSize - let pointSize: CGSize - private let size: CGSize - private var disposable: Disposable? - private var fetchDisposable: Disposable? - private var premiumBadgeView: PremiumBadgeView? - - private var iconLayer: SimpleLayer? - private var tintIconLayer: SimpleLayer? - - private(set) var tintContentLayer: SimpleLayer? - - private var badge: Badge? - private var validSize: CGSize? - - private var isInHierarchyValue: Bool = false - public var isVisibleForAnimations: Bool = false { - didSet { - if self.isVisibleForAnimations != oldValue { - self.updatePlayback() - } - } - } - public private(set) var displayPlaceholder: Bool = false - public let onUpdateDisplayPlaceholder: (Bool, Double) -> Void - - weak var cloneLayer: CloneItemLayer? { - didSet { - if let cloneLayer = self.cloneLayer { - cloneLayer.contents = self.contents - } - } - } - - override public var contents: Any? { - didSet { - self.onContentsUpdate() - if let cloneLayer = self.cloneLayer { - cloneLayer.contents = self.contents - } - } - } - - override public var position: CGPoint { - get { - return super.position - } set(value) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.position = value - } - super.position = value - } - } - - override public var bounds: CGRect { - get { - return super.bounds - } set(value) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.bounds = value - } - super.bounds = value - } - } - - override public func add(_ animation: CAAnimation, forKey key: String?) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.add(animation, forKey: key) - } - - super.add(animation, forKey: key) - } - - override public func removeAllAnimations() { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.removeAllAnimations() - } - - super.removeAllAnimations() - } - - override public func removeAnimation(forKey: String) { - if let mirrorLayer = self.tintContentLayer { - mirrorLayer.removeAnimation(forKey: forKey) - } - - super.removeAnimation(forKey: forKey) - } - - public var onContentsUpdate: () -> Void = {} - public var onLoop: () -> Void = {} - - public init( - item: Item, - context: AccountContext, - attemptSynchronousLoad: Bool, - content: ItemContent, - cache: AnimationCache, - renderer: MultiAnimationRenderer, - placeholderColor: UIColor, - blurredBadgeColor: UIColor, - accentIconColor: UIColor, - pointSize: CGSize, - onUpdateDisplayPlaceholder: @escaping (Bool, Double) -> Void - ) { - self.item = item - self.context = context - self.content = content - self.placeholderColor = placeholderColor - self.onUpdateDisplayPlaceholder = onUpdateDisplayPlaceholder - - let scale = min(2.0, UIScreenScale) - let pixelSize = CGSize(width: pointSize.width * scale, height: pointSize.height * scale) - self.pixelSize = pixelSize - self.pointSize = pointSize - self.size = CGSize(width: pixelSize.width / scale, height: pixelSize.height / scale) - - super.init() - - switch content { - case let .animation(animationData): - let loadAnimation: () -> Void = { [weak self] in - guard let strongSelf = self else { - return - } - - strongSelf.disposable = renderer.add(target: strongSelf, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, unique: false, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: pixelSize.width >= 120.0, customColor: animationData.isTemplate ? .white : nil)) - } - - if attemptSynchronousLoad { - if !renderer.loadFirstFrameSynchronously(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize) { - self.updateDisplayPlaceholder(displayPlaceholder: true) - - self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in - if !isFinal { - if !success { - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - return - } - - Queue.mainQueue().async { - loadAnimation() - - if !success { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - }) - } else { - loadAnimation() - } - } else { - self.fetchDisposable = renderer.loadFirstFrame(target: self, cache: cache, itemId: animationData.resource.resource.id.stringRepresentation, size: pixelSize, fetch: animationCacheFetchFile(context: context, userLocation: .other, userContentType: .sticker, resource: animationData.resource, type: animationData.type.animationCacheAnimationType, keyframeOnly: true, customColor: animationData.isTemplate ? .white : nil), completion: { [weak self] success, isFinal in - if !isFinal { - if !success { - Queue.mainQueue().async { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - return - } - - Queue.mainQueue().async { - loadAnimation() - - if !success { - guard let strongSelf = self else { - return - } - - strongSelf.updateDisplayPlaceholder(displayPlaceholder: true) - } - } - }) - } - case let .staticEmoji(staticEmoji): - let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - let preScaleFactor: CGFloat = 1.0 - let scaledSize = CGSize(width: floor(size.width * preScaleFactor), height: floor(size.height * preScaleFactor)) - let scaleFactor = scaledSize.width / size.width - - context.scaleBy(x: 1.0 / scaleFactor, y: 1.0 / scaleFactor) - - let string = NSAttributedString(string: staticEmoji, font: Font.regular(floor(32.0 * scaleFactor)), textColor: .black) - let boundingRect = string.boundingRect(with: scaledSize, options: .usesLineFragmentOrigin, context: nil) - UIGraphicsPushContext(context) - string.draw(at: CGPoint(x: floorToScreenPixels((scaledSize.width - boundingRect.width) / 2.0 + boundingRect.minX), y: floorToScreenPixels((scaledSize.height - boundingRect.height) / 2.0 + boundingRect.minY))) - UIGraphicsPopContext() - }) - self.contents = image?.cgImage - case let .icon(icon): - let image = generateImage(pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - switch icon { - case .premiumStar: - if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon"), color: accentIconColor) { - let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case let .topic(title, color): - let colors = topicIconColors(for: color) - if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { - let imageSize = image.size//.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case .stop: - if let image = generateTintedImage(image: UIImage(bundleImageName: "Premium/NoIcon"), color: .white) { - let imageSize = image.size.aspectFitted(CGSize(width: size.width - 6.0, height: size.height - 6.0)) - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - case .add: - break - } - - UIGraphicsPopContext() - })?.withRenderingMode(icon == .stop ? .alwaysTemplate : .alwaysOriginal) - self.contents = image?.cgImage - } - - if case .icon(.add) = content { - let tintContentLayer = SimpleLayer() - self.tintContentLayer = tintContentLayer - - let iconLayer = SimpleLayer() - self.iconLayer = iconLayer - self.addSublayer(iconLayer) - - let tintIconLayer = SimpleLayer() - self.tintIconLayer = tintIconLayer - tintContentLayer.addSublayer(tintIconLayer) - } - } - - override public init(layer: Any) { - guard let layer = layer as? ItemLayer else { - preconditionFailure() - } - - self.context = layer.context - self.item = layer.item - - self.content = layer.content - self.placeholderColor = layer.placeholderColor - self.size = layer.size - self.pixelSize = layer.pixelSize - self.pointSize = layer.pointSize - - self.onUpdateDisplayPlaceholder = { _, _ in } - - super.init(layer: layer) - } - - required public init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - deinit { - self.disposable?.dispose() - self.fetchDisposable?.dispose() - } - - public override func action(forKey event: String) -> CAAction? { - if event == kCAOnOrderIn { - self.isInHierarchyValue = true - } else if event == kCAOnOrderOut { - self.isInHierarchyValue = false - } - self.updatePlayback() - return nullAction - } - - func update( - content: ItemContent, - theme: PresentationTheme - ) { - var themeUpdated = false - if self.theme !== theme { - self.theme = theme - themeUpdated = true - } - var contentUpdated = false - if self.content != content { - self.content = content - contentUpdated = true - } - - if themeUpdated || contentUpdated { - if case let .icon(icon) = content, case let .topic(title, color) = icon { - let image = generateImage(self.size, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - let colors = topicIconColors(for: color) - if let image = generateTopicIcon(backgroundColors: colors.0.map { UIColor(rgb: $0) }, strokeColors: colors.1.map { UIColor(rgb: $0) }, title: title) { - let imageSize = image.size - image.draw(in: CGRect(origin: CGPoint(x: floor((size.width - imageSize.width) / 2.0), y: floor((size.height - imageSize.height) / 2.0)), size: imageSize)) - } - - UIGraphicsPopContext() - }) - self.contents = image?.cgImage - } else if case .icon(.add) = content { - guard let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer else { - return - } - func generateIcon(color: UIColor) -> UIImage? { - return generateImage(self.pointSize, opaque: false, scale: min(UIScreenScale, 3.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - - UIGraphicsPushContext(context) - - context.setFillColor(color.withMultipliedAlpha(0.2).cgColor) - context.fillEllipse(in: CGRect(origin: .zero, size: size).insetBy(dx: 8.0, dy: 8.0)) - context.setFillColor(color.cgColor) - - let plusSize = CGSize(width: 4.5, height: 31.5) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.width) / 2.0), y: floorToScreenPixels((size.height - plusSize.height) / 2.0), width: plusSize.width, height: plusSize.height), cornerRadius: plusSize.width / 2.0).cgPath) - context.addPath(UIBezierPath(roundedRect: CGRect(x: floorToScreenPixels((size.width - plusSize.height) / 2.0), y: floorToScreenPixels((size.height - plusSize.width) / 2.0), width: plusSize.height, height: plusSize.width), cornerRadius: plusSize.width / 2.0).cgPath) - context.fillPath() - - UIGraphicsPopContext() - }) - } - - let needsVibrancy = !theme.overallDarkAppearance - let color = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor - - iconLayer.contents = generateIcon(color: color)?.cgImage - tintIconLayer.contents = generateIcon(color: .white)?.cgImage - - tintIconLayer.isHidden = !needsVibrancy - } - } - } - - func update( - transition: Transition, - size: CGSize, - badge: Badge?, - blurredBadgeColor: UIColor, - blurredBadgeBackgroundColor: UIColor - ) { - if self.badge != badge || self.validSize != size { - self.badge = badge - self.validSize = size - - if let iconLayer = self.iconLayer, let tintIconLayer = self.tintIconLayer { - transition.setFrame(layer: iconLayer, frame: CGRect(origin: .zero, size: size)) - transition.setFrame(layer: tintIconLayer, frame: CGRect(origin: .zero, size: size)) - } - - if let badge = badge { - var badgeTransition = transition - let premiumBadgeView: PremiumBadgeView - if let current = self.premiumBadgeView { - premiumBadgeView = current - } else { - badgeTransition = .immediate - premiumBadgeView = PremiumBadgeView(context: self.context) - self.premiumBadgeView = premiumBadgeView - self.addSublayer(premiumBadgeView.layer) - } - - let badgeDiameter = min(16.0, floor(size.height * 0.5)) - let badgeSize = CGSize(width: badgeDiameter, height: badgeDiameter) - badgeTransition.setFrame(view: premiumBadgeView, frame: CGRect(origin: CGPoint(x: size.width - badgeSize.width, y: size.height - badgeSize.height), size: badgeSize)) - premiumBadgeView.update(transition: badgeTransition, badge: badge, backgroundColor: blurredBadgeColor, size: badgeSize) - - self.blurredRepresentationBackgroundColor = blurredBadgeBackgroundColor - self.blurredRepresentationTarget = premiumBadgeView.contentLayer - } else { - if let premiumBadgeView = self.premiumBadgeView { - self.premiumBadgeView = nil - premiumBadgeView.removeFromSuperview() - - self.blurredRepresentationBackgroundColor = nil - self.blurredRepresentationTarget = nil - } - } - } - } - - private func updatePlayback() { - let shouldBePlaying = self.isInHierarchyValue && self.isVisibleForAnimations - - self.shouldBeAnimating = shouldBePlaying - } - - public override func updateDisplayPlaceholder(displayPlaceholder: Bool) { - if self.displayPlaceholder == displayPlaceholder { - return - } - - self.displayPlaceholder = displayPlaceholder - self.onUpdateDisplayPlaceholder(displayPlaceholder, 0.0) - } - - public override func transitionToContents(_ contents: AnyObject, didLoop: Bool) { - self.contents = contents - - if self.displayPlaceholder { - self.displayPlaceholder = false - self.onUpdateDisplayPlaceholder(false, 0.2) - self.animateAlpha(from: 0.0, to: 1.0, duration: 0.18) - } - - if didLoop { - self.onLoop() - } - } - } - private final class GroupBorderLayer: PassthroughShapeLayer { let tintContainerLayer: CAShapeLayer @@ -4104,7 +1330,7 @@ public final class EmojiPagerContentComponent: Component { } private enum VisualItemKey: Hashable { - case item(id: ItemLayer.Key) + case item(id: EmojiKeyboardItemLayer.Key) case header(groupId: AnyHashable) case groupExpandButton(groupId: AnyHashable) case groupActionButton(groupId: AnyHashable) @@ -4134,10 +1360,10 @@ public final class EmojiPagerContentComponent: Component { private var visibleSearchHeader: EmojiSearchHeaderView? private var visibleEmptySearchResultsView: EmptySearchResultsView? private var visibleCustomContentView: EmojiCustomContentView? - private var visibleItemPlaceholderViews: [ItemLayer.Key: ItemPlaceholderView] = [:] + private var visibleItemPlaceholderViews: [EmojiKeyboardItemLayer.Key: ItemPlaceholderView] = [:] private var visibleFillPlaceholdersViews: [Int: ItemPlaceholderView] = [:] - private var visibleItemSelectionLayers: [ItemLayer.Key: ItemSelectionLayer] = [:] - private var visibleItemLayers: [ItemLayer.Key: ItemLayer] = [:] + private var visibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: ItemSelectionLayer] = [:] + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] private var visibleGroupHeaders: [AnyHashable: GroupHeaderLayer] = [:] private var visibleGroupBorders: [AnyHashable: GroupBorderLayer] = [:] private var visibleGroupPremiumButtons: [AnyHashable: ComponentView] = [:] @@ -4163,7 +1389,7 @@ public final class EmojiPagerContentComponent: Component { private var activeItemUpdated: ActionSlot<(AnyHashable, AnyHashable?, Transition)>? private var itemLayout: ItemLayout? - private var contextFocusItemKey: EmojiPagerContentComponent.View.ItemLayer.Key? + private var contextFocusItemKey: EmojiKeyboardItemLayer.Key? private var contextGesture: ContextGesture? private var tapRecognizer: UITapGestureRecognizer? @@ -4503,7 +1729,7 @@ public final class EmojiPagerContentComponent: Component { } public func layerForItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item) -> CALayer? { - let itemKey = EmojiPagerContentComponent.View.ItemLayer.Key(groupId: groupId, itemId: item.content.id) + let itemKey = EmojiKeyboardItemLayer.Key(groupId: groupId, itemId: item.content.id) if let itemLayer = self.visibleItemLayers[itemKey] { return itemLayer } else { @@ -4530,15 +1756,15 @@ public final class EmojiPagerContentComponent: Component { let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0 - var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemLayers { previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleItemSelectionLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemSelectionLayers { previousVisibleItemSelectionLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:] + var previousVisiblePlaceholderViews: [EmojiKeyboardItemLayer.Key: (UIView, CGRect)] = [:] for (id, view) in self.visibleItemPlaceholderViews { previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -5024,15 +2250,15 @@ public final class EmojiPagerContentComponent: Component { let offsetDirectionSign: Double = scrollPosition < self.scrollView.bounds.minY ? -1.0 : 1.0 - var previousVisibleLayers: [ItemLayer.Key: (CALayer, CGRect)] = [:] + var previousVisibleLayers: [EmojiKeyboardItemLayer.Key: (CALayer, CGRect)] = [:] for (id, layer) in self.visibleItemLayers { previousVisibleLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisibleItemSelectionLayers: [ItemLayer.Key: (ItemSelectionLayer, CGRect)] = [:] + var previousVisibleItemSelectionLayers: [EmojiKeyboardItemLayer.Key: (ItemSelectionLayer, CGRect)] = [:] for (id, layer) in self.visibleItemSelectionLayers { previousVisibleItemSelectionLayers[id] = (layer, layer.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } - var previousVisiblePlaceholderViews: [ItemLayer.Key: (UIView, CGRect)] = [:] + var previousVisiblePlaceholderViews: [EmojiKeyboardItemLayer.Key: (UIView, CGRect)] = [:] for (id, view) in self.visibleItemPlaceholderViews { previousVisiblePlaceholderViews[id] = (view, view.frame.offsetBy(dx: 0.0, dy: -self.scrollView.bounds.minY)) } @@ -5503,8 +2729,8 @@ public final class EmojiPagerContentComponent: Component { } private let longPressDuration: Double = 0.5 - private var longPressItem: EmojiPagerContentComponent.View.ItemLayer.Key? - private var currentLongPressLayer: CloneItemLayer? + private var longPressItem: EmojiKeyboardItemLayer.Key? + private var currentLongPressLayer: EmojiKeyboardCloneItemLayer? private var hapticFeedback: HapticFeedback? private var continuousHaptic: AnyObject? private var longPressTimer: SwiftSignalKit.Timer? @@ -5543,7 +2769,7 @@ public final class EmojiPagerContentComponent: Component { self.currentLongPressLayer = nil currentLongPressLayer.removeFromSuperlayer() } - let currentLongPressLayer = CloneItemLayer() + let currentLongPressLayer = EmojiKeyboardCloneItemLayer() currentLongPressLayer.position = self.scrollView.layer.convert(itemLayer.position, to: externalExpansionView.layer) currentLongPressLayer.bounds = itemLayer.convert(itemLayer.bounds, to: externalExpansionView.layer) currentLongPressLayer.transform = itemLayer.transform @@ -5641,10 +2867,10 @@ public final class EmojiPagerContentComponent: Component { } } - private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, ItemLayer.Key)? { + private func item(atPoint point: CGPoint, extendedHitRange: Bool = false) -> (Item, EmojiKeyboardItemLayer.Key)? { let localPoint = self.convert(point, to: self.scrollView) - var closestItem: (key: ItemLayer.Key, distance: CGFloat)? + var closestItem: (key: EmojiKeyboardItemLayer.Key, distance: CGFloat)? for (key, itemLayer) in self.visibleItemLayers { if extendedHitRange { @@ -5806,7 +3032,7 @@ public final class EmojiPagerContentComponent: Component { var topVisibleGroupId: AnyHashable? var topVisibleSubgroupId: AnyHashable? - var validIds = Set() + var validIds = Set() var validGroupHeaderIds = Set() var validGroupBorderIds = Set() var validGroupPremiumButtonIds = Set() @@ -6136,7 +3362,7 @@ public final class EmojiPagerContentComponent: Component { } } - let itemId = ItemLayer.Key( + let itemId = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) @@ -6151,7 +3377,7 @@ public final class EmojiPagerContentComponent: Component { var animateItemIn = false var updateItemLayerPlaceholder = false var itemTransition = transition - let itemLayer: ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.visibleItemLayers[itemId] { itemLayer = current } else { @@ -6167,7 +3393,7 @@ public final class EmojiPagerContentComponent: Component { } let placeholderColor = keyboardChildEnvironment.theme.chat.inputPanel.primaryTextColor.withMultipliedAlpha(0.1) - itemLayer = ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: item, context: component.context, attemptSynchronousLoad: attemptSynchronousLoads, @@ -6264,7 +3490,7 @@ public final class EmojiPagerContentComponent: Component { let itemPosition = CGPoint(x: itemFrame.midX, y: itemFrame.midY) itemTransition.setPosition(layer: itemLayer, position: itemPosition) - var badge: ItemLayer.Badge? + var badge: EmojiKeyboardItemLayer.Badge? if itemGroup.displayPremiumBadges, let file = item.itemFile, file.isPremiumSticker { badge = .premium } else { @@ -6445,7 +3671,7 @@ public final class EmojiPagerContentComponent: Component { } var removedPlaceholerViews = false - var removedIds: [ItemLayer.Key] = [] + var removedIds: [EmojiKeyboardItemLayer.Key] = [] for (id, itemLayer) in self.visibleItemLayers { if !validIds.contains(id) { removedIds.append(id) @@ -6535,7 +3761,7 @@ public final class EmojiPagerContentComponent: Component { removedPlaceholerViews = true } } - var removedItemSelectionLayerIds: [ItemLayer.Key] = [] + var removedItemSelectionLayerIds: [EmojiKeyboardItemLayer.Key] = [] for (id, itemSelectionLayer) in self.visibleItemSelectionLayers { var fileId: MediaId? switch id.itemId { @@ -6924,7 +4150,7 @@ public final class EmojiPagerContentComponent: Component { var hintDisappearingGroupFrame: (groupId: AnyHashable, frame: CGRect)? var previousAbsoluteItemPositions: [VisualItemKey: CGPoint] = [:] - var anchorItems: [ItemLayer.Key: CGRect] = [:] + var anchorItems: [EmojiKeyboardItemLayer.Key: CGRect] = [:] if let previousComponent = previousComponent, let previousItemLayout = self.itemLayout, previousComponent.contentItemGroups != component.contentItemGroups, previousComponent.itemContentUniqueId == component.itemContentUniqueId { if !transition.animation.isImmediate { var previousItemPositionsValue: [VisualItemKey: CGPoint] = [:] @@ -6932,8 +4158,8 @@ public final class EmojiPagerContentComponent: Component { let itemGroup = previousComponent.contentItemGroups[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { let item = itemGroup.items[itemIndex] - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) @@ -7200,7 +4426,7 @@ public final class EmojiPagerContentComponent: Component { var animatedScrollOffset: CGFloat = 0.0 if !anchorItems.isEmpty && !keepOffset { - let sortedAnchorItems: [(ItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in + let sortedAnchorItems: [(EmojiKeyboardItemLayer.Key, CGRect)] = anchorItems.sorted(by: { lhs, rhs in if lhs.value.minY != rhs.value.minY { return lhs.value.minY < rhs.value.minY } else { @@ -7214,8 +4440,8 @@ public final class EmojiPagerContentComponent: Component { continue } for j in 0 ..< component.contentItemGroups[i].items.count { - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: component.contentItemGroups[i].groupId, itemId: component.contentItemGroups[i].items[j].content.id ) @@ -7261,8 +4487,8 @@ public final class EmojiPagerContentComponent: Component { let itemGroupLayout = itemLayout.itemGroupLayouts[groupIndex] for itemIndex in 0 ..< itemGroup.items.count { let item = itemGroup.items[itemIndex] - let itemKey: ItemLayer.Key - itemKey = ItemLayer.Key( + let itemKey: EmojiKeyboardItemLayer.Key + itemKey = EmojiKeyboardItemLayer.Key( groupId: itemGroup.groupId, itemId: item.content.id ) diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift new file mode 100644 index 0000000000..d8369244f7 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchHeaderView.swift @@ -0,0 +1,607 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramCore +import TelegramPresentationData +import AccountContext +import SwiftSignalKit + +public final class EmojiSearchHeaderView: UIView, UITextFieldDelegate { + private final class EmojiSearchTextField: UITextField { + override func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.integral + } + } + + private struct Params: Equatable { + var context: AccountContext + var theme: PresentationTheme + var forceNeedsVibrancy: Bool + var strings: PresentationStrings + var text: String + var useOpaqueTheme: Bool + var isActive: Bool + var hasPresetSearch: Bool + var textInputState: EmojiSearchSearchBarComponent.TextInputState + var searchState: EmojiPagerContentComponent.SearchState + var size: CGSize + var canFocus: Bool + var searchCategories: EmojiSearchCategories? + + static func ==(lhs: Params, rhs: Params) -> Bool { + if lhs.context !== rhs.context { + return false + } + if lhs.theme !== rhs.theme { + return false + } + if lhs.forceNeedsVibrancy != rhs.forceNeedsVibrancy { + return false + } + if lhs.strings !== rhs.strings { + return false + } + if lhs.text != rhs.text { + return false + } + if lhs.useOpaqueTheme != rhs.useOpaqueTheme { + return false + } + if lhs.isActive != rhs.isActive { + return false + } + if lhs.hasPresetSearch != rhs.hasPresetSearch { + return false + } + if lhs.textInputState != rhs.textInputState { + return false + } + if lhs.searchState != rhs.searchState { + return false + } + if lhs.size != rhs.size { + return false + } + if lhs.canFocus != rhs.canFocus { + return false + } + if lhs.searchCategories != rhs.searchCategories { + return false + } + return true + } + } + + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + private let activated: (Bool) -> Void + private let deactivated: (Bool) -> Void + private let updateQuery: (EmojiPagerContentComponent.SearchQuery?) -> Void + + let tintContainerView: UIView + + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + + private let statusIcon = ComponentView() + + private let clearIconView: UIImageView + private let clearIconTintView: UIImageView + private let clearIconButton: HighlightTrackingButton + + private let cancelButtonTintTitle: ComponentView + private let cancelButtonTitle: ComponentView + private let cancelButton: HighlightTrackingButton + + private var placeholderContent = ComponentView() + + private var textFrame: CGRect? + private var textField: EmojiSearchTextField? + + private var tapRecognizer: UITapGestureRecognizer? + private(set) var currentPresetSearchTerm: EmojiSearchCategories.Group? + + private var params: Params? + + public var wantsDisplayBelowKeyboard: Bool { + return self.textField != nil + } + + init(activated: @escaping (Bool) -> Void, deactivated: @escaping (Bool) -> Void, updateQuery: @escaping (EmojiPagerContentComponent.SearchQuery?) -> Void) { + self.activated = activated + self.deactivated = deactivated + self.updateQuery = updateQuery + + self.tintContainerView = UIView() + + self.backgroundLayer = SimpleLayer() + self.tintBackgroundLayer = SimpleLayer() + + self.clearIconView = UIImageView() + self.clearIconTintView = UIImageView() + self.clearIconButton = HighlightableButton() + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + self.cancelButtonTintTitle = ComponentView() + self.cancelButtonTitle = ComponentView() + self.cancelButton = HighlightTrackingButton() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.backgroundLayer) + self.tintContainerView.layer.addSublayer(self.tintBackgroundLayer) + + self.addSubview(self.clearIconView) + self.tintContainerView.addSubview(self.clearIconTintView) + self.addSubview(self.clearIconButton) + + self.addSubview(self.cancelButton) + self.clipsToBounds = true + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer + + let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:))) + self.tapRecognizer = tapRecognizer + self.addGestureRecognizer(tapRecognizer) + + self.cancelButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { + cancelButtonTitleView.layer.removeAnimation(forKey: "opacity") + cancelButtonTitleView.alpha = 0.4 + } + if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { + cancelButtonTintTitleView.layer.removeAnimation(forKey: "opacity") + cancelButtonTintTitleView.alpha = 0.4 + } + } else { + if let cancelButtonTitleView = strongSelf.cancelButtonTitle.view { + cancelButtonTitleView.alpha = 1.0 + cancelButtonTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + if let cancelButtonTintTitleView = strongSelf.cancelButtonTintTitle.view { + cancelButtonTintTitleView.alpha = 1.0 + cancelButtonTintTitleView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + self.cancelButton.addTarget(self, action: #selector(self.cancelPressed), for: .touchUpInside) + + self.clearIconButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.clearIconView.layer.removeAnimation(forKey: "opacity") + strongSelf.clearIconView.alpha = 0.4 + strongSelf.clearIconTintView.layer.removeAnimation(forKey: "opacity") + strongSelf.clearIconTintView.alpha = 0.4 + } else { + strongSelf.clearIconView.alpha = 1.0 + strongSelf.clearIconView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.clearIconTintView.alpha = 1.0 + strongSelf.clearIconTintView.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.clearIconButton.addTarget(self, action: #selector(self.clearPressed), for: .touchUpInside) + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let location = recognizer.location(in: self) + if let view = self.statusIcon.view, view.frame.contains(location), self.currentPresetSearchTerm != nil { + self.clearCategorySearch() + } else { + self.activateTextInput() + } + } + } + + func clearCategorySearch() { + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent : true) + } + } + + private func activateTextInput() { + guard let params = self.params else { + return + } + if self.textField == nil, let textFrame = self.textFrame, params.canFocus == true { + let backgroundFrame = self.backgroundLayer.frame + let textFieldFrame = CGRect(origin: CGPoint(x: textFrame.minX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textFrame.minX, height: backgroundFrame.height)) + + let textField = EmojiSearchTextField(frame: textFieldFrame) + textField.keyboardAppearance = params.theme.rootController.keyboardColor.keyboardAppearance + textField.autocorrectionType = .no + textField.returnKeyType = .search + self.textField = textField + self.insertSubview(textField, belowSubview: self.clearIconView) + textField.delegate = self + textField.addTarget(self, action: #selector(self.textFieldChanged(_:)), for: .editingChanged) + } + + if params.canFocus { + self.currentPresetSearchTerm = nil + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + placeholderContentView.clearSelection(dispatchEvent: false) + } + } + + self.activated(true) + + self.textField?.becomeFirstResponder() + } + + @objc private func cancelPressed() { + self.currentPresetSearchTerm = nil + self.updateQuery(nil) + + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + let textField = self.textField + self.textField = nil + + self.deactivated(textField?.isFirstResponder ?? false) + + if let textField { + textField.resignFirstResponder() + textField.removeFromSuperview() + } + + /*self.tintTextView.view?.isHidden = false + self.textView.view?.isHidden = false*/ + } + + @objc private func clearPressed() { + self.currentPresetSearchTerm = nil + self.updateQuery(nil) + self.textField?.text = "" + + self.clearIconView.isHidden = true + self.clearIconTintView.isHidden = true + self.clearIconButton.isHidden = true + + /*self.tintTextView.view?.isHidden = false + self.textView.view?.isHidden = false*/ + } + + var isActive: Bool { + return self.textField?.isFirstResponder ?? false + } + + func deactivate() { + if let text = self.textField?.text, !text.isEmpty { + self.textField?.endEditing(true) + } else { + self.cancelPressed() + } + } + + public func textFieldDidBeginEditing(_ textField: UITextField) { + } + + public func textFieldDidEndEditing(_ textField: UITextField) { + } + + public func textFieldShouldReturn(_ textField: UITextField) -> Bool { + textField.endEditing(true) + return false + } + + @objc private func textFieldChanged(_ textField: UITextField) { + self.update(transition: .immediate) + + let text = textField.text ?? "" + + var inputLanguage = textField.textInputMode?.primaryLanguage ?? "en" + if let range = inputLanguage.range(of: "-") { + inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) + } + if let range = inputLanguage.range(of: "_") { + inputLanguage = String(inputLanguage[inputLanguage.startIndex ..< range.lowerBound]) + } + + self.clearIconView.isHidden = text.isEmpty + self.clearIconTintView.isHidden = text.isEmpty + self.clearIconButton.isHidden = text.isEmpty + + self.currentPresetSearchTerm = nil + self.updateQuery(.text(value: text, language: inputLanguage)) + } + + private func update(transition: Transition) { + guard let params = self.params else { + return + } + self.params = nil + self.update(context: params.context, theme: params.theme, forceNeedsVibrancy: params.forceNeedsVibrancy, strings: params.strings, text: params.text, useOpaqueTheme: params.useOpaqueTheme, isActive: params.isActive, size: params.size, canFocus: params.canFocus, searchCategories: params.searchCategories, searchState: params.searchState, transition: transition) + } + + public func update(context: AccountContext, theme: PresentationTheme, forceNeedsVibrancy: Bool, strings: PresentationStrings, text: String, useOpaqueTheme: Bool, isActive: Bool, size: CGSize, canFocus: Bool, searchCategories: EmojiSearchCategories?, searchState: EmojiPagerContentComponent.SearchState, transition: Transition) { + let textInputState: EmojiSearchSearchBarComponent.TextInputState + if let textField = self.textField { + textInputState = .active(hasText: !(textField.text ?? "").isEmpty) + } else { + textInputState = .inactive + } + + let params = Params( + context: context, + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + text: text, + useOpaqueTheme: useOpaqueTheme, + isActive: isActive, + hasPresetSearch: self.currentPresetSearchTerm == nil, + textInputState: textInputState, + searchState: searchState, + size: size, + canFocus: canFocus, + searchCategories: searchCategories + ) + + if self.params == params { + return + } + + let isActiveWithText = isActive && self.currentPresetSearchTerm == nil + + if self.params?.theme !== theme { + /*self.searchIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.searchIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.searchIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Loupe"), color: .white) + + self.backIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.backIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.backIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: .white)*/ + + self.clearIconView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white)?.withRenderingMode(.alwaysTemplate) + self.clearIconView.tintColor = useOpaqueTheme ? theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + + self.clearIconTintView.image = generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: .white) + } + + self.params = params + + let sideInset: CGFloat = 12.0 + let topInset: CGFloat = 8.0 + let inputHeight: CGFloat = 36.0 + + let sideTextInset: CGFloat = sideInset + 4.0 + 24.0 + + if theme.overallDarkAppearance && forceNeedsVibrancy { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.withMultipliedAlpha(0.3).cgColor + self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + } else if useOpaqueTheme { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueSelectionColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor.white.cgColor + } else { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantSelectionColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor(white: 1.0, alpha: 0.2).cgColor + } + + self.backgroundLayer.cornerRadius = inputHeight * 0.5 + self.tintBackgroundLayer.cornerRadius = inputHeight * 0.5 + + let cancelColor: UIColor + if theme.overallDarkAppearance && forceNeedsVibrancy { + cancelColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor.withMultipliedAlpha(0.3) + } else { + cancelColor = useOpaqueTheme ? theme.list.itemAccentColor : theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + } + + let cancelTextSize = self.cancelButtonTitle.update( + transition: .immediate, + component: AnyComponent(Text( + text: strings.Common_Cancel, + font: Font.regular(17.0), + color: cancelColor + )), + environment: {}, + containerSize: CGSize(width: size.width - 32.0, height: 100.0) + ) + let _ = self.cancelButtonTintTitle.update( + transition: .immediate, + component: AnyComponent(Text( + text: strings.Common_Cancel, + font: Font.regular(17.0), + color: .white + )), + environment: {}, + containerSize: CGSize(width: size.width - 32.0, height: 100.0) + ) + + let cancelButtonSpacing: CGFloat = 8.0 + + var backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: topInset), size: CGSize(width: size.width - sideInset * 2.0, height: inputHeight)) + if isActiveWithText { + backgroundFrame.size.width -= cancelTextSize.width + cancelButtonSpacing + } + transition.setFrame(layer: self.backgroundLayer, frame: backgroundFrame) + transition.setFrame(layer: self.tintBackgroundLayer, frame: backgroundFrame) + + transition.setFrame(view: self.cancelButton, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX, y: 0.0), size: CGSize(width: cancelButtonSpacing + cancelTextSize.width, height: size.height))) + + let textX: CGFloat = backgroundFrame.minX + sideTextInset + let textFrame = CGRect(origin: CGPoint(x: textX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - textX, height: backgroundFrame.height)) + self.textFrame = textFrame + + let statusContent: EmojiSearchStatusComponent.Content + switch searchState { + case .empty: + statusContent = .search + case .searching: + statusContent = .progress + case .active: + statusContent = .results + } + + let statusSize = CGSize(width: 24.0, height: 24.0) + let _ = self.statusIcon.update( + transition: transition, + component: AnyComponent(EmojiSearchStatusComponent( + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + useOpaqueTheme: useOpaqueTheme, + content: statusContent + )), + environment: {}, + containerSize: statusSize + ) + let iconFrame = CGRect(origin: CGPoint(x: textFrame.minX - statusSize.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - statusSize.height) / 2.0)), size: statusSize) + if let statusIconView = self.statusIcon.view as? EmojiSearchStatusComponent.View { + if statusIconView.superview == nil { + self.addSubview(statusIconView) + self.tintContainerView.addSubview(statusIconView.tintContainerView) + } + + transition.setFrame(view: statusIconView, frame: iconFrame) + transition.setFrame(view: statusIconView.tintContainerView, frame: iconFrame) + } + + let placeholderContentFrame = CGRect(origin: CGPoint(x: textFrame.minX - 6.0, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - (textFrame.minX - 6.0), height: backgroundFrame.height)) + let _ = self.placeholderContent.update( + transition: transition, + component: AnyComponent(EmojiSearchSearchBarComponent( + context: context, + theme: theme, + forceNeedsVibrancy: forceNeedsVibrancy, + strings: strings, + useOpaqueTheme: useOpaqueTheme, + textInputState: textInputState, + categories: searchCategories, + searchTermUpdated: { [weak self] term in + guard let self else { + return + } + var shouldChangeActivation = false + if (self.currentPresetSearchTerm == nil) != (term == nil) { + shouldChangeActivation = true + } + self.currentPresetSearchTerm = term + + if shouldChangeActivation { + if let term { + self.update(transition: Transition(animation: .curve(duration: 0.4, curve: .spring))) + + self.updateQuery(.category(value: term)) + self.activated(false) + } else { + self.deactivated(self.textField?.isFirstResponder ?? false) + self.updateQuery(nil) + } + } else { + if let term { + self.updateQuery(.category(value: term)) + } else { + self.updateQuery(nil) + } + } + }, + activateTextInput: { [weak self] in + guard let self else { + return + } + self.activateTextInput() + } + )), + environment: {}, + containerSize: placeholderContentFrame.size + ) + if let placeholderContentView = self.placeholderContent.view as? EmojiSearchSearchBarComponent.View { + if placeholderContentView.superview == nil { + self.addSubview(placeholderContentView) + self.tintContainerView.addSubview(placeholderContentView.tintContainerView) + } + transition.setFrame(view: placeholderContentView, frame: placeholderContentFrame) + transition.setFrame(view: placeholderContentView.tintContainerView, frame: placeholderContentFrame) + } + + /*if let searchCategories { + let suggestedItemsView: ComponentView + var suggestedItemsTransition = transition + if let current = self.suggestedItemsView { + suggestedItemsView = current + } else { + suggestedItemsTransition = .immediate + suggestedItemsView = ComponentView() + self.suggestedItemsView = suggestedItemsView + } + + let itemsX: CGFloat = textFrame.maxX + 8.0 + let suggestedItemsFrame = CGRect(origin: CGPoint(x: itemsX, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.maxX - itemsX, height: backgroundFrame.height)) + + if let suggestedItemsComponentView = suggestedItemsView.view { + if suggestedItemsComponentView.superview == nil { + self.addSubview(suggestedItemsComponentView) + } + suggestedItemsTransition.setFrame(view: suggestedItemsComponentView, frame: suggestedItemsFrame) + suggestedItemsTransition.setAlpha(view: suggestedItemsComponentView, alpha: isActiveWithText ? 0.0 : 1.0) + } + } else { + if let suggestedItemsView = self.suggestedItemsView { + self.suggestedItemsView = nil + if let suggestedItemsComponentView = suggestedItemsView.view { + transition.setAlpha(view: suggestedItemsComponentView, alpha: 0.0, completion: { [weak suggestedItemsComponentView] _ in + suggestedItemsComponentView?.removeFromSuperview() + }) + } + } + }*/ + + if let image = self.clearIconView.image { + let iconFrame = CGRect(origin: CGPoint(x: backgroundFrame.maxX - image.size.width - 4.0, y: backgroundFrame.minY + floor((backgroundFrame.height - image.size.height) / 2.0)), size: image.size) + transition.setFrame(view: self.clearIconView, frame: iconFrame) + transition.setFrame(view: self.clearIconTintView, frame: iconFrame) + transition.setFrame(view: self.clearIconButton, frame: iconFrame.insetBy(dx: -8.0, dy: -10.0)) + } + + if let cancelButtonTitleComponentView = self.cancelButtonTitle.view { + if cancelButtonTitleComponentView.superview == nil { + self.addSubview(cancelButtonTitleComponentView) + cancelButtonTitleComponentView.isUserInteractionEnabled = false + } + transition.setFrame(view: cancelButtonTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + if let cancelButtonTintTitleComponentView = self.cancelButtonTintTitle.view { + if cancelButtonTintTitleComponentView.superview == nil { + self.tintContainerView.addSubview(cancelButtonTintTitleComponentView) + cancelButtonTintTitleComponentView.isUserInteractionEnabled = false + } + transition.setFrame(view: cancelButtonTintTitleComponentView, frame: CGRect(origin: CGPoint(x: backgroundFrame.maxX + cancelButtonSpacing, y: floor((size.height - cancelTextSize.height) / 2.0)), size: cancelTextSize)) + transition.setAlpha(view: cancelButtonTintTitleComponentView, alpha: isActiveWithText ? 1.0 : 0.0) + } + + var hasText = false + if let textField = self.textField { + textField.textColor = theme.contextMenu.primaryColor + transition.setFrame(view: textField, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + sideTextInset, y: backgroundFrame.minY), size: CGSize(width: backgroundFrame.width - sideTextInset - 32.0, height: backgroundFrame.height))) + + if let text = textField.text, !text.isEmpty { + hasText = true + } + } + let _ = hasText + + /*self.tintTextView.view?.isHidden = hasText + self.textView.view?.isHidden = hasText*/ + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift index 93c8f1e164..b8e7be9d63 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiSearchSearchBarComponent.swift @@ -420,6 +420,15 @@ final class EmojiSearchSearchBarComponent: Component { } } + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let component = self.component else { + return nil + } + let _ = component + + return super.hitTest(point, with: event) + } + private func updateScrolling(transition: Transition, fromScrolling: Bool) { guard let component = self.component, let itemLayout = self.itemLayout else { return @@ -427,8 +436,12 @@ final class EmojiSearchSearchBarComponent: Component { let itemAlpha: CGFloat switch component.textInputState { - case .active: - itemAlpha = 0.0 + case let .active(hasText): + if hasText { + itemAlpha = 0.0 + } else { + itemAlpha = 1.0 + } case .inactive: itemAlpha = 1.0 } @@ -674,7 +687,7 @@ final class EmojiSearchSearchBarComponent: Component { if self.scrollView.bounds.size != availableSize { transition.setFrame(view: self.scrollView, frame: CGRect(origin: CGPoint(), size: availableSize)) } - if case .active = component.textInputState { + if case .active(true) = component.textInputState { transition.setBoundsOrigin(view: self.scrollView, origin: CGPoint()) } if self.scrollView.contentSize != itemLayout.contentSize { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift new file mode 100644 index 0000000000..466c430e8d --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmptySearchResultsView.swift @@ -0,0 +1,101 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import TelegramPresentationData +import EmojiStatusComponent + +final class EmptySearchResultsView: UIView { + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerView: UIView + let titleLabel: ComponentView + let titleTintLabel: ComponentView + let icon: ComponentView + + override init(frame: CGRect) { + self.tintContainerView = UIView() + + self.titleLabel = ComponentView() + self.titleTintLabel = ComponentView() + self.icon = ComponentView() + + super.init(frame: frame) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerView.layer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(context: AccountContext, theme: PresentationTheme, useOpaqueTheme: Bool, text: String, file: TelegramMediaFile?, size: CGSize, searchInitiallyHidden: Bool, transition: Transition) { + let titleColor: UIColor + if useOpaqueTheme { + titleColor = theme.chat.inputMediaPanel.panelContentOpaqueSearchOverlayColor + } else { + titleColor = theme.chat.inputMediaPanel.panelContentVibrantSearchOverlayColor + } + + let iconSize: CGSize + if let file = file { + iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(EmojiStatusComponent( + context: context, + animationCache: context.animationCache, + animationRenderer: context.animationRenderer, + content: .animation(content: .file(file: file), size: CGSize(width: 32.0, height: 32.0), placeholderColor: titleColor, themeColor: nil, loopMode: .forever), + isVisibleForAnimations: context.sharedContext.energyUsageSettings.loopEmoji, + action: nil + )), + environment: {}, + containerSize: CGSize(width: 32.0, height: 32.0) + ) + } else { + iconSize = CGSize() + } + + let titleSize = self.titleLabel.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: titleColor)), + environment: {}, + containerSize: CGSize(width: size.width, height: 100.0) + ) + let _ = self.titleTintLabel.update( + transition: .immediate, + component: AnyComponent(Text(text: text, font: Font.regular(15.0), color: .white)), + environment: {}, + containerSize: CGSize(width: size.width, height: 100.0) + ) + + let spacing: CGFloat = 4.0 + let contentHeight = iconSize.height + spacing + titleSize.height + let contentOriginY = searchInitiallyHidden ? floor((size.height - contentHeight) / 2.0) : 10.0 + let iconFrame = CGRect(origin: CGPoint(x: floor((size.width - iconSize.width) / 2.0), y: contentOriginY), size: iconSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: iconFrame.maxY + spacing), size: titleSize) + + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + transition.setFrame(view: iconView, frame: iconFrame) + } + if let titleLabelView = self.titleLabel.view { + if titleLabelView.superview == nil { + self.addSubview(titleLabelView) + } + transition.setFrame(view: titleLabelView, frame: titleFrame) + } + if let titleTintLabelView = self.titleTintLabel.view { + if titleTintLabelView.superview == nil { + self.tintContainerView.addSubview(titleTintLabelView) + } + transition.setFrame(view: titleTintLabelView, frame: titleFrame) + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift index eea256000d..236588fff2 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EntityKeyboardTopPanelComponent.swift @@ -85,7 +85,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { } final class View: UIView { - var itemLayer: EmojiPagerContentComponent.View.ItemLayer? + var itemLayer: EmojiKeyboardItemLayer? var placeholderView: EmojiPagerContentComponent.View.ItemPlaceholderView? var component: EntityKeyboardAnimationTopPanelComponent? var titleView: ComponentView? @@ -116,7 +116,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { if self.itemLayer == nil { let tintColor: EmojiPagerContentComponent.Item.TintMode = component.customTintColor.flatMap { .custom($0) } ?? .primary - let itemLayer = EmojiPagerContentComponent.View.ItemLayer( + let itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: component.item, content: .animation(component.item), @@ -157,7 +157,7 @@ final class EntityKeyboardAnimationTopPanelComponent: Component { transition.setPosition(layer: itemLayer, position: CGPoint(x: iconFrame.midX, y: iconFrame.midY)) transition.setBounds(layer: itemLayer, bounds: CGRect(origin: CGPoint(), size: iconFrame.size)) - var badge: EmojiPagerContentComponent.View.ItemLayer.Badge? + var badge: EmojiKeyboardItemLayer.Badge? if component.isPremiumLocked { badge = .locked } else if component.isFeatured { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift new file mode 100644 index 0000000000..4721e8897f --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupEmbeddedView.swift @@ -0,0 +1,209 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramPresentationData +import AnimationCache +import MultiAnimationRenderer +import PagerComponent + +final class GroupEmbeddedView: UIScrollView, UIScrollViewDelegate, PagerExpandableScrollView { + private struct ItemLayout { + var itemSize: CGFloat + var itemSpacing: CGFloat + var sideInset: CGFloat + var itemCount: Int + var contentSize: CGSize + + init(height: CGFloat, sideInset: CGFloat, itemCount: Int) { + self.itemSize = 30.0 + self.itemSpacing = 20.0 + self.sideInset = sideInset + self.itemCount = itemCount + + self.contentSize = CGSize(width: self.sideInset * 2.0 + CGFloat(self.itemCount) * self.itemSize + CGFloat(self.itemCount - 1) * self.itemSpacing, height: height) + } + + func frame(at index: Int) -> CGRect { + return CGRect(origin: CGPoint(x: sideInset + CGFloat(index) * (self.itemSize + self.itemSpacing), y: floor((self.contentSize.height - self.itemSize) / 2.0)), size: CGSize(width: self.itemSize, height: self.itemSize)) + } + + func visibleItems(for rect: CGRect) -> Range? { + let offsetRect = rect.offsetBy(dx: -self.sideInset, dy: 0.0) + var minVisibleIndex = Int(floor((offsetRect.minX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + minVisibleIndex = max(0, minVisibleIndex) + var maxVisibleIndex = Int(ceil((offsetRect.maxX - self.itemSpacing) / (self.itemSize + self.itemSpacing))) + maxVisibleIndex = min(maxVisibleIndex, self.itemCount - 1) + + if minVisibleIndex <= maxVisibleIndex { + return minVisibleIndex ..< (maxVisibleIndex + 1) + } else { + return nil + } + } + } + + private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void + + private var visibleItemLayers: [EmojiKeyboardItemLayer.Key: EmojiKeyboardItemLayer] = [:] + private var ignoreScrolling: Bool = false + + private var context: AccountContext? + private var theme: PresentationTheme? + private var cache: AnimationCache? + private var renderer: MultiAnimationRenderer? + private var currentInsets: UIEdgeInsets? + private var currentSize: CGSize? + private var items: [EmojiPagerContentComponent.Item]? + private var isStickers: Bool = false + + private var itemLayout: ItemLayout? + + init(performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { + self.performItemAction = performItemAction + + super.init(frame: CGRect()) + + self.delaysContentTouches = false + if #available(iOSApplicationExtension 11.0, iOS 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + if #available(iOS 13.0, *) { + self.automaticallyAdjustsScrollIndicatorInsets = false + } + self.showsVerticalScrollIndicator = true + self.showsHorizontalScrollIndicator = false + self.delegate = self + self.clipsToBounds = true + self.scrollsToTop = false + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func tapGesture(point: CGPoint) -> Bool { + guard let itemLayout = self.itemLayout else { + return false + } + + for (_, itemLayer) in self.visibleItemLayers { + if itemLayer.frame.inset(by: UIEdgeInsets(top: -6.0, left: -itemLayout.itemSpacing, bottom: -6.0, right: -itemLayout.itemSpacing)).contains(point) { + self.performItemAction(itemLayer.item, self, itemLayer.frame, itemLayer) + return true + } + } + + return false + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.ignoreScrolling { + self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: false) + } + } + + private func updateVisibleItems(transition: Transition, attemptSynchronousLoad: Bool) { + guard let context = self.context, let theme = self.theme, let itemLayout = self.itemLayout, let items = self.items, let cache = self.cache, let renderer = self.renderer else { + return + } + + var validIds = Set() + if let itemRange = itemLayout.visibleItems(for: self.bounds) { + for index in itemRange.lowerBound ..< itemRange.upperBound { + let item = items[index] + let itemId = EmojiKeyboardItemLayer.Key( + groupId: AnyHashable(0), + itemId: item.content.id + ) + validIds.insert(itemId) + + let itemLayer: EmojiKeyboardItemLayer + if let current = self.visibleItemLayers[itemId] { + itemLayer = current + } else { + itemLayer = EmojiKeyboardItemLayer( + item: item, + context: context, + attemptSynchronousLoad: attemptSynchronousLoad, + content: item.content, + cache: cache, + renderer: renderer, + placeholderColor: .clear, + blurredBadgeColor: .clear, + accentIconColor: theme.list.itemAccentColor, + pointSize: CGSize(width: 32.0, height: 32.0), + onUpdateDisplayPlaceholder: { _, _ in + } + ) + self.visibleItemLayers[itemId] = itemLayer + self.layer.addSublayer(itemLayer) + } + + switch item.tintMode { + case let .custom(color): + itemLayer.layerTintColor = color.cgColor + case .accent: + itemLayer.layerTintColor = theme.list.itemAccentColor.cgColor + case .primary: + itemLayer.layerTintColor = theme.list.itemPrimaryTextColor.cgColor + case .none: + itemLayer.layerTintColor = nil + } + + let itemFrame = itemLayout.frame(at: index) + itemLayer.frame = itemFrame + + itemLayer.isVisibleForAnimations = self.isStickers ? context.sharedContext.energyUsageSettings.loopStickers : context.sharedContext.energyUsageSettings.loopEmoji + } + } + + var removedIds: [EmojiKeyboardItemLayer.Key] = [] + for (id, itemLayer) in self.visibleItemLayers { + if !validIds.contains(id) { + removedIds.append(id) + itemLayer.removeFromSuperlayer() + } + } + for id in removedIds { + self.visibleItemLayers.removeValue(forKey: id) + } + } + + func update( + context: AccountContext, + theme: PresentationTheme, + insets: UIEdgeInsets, + size: CGSize, + items: [EmojiPagerContentComponent.Item], + isStickers: Bool, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + attemptSynchronousLoad: Bool + ) { + if self.theme === theme && self.currentInsets == insets && self.currentSize == size && self.items == items { + return + } + + self.context = context + self.theme = theme + self.currentInsets = insets + self.currentSize = size + self.items = items + self.isStickers = isStickers + self.cache = cache + self.renderer = renderer + + let itemLayout = ItemLayout(height: size.height, sideInset: insets.left, itemCount: items.count) + self.itemLayout = itemLayout + + self.ignoreScrolling = true + if itemLayout.contentSize != self.contentSize { + self.contentSize = itemLayout.contentSize + } + self.ignoreScrolling = false + + self.updateVisibleItems(transition: .immediate, attemptSynchronousLoad: attemptSynchronousLoad) + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift new file mode 100644 index 0000000000..0b3be8405d --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupExpandActionButton.swift @@ -0,0 +1,128 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class GroupExpandActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + private let textLayer: SimpleLayer + private let pressed: () -> Void + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + self.tintContainerLayer = SimpleLayer() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.masksToBounds = true + + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + + self.textLayer = SimpleLayer() + + super.init(frame: CGRect()) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + + self.layer.addSublayer(self.backgroundLayer) + + self.layer.addSublayer(self.textLayer) + + self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + @objc private func onPressed() { + self.pressed() + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.alpha = 0.6 + + return super.beginTracking(touch, with: event) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.endTracking(touch, with: event) + } + + override func cancelTracking(with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.cancelTracking(with: event) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.touchesCancelled(touches, with: event) + } + + func update(theme: PresentationTheme, title: String, useOpaqueTheme: Bool) -> CGSize { + let textConstrainedWidth: CGFloat = 100.0 + let color = theme.list.itemCheckColors.foregroundColor + + if useOpaqueTheme { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlOpaqueOverlayColor.cgColor + } else { + self.backgroundLayer.backgroundColor = theme.chat.inputMediaPanel.panelContentControlVibrantOverlayColor.cgColor + } + self.tintContainerLayer.backgroundColor = UIColor.white.cgColor + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont = Font.semibold(13.0) + let string = NSAttributedString(string: title.uppercased(), font: font, textColor: color) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.currentTextLayout = (title, color, textConstrainedWidth, textSize) + } + + var sideInset: CGFloat = 10.0 + if textSize.width > 24.0 { + sideInset = 6.0 + } + let size = CGSize(width: textSize.width + sideInset * 2.0, height: 28.0) + + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floor((size.height - textSize.height) / 2.0)), size: textSize) + self.textLayer.frame = textFrame + + self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.tintBackgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + self.tintContainerLayer.cornerRadius = min(size.width, size.height) / 2.0 + + return size + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift new file mode 100644 index 0000000000..cbc39084de --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderActionButton.swift @@ -0,0 +1,149 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class GroupHeaderActionButton: UIButton { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + let tintContainerLayer: SimpleLayer + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private let backgroundLayer: SimpleLayer + private let tintBackgroundLayer: SimpleLayer + private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer + private let pressed: () -> Void + + init(pressed: @escaping () -> Void) { + self.pressed = pressed + + self.tintContainerLayer = SimpleLayer() + + self.backgroundLayer = SimpleLayer() + self.backgroundLayer.masksToBounds = true + + self.tintBackgroundLayer = SimpleLayer() + self.tintBackgroundLayer.masksToBounds = true + + self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() + + super.init(frame: CGRect()) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContainerLayer + + self.layer.addSublayer(self.backgroundLayer) + self.layer.addSublayer(self.textLayer) + + self.addTarget(self, action: #selector(self.onPressed), for: .touchUpInside) + + self.tintContainerLayer.addSublayer(self.tintBackgroundLayer) + self.tintContainerLayer.addSublayer(self.tintTextLayer) + } + + required init(coder: NSCoder) { + preconditionFailure() + } + + @objc private func onPressed() { + self.pressed() + } + + override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.alpha = 0.6 + + return super.beginTracking(touch, with: event) + } + + override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.endTracking(touch, with: event) + } + + override func cancelTracking(with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.cancelTracking(with: event) + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + let alpha = self.alpha + self.alpha = 1.0 + self.layer.animateAlpha(from: alpha, to: 1.0, duration: 0.25) + + super.touchesCancelled(touches, with: event) + } + + func update(theme: PresentationTheme, title: String, compact: Bool) -> CGSize { + let textConstrainedWidth: CGFloat = 100.0 + + let needsVibrancy = !theme.overallDarkAppearance && compact + + let foregroundColor: UIColor + let backgroundColor: UIColor + + if compact { + foregroundColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + backgroundColor = foregroundColor.withMultipliedAlpha(0.2) + } else { + foregroundColor = theme.list.itemCheckColors.foregroundColor + backgroundColor = theme.list.itemCheckColors.fillColor + } + + self.backgroundLayer.backgroundColor = backgroundColor.cgColor + self.tintBackgroundLayer.backgroundColor = UIColor.white.withAlphaComponent(0.2).cgColor + + self.tintContainerLayer.isHidden = !needsVibrancy + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == foregroundColor, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont = compact ? Font.medium(11.0) : Font.semibold(15.0) + let string = NSAttributedString(string: title.uppercased(), font: font, textColor: foregroundColor) + let tintString = NSAttributedString(string: title.uppercased(), font: font, textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + tintString.draw(in: stringBounds) + + UIGraphicsPopContext() + })?.cgImage + self.currentTextLayout = (title, foregroundColor, textConstrainedWidth, textSize) + } + + let size = CGSize(width: textSize.width + (compact ? 6.0 : 16.0) * 2.0, height: compact ? 16.0 : 28.0) + + let textFrame = CGRect(origin: CGPoint(x: floor((size.width - textSize.width) / 2.0), y: floorToScreenPixels((size.height - textSize.height) / 2.0)), size: textSize) + self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame + + self.backgroundLayer.frame = CGRect(origin: CGPoint(), size: size) + self.backgroundLayer.cornerRadius = min(size.width, size.height) / 2.0 + + self.tintBackgroundLayer.frame = self.backgroundLayer.frame + self.tintBackgroundLayer.cornerRadius = self.backgroundLayer.cornerRadius + + return size + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift new file mode 100644 index 0000000000..857c0ca5d1 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/GroupHeaderLayer.swift @@ -0,0 +1,525 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore +import TelegramPresentationData +import AnimationCache +import MultiAnimationRenderer + +final class GroupHeaderLayer: UIView { + override static var layerClass: AnyClass { + return PassthroughLayer.self + } + + private let actionPressed: () -> Void + private let performItemAction: (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void + + private let textLayer: SimpleLayer + private let tintTextLayer: SimpleLayer + + private var subtitleLayer: SimpleLayer? + private var tintSubtitleLayer: SimpleLayer? + private var lockIconLayer: SimpleLayer? + private var tintLockIconLayer: SimpleLayer? + private var badgeLayer: SimpleLayer? + private var tintBadgeLayer: SimpleLayer? + private(set) var clearIconLayer: SimpleLayer? + private var tintClearIconLayer: SimpleLayer? + private var separatorLayer: SimpleLayer? + private var tintSeparatorLayer: SimpleLayer? + private var actionButton: GroupHeaderActionButton? + + private var groupEmbeddedView: GroupEmbeddedView? + + private var theme: PresentationTheme? + + private var currentTextLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + private var currentSubtitleLayout: (string: String, color: UIColor, constrainedWidth: CGFloat, size: CGSize)? + + let tintContentLayer: SimpleLayer + + init(actionPressed: @escaping () -> Void, performItemAction: @escaping (EmojiPagerContentComponent.Item, UIView, CGRect, CALayer) -> Void) { + self.actionPressed = actionPressed + self.performItemAction = performItemAction + + self.textLayer = SimpleLayer() + self.tintTextLayer = SimpleLayer() + + self.tintContentLayer = SimpleLayer() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.textLayer) + self.tintContentLayer.addSublayer(self.tintTextLayer) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.tintContentLayer + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update( + context: AccountContext, + theme: PresentationTheme, + forceNeedsVibrancy: Bool, + layoutType: EmojiPagerContentComponent.ItemLayoutType, + hasTopSeparator: Bool, + actionButtonTitle: String?, + actionButtonIsCompact: Bool, + title: String, + subtitle: String?, + badge: String?, + isPremiumLocked: Bool, + hasClear: Bool, + embeddedItems: [EmojiPagerContentComponent.Item]?, + isStickers: Bool, + constrainedSize: CGSize, + insets: UIEdgeInsets, + cache: AnimationCache, + renderer: MultiAnimationRenderer, + attemptSynchronousLoad: Bool + ) -> (size: CGSize, centralContentWidth: CGFloat) { + var themeUpdated = false + if self.theme !== theme { + self.theme = theme + themeUpdated = true + } + + let needsVibrancy = !theme.overallDarkAppearance || forceNeedsVibrancy + + let textOffsetY: CGFloat + if hasTopSeparator { + textOffsetY = 9.0 + } else { + textOffsetY = 0.0 + } + + let subtitleColor: UIColor + if theme.overallDarkAppearance && forceNeedsVibrancy { + subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor.withMultipliedAlpha(0.2) + } else { + subtitleColor = theme.chat.inputMediaPanel.panelContentVibrantOverlayColor + } + + let color: UIColor + let needsTintText: Bool + if subtitle != nil { + color = theme.chat.inputPanel.primaryTextColor + needsTintText = false + } else { + color = subtitleColor + needsTintText = true + } + + let titleHorizontalOffset: CGFloat + if isPremiumLocked { + titleHorizontalOffset = 10.0 + 2.0 + } else { + titleHorizontalOffset = 0.0 + } + + var actionButtonSize: CGSize? + if let actionButtonTitle = actionButtonTitle { + let actionButton: GroupHeaderActionButton + if let current = self.actionButton { + actionButton = current + } else { + actionButton = GroupHeaderActionButton(pressed: self.actionPressed) + self.actionButton = actionButton + self.addSubview(actionButton) + self.tintContentLayer.addSublayer(actionButton.tintContainerLayer) + } + + actionButtonSize = actionButton.update(theme: theme, title: actionButtonTitle, compact: actionButtonIsCompact) + } else { + if let actionButton = self.actionButton { + self.actionButton = nil + actionButton.removeFromSuperview() + } + } + + var clearSize: CGSize = .zero + var clearWidth: CGFloat = 0.0 + if hasClear { + var updateImage = themeUpdated + + let clearIconLayer: SimpleLayer + if let current = self.clearIconLayer { + clearIconLayer = current + } else { + updateImage = true + clearIconLayer = SimpleLayer() + self.clearIconLayer = clearIconLayer + self.layer.addSublayer(clearIconLayer) + } + let tintClearIconLayer: SimpleLayer + if let current = self.tintClearIconLayer { + tintClearIconLayer = current + } else { + updateImage = true + tintClearIconLayer = SimpleLayer() + self.tintClearIconLayer = tintClearIconLayer + self.tintContentLayer.addSublayer(tintClearIconLayer) + } + + tintClearIconLayer.isHidden = !needsVibrancy + + clearSize = clearIconLayer.bounds.size + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: subtitleColor) { + clearSize = image.size + clearIconLayer.contents = image.cgImage + } + if updateImage, let image = PresentationResourcesChat.chatInputMediaPanelGridDismissImage(theme, color: .white) { + tintClearIconLayer.contents = image.cgImage + } + + tintClearIconLayer.frame = clearIconLayer.frame + clearWidth = 4.0 + clearSize.width + } else { + if let clearIconLayer = self.clearIconLayer { + self.clearIconLayer = nil + clearIconLayer.removeFromSuperlayer() + } + if let tintClearIconLayer = self.tintClearIconLayer { + self.tintClearIconLayer = nil + tintClearIconLayer.removeFromSuperlayer() + } + } + + var textConstrainedWidth = constrainedSize.width - titleHorizontalOffset - 10.0 + if let actionButtonSize = actionButtonSize { + if actionButtonIsCompact { + textConstrainedWidth -= actionButtonSize.width * 2.0 + 10.0 + } else { + textConstrainedWidth -= actionButtonSize.width + 10.0 + } + } + if clearWidth > 0.0 { + textConstrainedWidth -= clearWidth + 8.0 + } + + let textSize: CGSize + if let currentTextLayout = self.currentTextLayout, currentTextLayout.string == title, currentTextLayout.color == color, currentTextLayout.constrainedWidth == textConstrainedWidth { + textSize = currentTextLayout.size + } else { + let font: UIFont + let stringValue: String + if subtitle == nil { + font = Font.medium(13.0) + stringValue = title.uppercased() + } else { + font = Font.semibold(16.0) + stringValue = title + } + let string = NSAttributedString(string: stringValue, font: font, textColor: color) + let whiteString = NSAttributedString(string: stringValue, font: font, textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + textSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + self.textLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + //string.draw(in: stringBounds) + string.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.contents = generateImage(textSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + //whiteString.draw(in: stringBounds) + whiteString.draw(with: stringBounds, options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + })?.cgImage + self.tintTextLayer.isHidden = !needsVibrancy + self.currentTextLayout = (title, color, textConstrainedWidth, textSize) + } + + var badgeSize: CGSize = .zero + if let badge { + func generateBadgeImage(color: UIColor) -> UIImage? { + let string = NSAttributedString(string: badge, font: Font.semibold(11.0), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: 120, height: 18.0), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + let badgeSize = CGSize(width: stringBounds.width + 8.0, height: 16.0) + return generateImage(badgeSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(color.cgColor) + context.addPath(UIBezierPath(roundedRect: CGRect(origin: .zero, size: badgeSize), cornerRadius: badgeSize.height / 2.0).cgPath) + context.fillPath() + + context.setBlendMode(.clear) + + UIGraphicsPushContext(context) + + string.draw(with: CGRect(origin: CGPoint(x: floorToScreenPixels((badgeSize.width - stringBounds.size.width) / 2.0), y: floorToScreenPixels((badgeSize.height - stringBounds.size.height) / 2.0)), size: stringBounds.size), options: [.usesLineFragmentOrigin, .truncatesLastVisibleLine], context: nil) + + UIGraphicsPopContext() + }) + } + + let badgeLayer: SimpleLayer + if let current = self.badgeLayer { + badgeLayer = current + } else { + badgeLayer = SimpleLayer() + self.badgeLayer = badgeLayer + self.layer.addSublayer(badgeLayer) + + if let image = generateBadgeImage(color: color.withMultipliedAlpha(0.66)) { + badgeLayer.contents = image.cgImage + badgeLayer.bounds = CGRect(origin: .zero, size: image.size) + } + } + badgeSize = badgeLayer.bounds.size + + let tintBadgeLayer: SimpleLayer + if let current = self.tintBadgeLayer { + tintBadgeLayer = current + } else { + tintBadgeLayer = SimpleLayer() + self.tintBadgeLayer = tintBadgeLayer + self.tintContentLayer.addSublayer(tintBadgeLayer) + + if let image = generateBadgeImage(color: .white) { + tintBadgeLayer.contents = image.cgImage + } + } + } else { + if let badgeLayer = self.badgeLayer { + self.badgeLayer = nil + badgeLayer.removeFromSuperlayer() + } + if let tintBadgeLayer = self.tintBadgeLayer { + self.tintBadgeLayer = nil + tintBadgeLayer.removeFromSuperlayer() + } + } + + let textFrame: CGRect + if subtitle == nil { + textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset + floor((constrainedSize.width - titleHorizontalOffset - (textSize.width + badgeSize.width)) / 2.0), y: textOffsetY), size: textSize) + } else { + textFrame = CGRect(origin: CGPoint(x: titleHorizontalOffset, y: textOffsetY), size: textSize) + } + self.textLayer.frame = textFrame + self.tintTextLayer.frame = textFrame + self.tintTextLayer.isHidden = !needsTintText + + if let badgeLayer = self.badgeLayer, let tintBadgeLayer = self.tintBadgeLayer { + badgeLayer.frame = CGRect(origin: CGPoint(x: textFrame.maxX + 4.0, y: 0.0), size: badgeLayer.frame.size) + tintBadgeLayer.frame = badgeLayer.frame + } + + if isPremiumLocked { + let lockIconLayer: SimpleLayer + if let current = self.lockIconLayer { + lockIconLayer = current + } else { + lockIconLayer = SimpleLayer() + self.lockIconLayer = lockIconLayer + self.layer.addSublayer(lockIconLayer) + } + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: color) { + let imageSize = image.size + lockIconLayer.contents = image.cgImage + lockIconLayer.frame = CGRect(origin: CGPoint(x: textFrame.minX - imageSize.width - 3.0, y: 2.0 + UIScreenPixel), size: imageSize) + } else { + lockIconLayer.contents = nil + } + + let tintLockIconLayer: SimpleLayer + if let current = self.tintLockIconLayer { + tintLockIconLayer = current + } else { + tintLockIconLayer = SimpleLayer() + self.tintLockIconLayer = tintLockIconLayer + self.tintContentLayer.addSublayer(tintLockIconLayer) + } + if let image = PresentationResourcesChat.chatEntityKeyboardLock(theme, color: .white) { + tintLockIconLayer.contents = image.cgImage + tintLockIconLayer.frame = lockIconLayer.frame + tintLockIconLayer.isHidden = !needsVibrancy + } else { + tintLockIconLayer.contents = nil + } + } else { + if let lockIconLayer = self.lockIconLayer { + self.lockIconLayer = nil + lockIconLayer.removeFromSuperlayer() + } + if let tintLockIconLayer = self.tintLockIconLayer { + self.tintLockIconLayer = nil + tintLockIconLayer.removeFromSuperlayer() + } + } + + let subtitleSize: CGSize + if let subtitle = subtitle { + var updateSubtitleContents: UIImage? + var updateTintSubtitleContents: UIImage? + if let currentSubtitleLayout = self.currentSubtitleLayout, currentSubtitleLayout.string == subtitle, currentSubtitleLayout.color == subtitleColor, currentSubtitleLayout.constrainedWidth == textConstrainedWidth { + subtitleSize = currentSubtitleLayout.size + } else { + let string = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: subtitleColor) + let whiteString = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: .white) + let stringBounds = string.boundingRect(with: CGSize(width: textConstrainedWidth, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + subtitleSize = CGSize(width: ceil(stringBounds.width), height: ceil(stringBounds.height)) + updateSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + string.draw(in: stringBounds) + + UIGraphicsPopContext() + }) + updateTintSubtitleContents = generateImage(subtitleSize, opaque: false, scale: 0.0, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + + whiteString.draw(in: stringBounds) + + UIGraphicsPopContext() + }) + self.currentSubtitleLayout = (subtitle, subtitleColor, textConstrainedWidth, subtitleSize) + } + + let subtitleLayer: SimpleLayer + if let current = self.subtitleLayer { + subtitleLayer = current + } else { + subtitleLayer = SimpleLayer() + self.subtitleLayer = subtitleLayer + self.layer.addSublayer(subtitleLayer) + } + + if let updateSubtitleContents = updateSubtitleContents { + subtitleLayer.contents = updateSubtitleContents.cgImage + } + + let tintSubtitleLayer: SimpleLayer + if let current = self.tintSubtitleLayer { + tintSubtitleLayer = current + } else { + tintSubtitleLayer = SimpleLayer() + self.tintSubtitleLayer = tintSubtitleLayer + self.tintContentLayer.addSublayer(tintSubtitleLayer) + } + tintSubtitleLayer.isHidden = !needsVibrancy + + if let updateTintSubtitleContents = updateTintSubtitleContents { + tintSubtitleLayer.contents = updateTintSubtitleContents.cgImage + } + + let subtitleFrame = CGRect(origin: CGPoint(x: 0.0, y: textFrame.maxY + 1.0), size: subtitleSize) + subtitleLayer.frame = subtitleFrame + tintSubtitleLayer.frame = subtitleFrame + } else { + subtitleSize = CGSize() + if let subtitleLayer = self.subtitleLayer { + self.subtitleLayer = nil + subtitleLayer.removeFromSuperlayer() + } + if let tintSubtitleLayer = self.tintSubtitleLayer { + self.tintSubtitleLayer = nil + tintSubtitleLayer.removeFromSuperlayer() + } + } + + self.clearIconLayer?.frame = CGRect(origin: CGPoint(x: constrainedSize.width - clearSize.width, y: floorToScreenPixels((textSize.height - clearSize.height) / 2.0)), size: clearSize) + + var size: CGSize + size = CGSize(width: constrainedSize.width, height: constrainedSize.height) + + if let embeddedItems = embeddedItems { + let groupEmbeddedView: GroupEmbeddedView + if let current = self.groupEmbeddedView { + groupEmbeddedView = current + } else { + groupEmbeddedView = GroupEmbeddedView(performItemAction: self.performItemAction) + self.groupEmbeddedView = groupEmbeddedView + self.addSubview(groupEmbeddedView) + } + + let groupEmbeddedViewSize = CGSize(width: constrainedSize.width + insets.left + insets.right, height: 36.0) + groupEmbeddedView.frame = CGRect(origin: CGPoint(x: -insets.left, y: size.height - groupEmbeddedViewSize.height), size: groupEmbeddedViewSize) + groupEmbeddedView.update( + context: context, + theme: theme, + insets: insets, + size: groupEmbeddedViewSize, + items: embeddedItems, + isStickers: isStickers, + cache: cache, + renderer: renderer, + attemptSynchronousLoad: attemptSynchronousLoad + ) + } else { + if let groupEmbeddedView = self.groupEmbeddedView { + self.groupEmbeddedView = nil + groupEmbeddedView.removeFromSuperview() + } + } + + if let actionButtonSize = actionButtonSize, let actionButton = self.actionButton { + let actionButtonFrame = CGRect(origin: CGPoint(x: size.width - actionButtonSize.width, y: textFrame.minY + (actionButtonIsCompact ? 0.0 : 3.0)), size: actionButtonSize) + actionButton.bounds = CGRect(origin: CGPoint(), size: actionButtonFrame.size) + actionButton.center = actionButtonFrame.center + } + + if hasTopSeparator { + let separatorLayer: SimpleLayer + if let current = self.separatorLayer { + separatorLayer = current + } else { + separatorLayer = SimpleLayer() + self.separatorLayer = separatorLayer + self.layer.addSublayer(separatorLayer) + } + separatorLayer.backgroundColor = subtitleColor.cgColor + separatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) + + let tintSeparatorLayer: SimpleLayer + if let current = self.tintSeparatorLayer { + tintSeparatorLayer = current + } else { + tintSeparatorLayer = SimpleLayer() + self.tintSeparatorLayer = tintSeparatorLayer + self.tintContentLayer.addSublayer(tintSeparatorLayer) + } + tintSeparatorLayer.backgroundColor = UIColor.white.cgColor + tintSeparatorLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel)) + + tintSeparatorLayer.isHidden = !needsVibrancy + } else { + if let separatorLayer = self.separatorLayer { + self.separatorLayer = separatorLayer + separatorLayer.removeFromSuperlayer() + } + if let tintSeparatorLayer = self.tintSeparatorLayer { + self.tintSeparatorLayer = tintSeparatorLayer + tintSeparatorLayer.removeFromSuperlayer() + } + } + + return (size, titleHorizontalOffset + textSize.width + clearWidth) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return super.hitTest(point, with: event) + } + + func tapGesture(point: CGPoint) -> Bool { + if let groupEmbeddedView = self.groupEmbeddedView { + return groupEmbeddedView.tapGesture(point: self.convert(point, to: groupEmbeddedView)) + } else { + return false + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift new file mode 100644 index 0000000000..eb60512e4e --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PassthroughComponents.swift @@ -0,0 +1,346 @@ +import Foundation +import UIKit +import Display +import ComponentFlow + +public class PassthroughLayer: CALayer { + public var mirrorLayer: CALayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override public var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override public var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override public var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override public var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override public func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override public func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override public func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} + +open class PassthroughView: UIView { + override public static var layerClass: AnyClass { + return PassthroughLayer.self + } + + public let passthroughView: UIView + + override public init(frame: CGRect) { + self.passthroughView = UIView() + + super.init(frame: frame) + + (self.layer as? PassthroughLayer)?.mirrorLayer = self.passthroughView.layer + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +class PassthroughShapeLayer: CAShapeLayer { + var mirrorLayer: CAShapeLayer? + + override init() { + super.init() + } + + override init(layer: Any) { + super.init(layer: layer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var position: CGPoint { + get { + return super.position + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.position = value + } + super.position = value + } + } + + override var bounds: CGRect { + get { + return super.bounds + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.bounds = value + } + super.bounds = value + } + } + + override var opacity: Float { + get { + return super.opacity + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.opacity = value + } + super.opacity = value + } + } + + override var sublayerTransform: CATransform3D { + get { + return super.sublayerTransform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.sublayerTransform = value + } + super.sublayerTransform = value + } + } + + override var transform: CATransform3D { + get { + return super.transform + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.transform = value + } + super.transform = value + } + } + + override var path: CGPath? { + get { + return super.path + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.path = value + } + super.path = value + } + } + + override var fillColor: CGColor? { + get { + return super.fillColor + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillColor = value + } + super.fillColor = value + } + } + + override var fillRule: CAShapeLayerFillRule { + get { + return super.fillRule + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.fillRule = value + } + super.fillRule = value + } + } + + override var strokeColor: CGColor? { + get { + return super.strokeColor + } set(value) { + /*if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeColor = value + }*/ + super.strokeColor = value + } + } + + override var strokeStart: CGFloat { + get { + return super.strokeStart + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeStart = value + } + super.strokeStart = value + } + } + + override var strokeEnd: CGFloat { + get { + return super.strokeEnd + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.strokeEnd = value + } + super.strokeEnd = value + } + } + + override var lineWidth: CGFloat { + get { + return super.lineWidth + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineWidth = value + } + super.lineWidth = value + } + } + + override var miterLimit: CGFloat { + get { + return super.miterLimit + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.miterLimit = value + } + super.miterLimit = value + } + } + + override var lineCap: CAShapeLayerLineCap { + get { + return super.lineCap + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineCap = value + } + super.lineCap = value + } + } + + override var lineJoin: CAShapeLayerLineJoin { + get { + return super.lineJoin + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineJoin = value + } + super.lineJoin = value + } + } + + override var lineDashPhase: CGFloat { + get { + return super.lineDashPhase + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPhase = value + } + super.lineDashPhase = value + } + } + + override var lineDashPattern: [NSNumber]? { + get { + return super.lineDashPattern + } set(value) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.lineDashPattern = value + } + super.lineDashPattern = value + } + } + + override func add(_ animation: CAAnimation, forKey key: String?) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.add(animation, forKey: key) + } + + super.add(animation, forKey: key) + } + + override func removeAllAnimations() { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAllAnimations() + } + + super.removeAllAnimations() + } + + override func removeAnimation(forKey: String) { + if let mirrorLayer = self.mirrorLayer { + mirrorLayer.removeAnimation(forKey: forKey) + } + + super.removeAnimation(forKey: forKey) + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift new file mode 100644 index 0000000000..6e0a0497d2 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/PremiumBadgeView.swift @@ -0,0 +1,140 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import AccountContext +import TelegramCore + +private let premiumBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat List/PeerPremiumIcon"), color: .white) +private let featuredBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeAdd"), color: .white) +private let lockedBadgeIcon: UIImage? = generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Media/PanelBadgeLock"), color: .white) + +private let itemBadgeTextFont: UIFont = { + return Font.regular(10.0) +}() + +final class PremiumBadgeView: UIView { + private let context: AccountContext + + private var badge: EmojiKeyboardItemLayer.Badge? + + let contentLayer: SimpleLayer + private let overlayColorLayer: SimpleLayer + private let iconLayer: SimpleLayer + private var customFileLayer: InlineFileIconLayer? + + init(context: AccountContext) { + self.context = context + + self.contentLayer = SimpleLayer() + self.contentLayer.contentsGravity = .resize + self.contentLayer.masksToBounds = true + + self.overlayColorLayer = SimpleLayer() + self.overlayColorLayer.masksToBounds = true + + self.iconLayer = SimpleLayer() + + super.init(frame: CGRect()) + + self.layer.addSublayer(self.contentLayer) + self.layer.addSublayer(self.overlayColorLayer) + self.layer.addSublayer(self.iconLayer) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(transition: Transition, badge: EmojiKeyboardItemLayer.Badge, backgroundColor: UIColor, size: CGSize) { + if self.badge != badge { + self.badge = badge + + switch badge { + case .premium: + self.iconLayer.contents = premiumBadgeIcon?.cgImage + case .featured: + self.iconLayer.contents = featuredBadgeIcon?.cgImage + case .locked: + self.iconLayer.contents = lockedBadgeIcon?.cgImage + case let .text(text): + let string = NSAttributedString(string: text, font: itemBadgeTextFont) + let size = CGSize(width: 12.0, height: 12.0) + let stringBounds = string.boundingRect(with: CGSize(width: 100.0, height: 100.0), options: .usesLineFragmentOrigin, context: nil) + let image = generateImage(size, rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + string.draw(at: CGPoint(x: floor((size.width - stringBounds.width) * 0.5), y: floor((size.height - stringBounds.height) * 0.5))) + UIGraphicsPopContext() + }) + self.iconLayer.contents = image?.cgImage + case .customFile: + self.iconLayer.contents = nil + } + + if case let .customFile(customFile) = badge { + let customFileLayer: InlineFileIconLayer + if let current = self.customFileLayer { + customFileLayer = current + } else { + customFileLayer = InlineFileIconLayer( + context: self.context, + userLocation: .other, + attemptSynchronousLoad: false, + file: customFile, + cache: self.context.animationCache, + renderer: self.context.animationRenderer, + unique: false, + placeholderColor: .clear, + pointSize: CGSize(width: 18.0, height: 18.0), + dynamicColor: nil + ) + self.customFileLayer = customFileLayer + self.layer.addSublayer(customFileLayer) + } + let _ = customFileLayer + } else { + if let customFileLayer = self.customFileLayer { + self.customFileLayer = nil + customFileLayer.removeFromSuperlayer() + } + } + } + + let iconInset: CGFloat + switch badge { + case .premium: + iconInset = 2.0 + case .featured: + iconInset = 0.0 + case .locked: + iconInset = 0.0 + case .text, .customFile: + iconInset = 0.0 + } + + switch badge { + case .text, .customFile: + self.contentLayer.isHidden = true + self.overlayColorLayer.isHidden = true + default: + self.contentLayer.isHidden = false + self.overlayColorLayer.isHidden = false + } + + self.overlayColorLayer.backgroundColor = backgroundColor.cgColor + + transition.setFrame(layer: self.contentLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setCornerRadius(layer: self.contentLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) + + transition.setFrame(layer: self.overlayColorLayer, frame: CGRect(origin: CGPoint(), size: size)) + transition.setCornerRadius(layer: self.overlayColorLayer, cornerRadius: min(size.width / 2.0, size.height / 2.0)) + + transition.setFrame(layer: self.iconLayer, frame: CGRect(origin: CGPoint(), size: size).insetBy(dx: iconInset, dy: iconInset)) + + if let customFileLayer = self.customFileLayer { + let iconSize = CGSize(width: 18.0, height: 18.0) + transition.setFrame(layer: customFileLayer, frame: CGRect(origin: CGPoint(), size: iconSize)) + } + } +} diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift new file mode 100644 index 0000000000..7ccea1a793 --- /dev/null +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/WarpView.swift @@ -0,0 +1,138 @@ +import Foundation +import UIKit +import Display +import ComponentFlow +import TelegramPresentationData + +final class WarpView: UIView { + private final class WarpPartView: UIView { + let cloneView: PortalView + + init?(contentView: PortalSourceView) { + guard let cloneView = PortalView(matchPosition: false) else { + return nil + } + self.cloneView = cloneView + + super.init(frame: CGRect()) + + self.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0) + + self.clipsToBounds = true + self.addSubview(cloneView.view) + contentView.addPortal(view: cloneView) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(containerSize: CGSize, rect: CGRect, transition: Transition) { + transition.setFrame(view: self.cloneView.view, frame: CGRect(origin: CGPoint(x: -rect.minX, y: -rect.minY), size: CGSize(width: containerSize.width, height: containerSize.height))) + } + } + + let contentView: PortalSourceView + + private let clippingView: UIView + + private var warpViews: [WarpPartView] = [] + private let warpMaskContainer: UIView + private let warpMaskGradientLayer: SimpleGradientLayer + + override init(frame: CGRect) { + self.contentView = PortalSourceView() + self.clippingView = UIView() + + self.warpMaskContainer = UIView() + self.warpMaskGradientLayer = SimpleGradientLayer() + self.warpMaskContainer.layer.mask = self.warpMaskGradientLayer + + super.init(frame: frame) + + self.clippingView.addSubview(self.contentView) + + self.clippingView.clipsToBounds = true + self.addSubview(self.clippingView) + self.addSubview(self.warpMaskContainer) + + for _ in 0 ..< 8 { + if let warpView = WarpPartView(contentView: self.contentView) { + self.warpViews.append(warpView) + self.warpMaskContainer.addSubview(warpView) + } + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(size: CGSize, topInset: CGFloat, warpHeight: CGFloat, theme: PresentationTheme, transition: Transition) { + transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size)) + + let allItemsHeight = warpHeight * 0.5 + for i in 0 ..< self.warpViews.count { + let itemHeight = warpHeight / CGFloat(self.warpViews.count) + let itemFraction = CGFloat(i + 1) / CGFloat(self.warpViews.count) + let _ = itemHeight + + let da = CGFloat.pi * 0.5 / CGFloat(self.warpViews.count) + let alpha = CGFloat.pi * 0.5 - itemFraction * CGFloat.pi * 0.5 + let endPoint = CGPoint(x: cos(alpha), y: sin(alpha)) + let prevAngle = alpha + da + let prevPt = CGPoint(x: cos(prevAngle), y: sin(prevAngle)) + var angle: CGFloat + angle = -atan2(endPoint.y - prevPt.y, endPoint.x - prevPt.x) + + let itemLengthVector = CGPoint(x: endPoint.x - prevPt.x, y: endPoint.y - prevPt.y) + let itemLength = sqrt(itemLengthVector.x * itemLengthVector.x + itemLengthVector.y * itemLengthVector.y) * warpHeight * 0.5 + let _ = itemLength + + var transform: CATransform3D + transform = CATransform3DIdentity + transform.m34 = 1.0 / 240.0 + + transform = CATransform3DTranslate(transform, 0.0, prevPt.x * allItemsHeight, (1.0 - prevPt.y) * allItemsHeight) + transform = CATransform3DRotate(transform, angle, 1.0, 0.0, 0.0) + + let positionY = size.height - allItemsHeight + 4.0 + CGFloat(i) * itemLength + let rect = CGRect(origin: CGPoint(x: 0.0, y: positionY), size: CGSize(width: size.width, height: itemLength)) + transition.setPosition(view: self.warpViews[i], position: CGPoint(x: rect.midX, y: 4.0)) + transition.setBounds(view: self.warpViews[i], bounds: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: itemLength))) + transition.setTransform(view: self.warpViews[i], transform: transform) + self.warpViews[i].update(containerSize: size, rect: rect, transition: transition) + } + + let clippingTopInset: CGFloat = topInset + let frame = CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: CGSize(width: size.width, height: -clippingTopInset + size.height - 21.0)) + transition.setPosition(view: self.clippingView, position: frame.center) + transition.setBounds(view: self.clippingView, bounds: CGRect(origin: CGPoint(x: 0.0, y: clippingTopInset), size: frame.size)) + self.clippingView.clipsToBounds = true + + transition.setFrame(view: self.warpMaskContainer, frame: CGRect(origin: CGPoint(x: 0.0, y: size.height - allItemsHeight), size: CGSize(width: size.width, height: allItemsHeight))) + + var locations: [NSNumber] = [] + var colors: [CGColor] = [] + let numStops = 6 + for i in 0 ..< numStops { + let step = CGFloat(i) / CGFloat(numStops - 1) + locations.append(step as NSNumber) + colors.append(UIColor.black.withAlphaComponent(1.0 - step * step).cgColor) + } + + let gradientHeight: CGFloat = 6.0 + self.warpMaskGradientLayer.startPoint = CGPoint(x: 0.0, y: (allItemsHeight - gradientHeight) / allItemsHeight) + self.warpMaskGradientLayer.endPoint = CGPoint(x: 0.0, y: 1.0) + + self.warpMaskGradientLayer.locations = locations + self.warpMaskGradientLayer.colors = colors + self.warpMaskGradientLayer.type = .axial + + transition.setFrame(layer: self.warpMaskGradientLayer, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: allItemsHeight))) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.contentView.hitTest(point, with: event) + } +} diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h index c9ee189b25..b17f942761 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/BezierPath.h @@ -13,10 +13,10 @@ namespace lottie { struct BezierTrimPathPosition { - double start; - double end; + float start; + float end; - explicit BezierTrimPathPosition(double start_, double end_); + explicit BezierTrimPathPosition(float start_, float end_); }; class BezierPathContents: public std::enable_shared_from_this { @@ -38,10 +38,10 @@ public: std::vector elements; std::optional closed; - double length(); + float length(); private: - std::optional _length; + std::optional _length; public: void moveToStartPoint(CurveVertex const &vertex); @@ -72,7 +72,7 @@ public: /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo /// | toLength| |Offset |fromLength | /// - std::vector> trim(double fromLength, double toLength, double offsetLength); + std::vector> trim(float fromLength, float toLength, float offsetLength); // MARK: Private @@ -87,7 +87,7 @@ public: lottiejson11::Json toJson() const; - double length(); + float length(); void moveToStartPoint(CurveVertex const &vertex); void addVertex(CurveVertex const &vertex); @@ -115,14 +115,14 @@ public: /// oooooooooooooooooo--------------------~~~~~~~~~~~~~~~~ooooooooooooooooooooooooooooo /// | toLength| |Offset |fromLength | /// - std::vector trim(double fromLength, double toLength, double offsetLength); + std::vector trim(float fromLength, float toLength, float offsetLength); std::vector const &elements() const; std::vector &mutableElements(); std::optional const &closed() const; void setClosed(std::optional const &closed); std::shared_ptr cgPath() const; - BezierPath copyUsingTransform(CATransform3D const &transform) const; + BezierPath copyUsingTransform(Transform2D const &transform) const; public: BezierPath(std::shared_ptr contents); @@ -146,7 +146,7 @@ CGRect bezierPathsBoundingBox(std::vector const &paths); CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, std::vector const &paths); CGRect bezierPathsBoundingBoxParallel(BezierPathsBoundingBoxContext &context, BezierPath const &path); -std::vector trimBezierPaths(std::vector &sourcePaths, double start, double end, double offset, TrimType type); +std::vector trimBezierPaths(std::vector &sourcePaths, float start, float end, float offset, TrimType type); } diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h index d7e445e635..1ddeeb81ed 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPath.h @@ -57,7 +57,7 @@ public: virtual bool empty() const = 0; - virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const = 0; + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const = 0; virtual void addLineTo(Vector2D const &point) = 0; virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) = 0; @@ -71,7 +71,7 @@ public: virtual bool isEqual(CGPath *other) const = 0; }; -Vector2D transformVector(Vector2D const &v, CATransform3D const &m); +Vector2D transformVector(Vector2D const &v, Transform2D const &m); } diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h index bf80eea07b..af32f20bb5 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CGPathCocoa.h @@ -22,7 +22,7 @@ public: virtual bool empty() const override; - virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const override; virtual void addLineTo(Vector2D const &point) override; virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h index 9f20d05a3e..6e404f29ae 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Color.h @@ -15,10 +15,10 @@ enum class ColorFormatDenominator { }; struct Color { - double r; - double g; - double b; - double a; + float r; + float g; + float b; + float a; bool operator==(Color const &rhs) const { if (r != rhs.r) { @@ -40,7 +40,7 @@ struct Color { return !(*this == rhs); } - explicit Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator = ColorFormatDenominator::One); + explicit Color(float r_, float g_, float b_, float a_, ColorFormatDenominator denominator = ColorFormatDenominator::One); explicit Color(lottiejson11::Json const &jsonAny) noexcept(false); lottiejson11::Json toJson() const; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h index 58f5ba3ae0..90e3ccf305 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/CurveVertex.h @@ -64,7 +64,7 @@ public: return CurveVertex(point + translation, inTangent + translation, outTangent + translation, false); } - CurveVertex transformed(CATransform3D const &transform) const { + CurveVertex transformed(Transform2D const &transform) const { return CurveVertex(transformVector(point, transform), transformVector(inTangent, transform), transformVector(outTangent, transform), false); } @@ -91,7 +91,7 @@ public: /// TI and TO are the new tangents for the trimPoint /// NO and NI are the new tangents for the startPoint and endPoints /// S==NO=========TI==T==TO=======NI==E - CurveVertexSplitResult splitCurve(CurveVertex const &toVertex, double position) const { + CurveVertexSplitResult splitCurve(CurveVertex const &toVertex, float position) const { /// If position is less than or equal to 0, trim at start. if (position <= 0.0) { return CurveVertexSplitResult( @@ -147,8 +147,8 @@ public: /// /// This function should probably live in PathElement, since it deals with curve /// lengths. - CurveVertexSplitResult trimCurve(CurveVertex const &toVertex, double atLength, double curveLength, int maxSamples, double accuracy = 1.0) const { - double currentPosition = atLength / curveLength; + CurveVertexSplitResult trimCurve(CurveVertex const &toVertex, float atLength, float curveLength, int maxSamples, float accuracy = 1.0f) const { + float currentPosition = atLength / curveLength; auto results = splitCurve(toVertex, currentPosition); if (maxSamples == 0) { @@ -162,7 +162,7 @@ public: if (lengthDiff < accuracy) { return results; } - auto diffPosition = std::max(std::min((currentPosition / length) * lengthDiff, currentPosition * 0.5), currentPosition * (-0.5)); + auto diffPosition = std::max(std::min((currentPosition / length) * lengthDiff, currentPosition * 0.5f), currentPosition * (-0.5f)); currentPosition = diffPosition + currentPosition; results = splitCurve(toVertex, currentPosition); } @@ -174,17 +174,17 @@ public: /// For lines (zeroed tangents) the distance between the two points is measured. /// For curves the curve is iterated over by sample count and the points are measured. /// This is ~99% accurate at a sample count of 30 - double distanceTo(CurveVertex const &toVertex, int sampleCount = 25) const { + float distanceTo(CurveVertex const &toVertex, int sampleCount = 25) const { if (outTangentRelative().isZero() && toVertex.inTangentRelative().isZero()) { /// Return a linear distance. return point.distanceTo(toVertex.point); } - double distance = 0.0; + float distance = 0.0; auto previousPoint = point; for (int i = 0; i < sampleCount; i++) { - auto pointOnCurve = splitCurve(toVertex, ((double)(i)) / ((double)(sampleCount))).trimPoint; + auto pointOnCurve = splitCurve(toVertex, ((float)(i)) / ((float)(sampleCount))).trimPoint; distance = distance + previousPoint.distanceTo(pointOnCurve.point); previousPoint = pointOnCurve.point; } diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h index f92e613cee..f4e936e77d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/LottieAnimationContainer.h @@ -25,7 +25,7 @@ typedef struct { CGRect bounds; CGPoint position; CATransform3D transform; - double opacity; + float opacity; bool masksToBounds; bool isHidden; } LottieRenderNodeLayerData; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h index 766fb21435..bb8a8f578d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/PathElement.h @@ -41,7 +41,7 @@ struct __attribute__((packed)) PathElement { } /// Initializes a new path with length - explicit PathElement(std::optional length_, CurveVertex const &vertex_) : + explicit PathElement(std::optional length_, CurveVertex const &vertex_) : vertex(vertex_) { } @@ -58,7 +58,7 @@ struct __attribute__((packed)) PathElement { } /// Splits an element span defined by the receiver and fromElement to a position 0-1 - PathSplitResult splitElementAtPosition(PathElement const &fromElement, double atLength) { + PathSplitResult splitElementAtPosition(PathElement const &fromElement, float atLength) { /// Trim the span. Start and trim go into the first, trim and end go into second. auto trimResults = fromElement.vertex.trimCurve(vertex, atLength, length(fromElement), 3); @@ -81,8 +81,8 @@ struct __attribute__((packed)) PathElement { ); } - double length(PathElement const &previous) { - double result = previous.vertex.distanceTo(vertex); + float length(PathElement const &previous) { + float result = previous.vertex.distanceTo(vertex); return result; } }; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h index 2e5a8f2155..09616bdf0a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/RenderTreeNode.h @@ -14,79 +14,12 @@ namespace lottie { class ProcessedRenderTreeNodeData { -public: - struct LayerParams { - CGRect _bounds; - Vector2D _position; - CATransform3D _transform; - double _opacity; - bool _masksToBounds; - bool _isHidden; - - LayerParams( - CGRect bounds_, - Vector2D position_, - CATransform3D transform_, - double opacity_, - bool masksToBounds_, - bool isHidden_ - ) : - _bounds(bounds_), - _position(position_), - _transform(transform_), - _opacity(opacity_), - _masksToBounds(masksToBounds_), - _isHidden(isHidden_) { - } - - CGRect bounds() const { - return _bounds; - } - - Vector2D position() const { - return _position; - } - - CATransform3D transform() const { - return _transform; - } - - double opacity() const { - return _opacity; - } - - bool masksToBounds() const { - return _masksToBounds; - } - - bool isHidden() const { - return _isHidden; - } - }; - - ProcessedRenderTreeNodeData() : - isValid(false), - layer( - CGRect(0.0, 0.0, 0.0, 0.0), - Vector2D(0.0, 0.0), - CATransform3D::identity(), - 1.0, - false, - false - ), - globalRect(CGRect(0.0, 0.0, 0.0, 0.0)), - globalTransform(CATransform3D::identity()), - drawContentDescendants(false), - isInvertedMatte(false) { - +public: + ProcessedRenderTreeNodeData() { } bool isValid = false; - LayerParams layer; - CGRect globalRect; - CATransform3D globalTransform; - int drawContentDescendants; - bool isInvertedMatte; + bool isInvertedMatte = false; }; class RenderableItem { @@ -135,19 +68,19 @@ public: struct Stroke { Color color; - double lineWidth = 0.0; + float lineWidth = 0.0; LineJoin lineJoin = LineJoin::Round; LineCap lineCap = LineCap::Square; - double dashPhase = 0.0; - std::vector dashPattern; + float dashPhase = 0.0; + std::vector dashPattern; Stroke( Color color_, - double lineWidth_, + float lineWidth_, LineJoin lineJoin_, LineCap lineCap_, - double dashPhase_, - std::vector dashPattern_ + float dashPhase_, + std::vector dashPattern_ ) : color(color_), lineWidth(lineWidth_), @@ -245,7 +178,7 @@ public: FillRule pathFillRule_, GradientType gradientType_, std::vector const &colors_, - std::vector const &locations_, + std::vector const &locations_, Vector2D const &start_, Vector2D const &end_, CGRect bounds_ @@ -301,7 +234,7 @@ public: FillRule pathFillRule; GradientType gradientType; std::vector colors; - std::vector locations; + std::vector locations; Vector2D start; Vector2D end; CGRect bounds; @@ -309,6 +242,17 @@ public: class RenderTreeNodeContentShadingVariant; +struct RenderTreeNodeContentPath { +public: + explicit RenderTreeNodeContentPath(BezierPath path_) : + path(path_) { + } + + BezierPath path; + CGRect bounds = CGRect(0.0, 0.0, 0.0, 0.0); + bool needsBoundsRecalculation = true; +}; + class RenderTreeNodeContentItem { public: enum class ShadingType { @@ -328,7 +272,7 @@ public: class SolidShading: public Shading { public: - SolidShading(Color const &color_, double opacity_) : + SolidShading(Color const &color_, float opacity_) : color(color_), opacity(opacity_) { } @@ -339,16 +283,16 @@ public: public: Color color; - double opacity = 0.0; + float opacity = 0.0; }; class GradientShading: public Shading { public: GradientShading( - double opacity_, + float opacity_, GradientType gradientType_, std::vector const &colors_, - std::vector const &locations_, + std::vector const &locations_, Vector2D const &start_, Vector2D const &end_ ) : @@ -365,31 +309,31 @@ public: } public: - double opacity = 0.0; + float opacity = 0.0; GradientType gradientType; std::vector colors; - std::vector locations; + std::vector locations; Vector2D start; Vector2D end; }; struct Stroke { std::shared_ptr shading; - double lineWidth = 0.0; + float lineWidth = 0.0; LineJoin lineJoin = LineJoin::Round; LineCap lineCap = LineCap::Square; - double miterLimit = 4.0; - double dashPhase = 0.0; - std::vector dashPattern; + float miterLimit = 4.0; + float dashPhase = 0.0; + std::vector dashPattern; Stroke( std::shared_ptr shading_, - double lineWidth_, + float lineWidth_, LineJoin lineJoin_, LineCap lineCap_, - double miterLimit_, - double dashPhase_, - std::vector dashPattern_ + float miterLimit_, + float dashPhase_, + std::vector dashPattern_ ) : shading(shading_), lineWidth(lineWidth_), @@ -420,13 +364,13 @@ public: public: bool isGroup = false; - CATransform3D transform = CATransform3D::identity(); - double alpha = 0.0; + Transform2D transform = Transform2D::identity(); + float alpha = 0.0; std::optional trimParams; - std::optional path; - CGRect pathBoundingBox = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr path; std::vector> shadings; std::vector> subItems; + int drawContentCount = 0; ProcessedRenderTreeNodeData renderData; }; @@ -447,18 +391,16 @@ public: class RenderTreeNode { public: RenderTreeNode( - CGRect bounds_, - Vector2D position_, - CATransform3D transform_, - double alpha_, + Vector2D size_, + Transform2D transform_, + float alpha_, bool masksToBounds_, bool isHidden_, std::vector> subnodes_, std::shared_ptr mask_, bool invertMask_ ) : - _bounds(bounds_), - _position(position_), + _size(size_), _transform(transform_), _alpha(alpha_), _masksToBounds(masksToBounds_), @@ -466,25 +408,24 @@ public: _subnodes(subnodes_), _mask(mask_), _invertMask(invertMask_) { + for (const auto &subnode : _subnodes) { + drawContentCount += subnode->drawContentCount; + } } ~RenderTreeNode() { } public: - CGRect const &bounds() const { - return _bounds; + Vector2D const &size() const { + return _size; } - Vector2D const &position() const { - return _position; - } - - CATransform3D const &transform() const { + Transform2D const &transform() const { return _transform; } - double alpha() const { + float alpha() const { return _alpha; } @@ -509,13 +450,13 @@ public: } public: - CGRect _bounds; - Vector2D _position; - CATransform3D _transform = CATransform3D::identity(); - double _alpha = 1.0; + Vector2D _size; + Transform2D _transform = Transform2D::identity(); + float _alpha = 1.0f; bool _masksToBounds = false; bool _isHidden = false; std::shared_ptr _contentItem; + int drawContentCount = 0; std::vector> _subnodes; std::shared_ptr _mask; bool _invertMask = false; diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h index c6f11afa4c..c3c6f9d809 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/ShapeAttributes.h @@ -37,12 +37,12 @@ enum class TrimType: int { }; struct TrimParams { - double start = 0.0; - double end = 0.0; - double offset = 0.0; + float start = 0.0; + float end = 0.0; + float offset = 0.0; TrimType type = TrimType::Simultaneously; - TrimParams(double start_, double end_, double offset_, TrimType type_) : + TrimParams(float start_, float end_, float offset_, TrimType type_) : start(start_), end(end_), offset(offset_), diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h index 19105a01dc..ffe5596872 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/Vectors.h @@ -8,6 +8,8 @@ #include +#import + namespace lottie { struct Vector1D { @@ -16,26 +18,26 @@ struct Vector1D { Array }; - explicit Vector1D(double value_) : + explicit Vector1D(float value_) : value(value_) { } explicit Vector1D(lottiejson11::Json const &json) noexcept(false); lottiejson11::Json toJson() const; - double value; + float value; - double distanceTo(Vector1D const &to) const { + float distanceTo(Vector1D const &to) const { return abs(to.value - value); } }; -double interpolate(double value, double to, double amount); +float interpolate(float value, float to, float amount); Vector1D interpolate( Vector1D const &from, Vector1D const &to, - double amount + float amount ); struct __attribute__((packed)) Vector2D { @@ -48,7 +50,7 @@ struct __attribute__((packed)) Vector2D { y(0.0) { } - explicit Vector2D(double x_, double y_) : + explicit Vector2D(float x_, float y_) : x(x_), y(y_) { } @@ -56,8 +58,8 @@ struct __attribute__((packed)) Vector2D { explicit Vector2D(lottiejson11::Json const &json) noexcept(false); lottiejson11::Json toJson() const; - double x; - double y; + float x; + float y; Vector2D operator+(Vector2D const &rhs) const { return Vector2D(x + rhs.x, y + rhs.y); @@ -67,7 +69,7 @@ struct __attribute__((packed)) Vector2D { return Vector2D(x - rhs.x, y - rhs.y); } - Vector2D operator*(double scalar) const { + Vector2D operator*(float scalar) const { return Vector2D(x * scalar, y * scalar); } @@ -83,44 +85,44 @@ struct __attribute__((packed)) Vector2D { return x == 0.0 && y == 0.0; } - double distanceTo(Vector2D const &to) const { + float distanceTo(Vector2D const &to) const { auto deltaX = to.x - x; auto deltaY = to.y - y; return sqrt(deltaX * deltaX + deltaY * deltaY); } bool colinear(Vector2D const &a, Vector2D const &b) const { - double area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); - double accuracy = 0.05; + float area = x * (a.y - b.y) + a.x * (b.y - y) + b.x * (y - a.y); + float accuracy = 0.05; if (area < accuracy && area > -accuracy) { return true; } return false; } - Vector2D pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const; + Vector2D pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, float amount) const; - Vector2D interpolate(Vector2D const &to, double amount) const; + Vector2D interpolate(Vector2D const &to, float amount) const; Vector2D interpolate( Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, - double amount, + float amount, int maxIterations = 3, int samples = 20, - double accuracy = 1.0 + float accuracy = 1.0 ) const; }; Vector2D interpolate( Vector2D const &from, Vector2D const &to, - double amount + float amount ); struct Vector3D { - explicit Vector3D(double x_, double y_, double z_) : + explicit Vector3D(float x_, float y_, float z_) : x(x_), y(y_), z(z_) { @@ -129,204 +131,101 @@ struct Vector3D { explicit Vector3D(lottiejson11::Json const &json) noexcept(false); lottiejson11::Json toJson() const; - double x = 0.0; - double y = 0.0; - double z = 0.0; + float x = 0.0; + float y = 0.0; + float z = 0.0; }; Vector3D interpolate( Vector3D const &from, Vector3D const &to, - double amount + float amount ); -inline double degreesToRadians(double value) { - return value * M_PI / 180.0; +inline float degreesToRadians(float value) { + return value * M_PI / 180.0f; } -inline double radiansToDegrees(double value) { - return value * 180.0 / M_PI; +inline float radiansToDegrees(float value) { + return value * 180.0f / M_PI; } -struct CATransform3D { - double m11, m12, m13, m14; - double m21, m22, m23, m24; - double m31, m32, m33, m34; - double m41, m42, m43, m44; - - CATransform3D( - double m11_, double m12_, double m13_, double m14_, - double m21_, double m22_, double m23_, double m24_, - double m31_, double m32_, double m33_, double m34_, - double m41_, double m42_, double m43_, double m44_ - ) : - m11(m11_), m12(m12_), m13(m13_), m14(m14_), - m21(m21_), m22(m22_), m23(m23_), m24(m24_), - m31(m31_), m32(m32_), m33(m33_), m34(m34_), - m41(m41_), m42(m42_), m43(m43_), m44(m44_) { - } - - bool operator==(CATransform3D const &rhs) const { - return m11 == rhs.m11 && m12 == rhs.m12 && m13 == rhs.m13 && m14 == rhs.m14 && - m21 == rhs.m21 && m22 == rhs.m22 && m23 == rhs.m23 && m24 == rhs.m24 && - m31 == rhs.m31 && m32 == rhs.m32 && m33 == rhs.m33 && m34 == rhs.m34 && - m41 == rhs.m41 && m42 == rhs.m42 && m43 == rhs.m43 && m44 == rhs.m44; - } - - bool operator!=(CATransform3D const &rhs) const { - return !(*this == rhs); - } - - inline bool isIdentity() const { - return m11 == 1.0 && m12 == 0.0 && m13 == 0.0 && m14 == 0.0 && - m21 == 0.0 && m22 == 1.0 && m23 == 0.0 && m24 == 0.0 && - m31 == 0.0 && m32 == 0.0 && m33 == 1.0 && m34 == 0.0 && - m41 == 0.0 && m42 == 0.0 && m43 == 0.0 && m44 == 1.0; - } - - static CATransform3D makeTranslation(double tx, double ty, double tz) { - return CATransform3D( - 1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - tx, ty, tz, 1 - ); - } - - static CATransform3D makeScale(double sx, double sy, double sz) { - return CATransform3D( - sx, 0, 0, 0, - 0, sy, 0, 0, - 0, 0, sz, 0, - 0, 0, 0, 1 - ); - } - - static CATransform3D makeRotation(double radians, double x, double y, double z); - - static CATransform3D makeSkew(double skew, double skewAxis) { - double mCos = cos(degreesToRadians(skewAxis)); - double mSin = sin(degreesToRadians(skewAxis)); - double aTan = tan(degreesToRadians(skew)); - - CATransform3D transform1( - mCos, - mSin, - 0.0, - 0.0, - -mSin, - mCos, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0 - ); - - CATransform3D transform2( - 1.0, - 0.0, - 0.0, - 0.0, - aTan, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0 - ); - - CATransform3D transform3( - mCos, - -mSin, - 0.0, - 0.0, - mSin, - mCos, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0, - 0.0, - 0.0, - 0.0, - 0.0, - 1.0 - ); - - return transform3 * transform2 * transform1; - } - - static CATransform3D makeTransform( - Vector2D const &anchor, - Vector2D const &position, - Vector2D const &scale, - double rotation, - std::optional skew, - std::optional skewAxis - ) { - CATransform3D result = CATransform3D::identity(); - if (skew.has_value() && skewAxis.has_value()) { - result = CATransform3D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); - } else { - result = CATransform3D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); - } - - return result; - } - - CATransform3D rotated(double degrees) const; - - CATransform3D translated(Vector2D const &translation) const; - - CATransform3D scaled(Vector2D const &scale) const; - - CATransform3D skewed(double skew, double skewAxis) const { - return CATransform3D::makeSkew(skew, skewAxis) * (*this); - } - - static CATransform3D const &identity() { +struct Transform2D { + static Transform2D const &identity() { return _identity; } - CATransform3D operator*(CATransform3D const &b) const; + explicit Transform2D(simd_float3x3 const &rows_) : + _rows(rows_) { + } - bool isInvertible() const; + Transform2D operator*(Transform2D const &other) const { + return Transform2D(simd_mul(other._rows, _rows)); + } - CATransform3D inverted() const; + bool isInvertible() const { + return simd_determinant(_rows) > 0.00000001; + } + Transform2D inverted() const { + return Transform2D(simd_inverse(_rows)); + } + + bool isIdentity() const { + return (*this) == identity(); + } + + static Transform2D makeTranslation(float tx, float ty); + static Transform2D makeScale(float sx, float sy); + static Transform2D makeRotation(float radians); + static Transform2D makeSkew(float skew, float skewAxis); + static Transform2D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis + ); + + Transform2D rotated(float degrees) const; + Transform2D translated(Vector2D const &translation) const; + Transform2D scaled(Vector2D const &scale) const; + Transform2D skewed(float skew, float skewAxis) const; + + bool operator==(Transform2D const &rhs) const { + return simd_equal(_rows, rhs._rows); + } + + bool operator!=(Transform2D const &rhs) const { + return !((*this) == rhs); + } + + simd_float3x3 const &rows() const { + return _rows; + } private: - static CATransform3D _identity; + static Transform2D _identity; + + simd_float3x3 _rows; }; struct CGRect { - explicit CGRect(double x_, double y_, double width_, double height_) : + explicit CGRect(float x_, float y_, float width_, float height_) : x(x_), y(y_), width(width_), height(height_) { } - double x = 0.0; - double y = 0.0; - double width = 0.0; - double height = 0.0; + float x = 0.0f; + float y = 0.0f; + float width = 0.0f; + float height = 0.0f; static CGRect veryLarge() { return CGRect( - -100000000.0, - -100000000.0, - 200000000.0, - 200000000.0 + -100000000.0f, + -100000000.0f, + 200000000.0f, + 200000000.0f ); } @@ -342,13 +241,13 @@ struct CGRect { return width <= 0.0 || height <= 0.0; } - CGRect insetBy(double dx, double dy) const { + CGRect insetBy(float dx, float dy) const { CGRect result = *this; result.x += dx; result.y += dy; - result.width -= dx * 2.0; - result.height -= dy * 2.0; + result.width -= dx * 2.0f; + result.height -= dy * 2.0f; return result; } @@ -359,18 +258,18 @@ struct CGRect { CGRect intersection(CGRect const &other) const; CGRect unionWith(CGRect const &other) const; - CGRect applyingTransform(CATransform3D const &transform) const; + CGRect applyingTransform(Transform2D const &transform) const; }; -inline bool isInRangeOrEqual(double value, double from, double to) { +inline bool isInRangeOrEqual(float value, float from, float to) { return from <= value && value <= to; } -inline bool isInRange(double value, double from, double to) { +inline bool isInRange(float value, float from, float to) { return from < value && value < to; } -double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3); +float cubicBezierInterpolate(float value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3); } diff --git a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h index d706e3643b..ef77ad9966 100644 --- a/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h +++ b/submodules/TelegramUI/Components/LottieCpp/PublicHeaders/LottieCpp/VectorsCocoa.h @@ -7,8 +7,8 @@ namespace lottie { -::CATransform3D nativeTransform(CATransform3D const &value); -CATransform3D fromNativeTransform(::CATransform3D const &value); +::CATransform3D nativeTransform(Transform2D const &value); +Transform2D fromNativeTransform(::CATransform3D const &value); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp index db9e76ffc7..53f850f090 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.cpp @@ -1,13 +1,10 @@ #include "CompositionLayer.hpp" -#include "Lottie/Public/Primitives/RenderTree.hpp" - namespace lottie { InvertedMatteLayer::InvertedMatteLayer(std::shared_ptr inputMatte) : _inputMatte(inputMatte) { - setBounds(inputMatte->bounds()); - setNeedsDisplay(true); + setSize(inputMatte->size()); addSublayer(_inputMatte); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp index c8a97b55dd..e82b9c8b33 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/CompositionLayer.hpp @@ -57,7 +57,7 @@ public: _childKeypaths.push_back(_transformNode->transformProperties()); - _contentsLayer->setBounds(CGRect(0.0, 0.0, size.x, size.y)); + _contentsLayer->setSize(size); if (layer->blendMode.has_value() && layer->blendMode.value() != BlendMode::Normal) { setCompositingFilter(layer->blendMode); @@ -82,14 +82,16 @@ public: return _contentsLayer; } - void displayWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { - _transformNode->updateTree(frame, forceUpdates); - + void displayWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { bool layerVisible = isInRangeOrEqual(frame, _inFrame, _outFrame); - _contentsLayer->setTransform(_transformNode->globalTransform()); - _contentsLayer->setOpacity(_transformNode->opacity()); - _contentsLayer->setIsHidden(!layerVisible); + if (_transformNode->updateTree(frame, forceUpdates) || _contentsLayer->isHidden() != !layerVisible) { + _contentsLayer->setTransform(_transformNode->globalTransform()); + _contentsLayer->setOpacity(_transformNode->opacity()); + _contentsLayer->setIsHidden(!layerVisible); + + updateContentsLayerParameters(); + } /// Only update contents if current time is within the layers time bounds. if (layerVisible) { @@ -100,7 +102,10 @@ public: } } - virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { + virtual void updateContentsLayerParameters() { + } + + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { /// To be overridden by subclass } @@ -138,16 +143,16 @@ public: return _matteType; } - double inFrame() const { + float inFrame() const { return _inFrame; } - double outFrame() const { + float outFrame() const { return _outFrame; } - double startFrame() const { + float startFrame() const { return _startFrame; } - double timeStretch() const { + float timeStretch() const { return _timeStretch; } @@ -155,9 +160,6 @@ public: return nullptr; } - virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) { - } - public: std::shared_ptr const transformNode() const { return _transformNode; @@ -172,10 +174,10 @@ private: std::shared_ptr _maskLayer; - double _inFrame = 0.0; - double _outFrame = 0.0; - double _startFrame = 0.0; - double _timeStretch = 0.0; + float _inFrame = 0.0; + float _outFrame = 0.0; + float _startFrame = 0.0; + float _timeStretch = 0.0; // MARK: Keypath Searchable diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp index 9c4e46f41b..95bdb3f093 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/MaskContainerLayer.hpp @@ -106,7 +106,7 @@ public: virtual ~MaskLayer() = default; - void updateWithFrame(double frame, bool forceUpdates) { + void updateWithFrame(float frame, bool forceUpdates) { if (_properties.opacity()->needsUpdate(frame) || forceUpdates) { _properties.opacity()->update(frame); setOpacity(_properties.opacity()->value().value); @@ -163,7 +163,7 @@ public: // MARK: Internal - void updateWithFrame(double frame, bool forceUpdates) { + void updateWithFrame(float frame, bool forceUpdates) { for (const auto &maskLayer : _maskLayers) { maskLayer->updateWithFrame(frame, forceUpdates); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp index aff1d21f8b..c170f21a58 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/PreCompositionLayer.hpp @@ -23,16 +23,16 @@ public: std::shared_ptr const &textProvider, std::shared_ptr const &fontProvider, std::shared_ptr const &assetLibrary, - double frameRate + float frameRate ) : CompositionLayer(precomp, Vector2D(precomp->width, precomp->height)) { if (precomp->timeRemapping) { _remappingNode = std::make_shared>(std::make_shared>(precomp->timeRemapping->keyframes)); } _frameRate = frameRate; - setBounds(CGRect(0.0, 0.0, precomp->width, precomp->height)); + setSize(Vector2D(precomp->width, precomp->height)); contentsLayer()->setMasksToBounds(true); - contentsLayer()->setBounds(bounds()); + contentsLayer()->setSize(size()); auto layers = initializeCompositionLayers( asset.layers, @@ -49,7 +49,7 @@ public: for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { std::shared_ptr layer = *layerIt; - layer->setBounds(bounds()); + layer->setSize(size()); _animationLayers.push_back(layer); if (layer->isImageCompositionLayer()) { @@ -86,8 +86,8 @@ public: return result; } - virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { - double localFrame = 0.0; + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { + float localFrame = 0.0; if (_remappingNode) { _remappingNode->update(frame); localFrame = _remappingNode->value().value * _frameRate; @@ -102,42 +102,6 @@ public: virtual std::shared_ptr renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override { if (!_renderTreeNode) { - _contentsTreeNode = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), - Vector2D(0.0, 0.0), - CATransform3D::identity(), - 1.0, - false, - false, - std::vector>(), - nullptr, - false - ); - - std::vector> subnodes; - subnodes.push_back(_contentsTreeNode); - - std::shared_ptr maskNode; - bool invertMask = false; - if (_matteLayer) { - maskNode = _matteLayer->renderTreeNode(boundingBoxContext); - if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { - invertMask = true; - } - } - - _renderTreeNode = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), - Vector2D(0.0, 0.0), - CATransform3D::identity(), - 1.0, - false, - false, - subnodes, - maskNode, - invertMask - ); - std::vector> renderTreeSubnodes; for (const auto &animationLayer : _animationLayers) { bool found = false; @@ -157,9 +121,8 @@ public: std::vector> renderTreeValue; auto renderTreeContentItem = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), Vector2D(0.0, 0.0), - CATransform3D::identity(), + Transform2D::identity(), 1.0, false, false, @@ -171,53 +134,61 @@ public: renderTreeValue.push_back(renderTreeContentItem); } - _contentsTreeNode->_subnodes = renderTreeValue; - } - - return _renderTreeNode; - } - - virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) override { - if (_matteLayer) { - _matteLayer->updateRenderTree(boundingBoxContext); - } - - for (const auto &animationLayer : _animationLayers) { - bool found = false; - for (const auto &sublayer : contentsLayer()->sublayers()) { - if (animationLayer == sublayer) { - found = true; - break; + _contentsTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + renderTreeValue, + nullptr, + false + ); + + std::vector> subnodes; + subnodes.push_back(_contentsTreeNode); + + std::shared_ptr maskNode; + bool invertMask = false; + if (_matteLayer) { + maskNode = _matteLayer->renderTreeNode(boundingBoxContext); + if (maskNode && _matteType.has_value() && _matteType.value() == MatteType::Invert) { + invertMask = true; } } - if (found) { - animationLayer->updateRenderTree(boundingBoxContext); - } + + _renderTreeNode = std::make_shared( + Vector2D(0.0, 0.0), + Transform2D::identity(), + 1.0, + false, + false, + subnodes, + maskNode, + invertMask + ); } - assert(opacity() == 1.0); - assert(!isHidden()); - assert(!masksToBounds()); - assert(transform().isIdentity()); - assert(position() == Vector2D::Zero()); - - _contentsTreeNode->_bounds = _contentsLayer->bounds(); - _contentsTreeNode->_position = _contentsLayer->position(); - _contentsTreeNode->_transform = _contentsLayer->transform(); - _contentsTreeNode->_alpha = _contentsLayer->opacity(); + _contentsTreeNode->_size = _contentsLayer->size(); _contentsTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); - _contentsTreeNode->_isHidden = _contentsLayer->isHidden(); - _renderTreeNode->_bounds = bounds(); - _renderTreeNode->_position = position(); + _renderTreeNode->_size = size(); _renderTreeNode->_transform = transform(); _renderTreeNode->_alpha = opacity(); _renderTreeNode->_masksToBounds = masksToBounds(); _renderTreeNode->_isHidden = isHidden(); + + return _renderTreeNode; + } + + virtual void updateContentsLayerParameters() override { + _contentsTreeNode->_transform = _contentsLayer->transform(); + _contentsTreeNode->_alpha = _contentsLayer->opacity(); + _contentsTreeNode->_isHidden = _contentsLayer->isHidden(); } private: - double _frameRate = 0.0; + float _frameRate = 0.0; std::shared_ptr> _remappingNode; std::vector> _animationLayers; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp index e9b2afb90e..e7ad955e0f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.cpp @@ -74,7 +74,7 @@ public: Color colorValue = Color(0.0, 0.0, 0.0, 0.0); KeyframeInterpolator opacity; - double opacityValue = 0.0; + float opacityValue = 0.0; std::shared_ptr _fill; }; @@ -93,7 +93,7 @@ public: 0.0, gradientType, std::vector(), - std::vector(), + std::vector(), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0) ); @@ -130,7 +130,7 @@ public: if (hasUpdates) { std::vector colors; - std::vector locations; + std::vector locations; getGradientParameters(numberOfColors, colorsValue, colors, locations); RenderTreeNodeContentItem::GradientShading *gradient = ((RenderTreeNodeContentItem::GradientShading *)_fill->shading.get()); @@ -161,7 +161,7 @@ public: Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator opacity; - double opacityValue = 0.0; + float opacityValue = 0.0; std::shared_ptr _fill; }; @@ -202,7 +202,7 @@ public: lineCap, miterLimit, 0.0, - std::vector() + std::vector() ); } @@ -257,7 +257,7 @@ public: _stroke->lineWidth = widthValue; _stroke->dashPhase = hasNonZeroDashes ? dashPhaseValue : 0.0; - _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); + _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); } } @@ -268,22 +268,22 @@ public: private: LineJoin lineJoin; LineCap lineCap; - double miterLimit = 4.0; + float miterLimit = 4.0; KeyframeInterpolator color; Color colorValue = Color(0.0, 0.0, 0.0, 0.0); KeyframeInterpolator opacity; - double opacityValue = 0.0; + float opacityValue = 0.0; KeyframeInterpolator width; - double widthValue = 0.0; + float widthValue = 0.0; std::unique_ptr dashPattern; DashPattern dashPatternValue = DashPattern({}); std::unique_ptr> dashPhase; - double dashPhaseValue = 0.0; + float dashPhaseValue = 0.0; std::shared_ptr _stroke; }; @@ -314,7 +314,7 @@ public: 0.0, gradientType, std::vector(), - std::vector(), + std::vector(), Vector2D(0.0, 0.0), Vector2D(0.0, 0.0) ); @@ -325,7 +325,7 @@ public: lineCap, miterLimit, 0.0, - std::vector() + std::vector() ); } @@ -385,7 +385,7 @@ public: } std::vector colors; - std::vector locations; + std::vector locations; getGradientParameters(numberOfColors, colorsValue, colors, locations); RenderTreeNodeContentItem::GradientShading *gradient = ((RenderTreeNodeContentItem::GradientShading *)_stroke->shading.get()); @@ -397,7 +397,7 @@ public: _stroke->lineWidth = widthValue; _stroke->dashPhase = hasNonZeroDashes ? dashPhaseValue : 0.0; - _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); + _stroke->dashPattern = hasNonZeroDashes ? dashPatternValue.values : std::vector(); } } @@ -408,7 +408,7 @@ public: private: LineJoin lineJoin; LineCap lineCap; - double miterLimit = 4.0; + float miterLimit = 4.0; int numberOfColors = 0; GradientType gradientType; @@ -423,16 +423,16 @@ public: Vector3D endPointValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator opacity; - double opacityValue = 0.0; + float opacityValue = 0.0; KeyframeInterpolator width; - double widthValue = 0.0; + float widthValue = 0.0; std::unique_ptr dashPattern; DashPattern dashPatternValue = DashPattern({}); std::unique_ptr> dashPhase; - double dashPhaseValue = 0.0; + float dashPhaseValue = 0.0; std::shared_ptr _stroke; }; @@ -461,12 +461,12 @@ public: } TrimParams trimParams() { - double resolvedStartValue = startValue * 0.01; - double resolvedEndValue = endValue * 0.01; - double resolvedStart = std::min(resolvedStartValue, resolvedEndValue); - double resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); + float resolvedStartValue = startValue * 0.01; + float resolvedEndValue = endValue * 0.01; + float resolvedStart = std::min(resolvedStartValue, resolvedEndValue); + float resolvedEnd = std::max(resolvedStartValue, resolvedEndValue); - double resolvedOffset = fmod(offsetValue, 360.0) / 360.0; + float resolvedOffset = fmod(offsetValue, 360.0) / 360.0; return TrimParams(resolvedStart, resolvedEnd, resolvedOffset, type); } @@ -475,13 +475,13 @@ public: TrimType type; KeyframeInterpolator start; - double startValue = 0.0; + float startValue = 0.0; KeyframeInterpolator end; - double endValue = 0.0; + float endValue = 0.0; KeyframeInterpolator offset; - double offsetValue = 0.0; + float offsetValue = 0.0; }; struct ShadingVariant { @@ -492,9 +492,9 @@ public: struct TransformedPath { BezierPath path; - CATransform3D transform; + Transform2D transform; - TransformedPath(BezierPath const &path_, CATransform3D const &transform_) : + TransformedPath(BezierPath const &path_, Transform2D const &transform_) : path(path_), transform(transform_) { } @@ -506,60 +506,44 @@ public: } virtual ~PathOutput() = default; - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) = 0; - virtual BezierPath const *currentPath() = 0; - virtual CGRect const ¤tPathBounds() = 0; + virtual void update(AnimationFrameTime frameTime) = 0; + virtual std::shared_ptr ¤tPath() = 0; }; class StaticPathOutput : public PathOutput { public: explicit StaticPathOutput(BezierPath const &path) : - resolvedPath(path) { + resolvedPath(std::make_shared(path)) { } - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override { - if (!isPathBoundsResolved) { - resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath); - isPathBoundsResolved = true; - } + virtual void update(AnimationFrameTime frameTime) override { } - virtual BezierPath const *currentPath() override { - return &resolvedPath; - } - - virtual CGRect const ¤tPathBounds() override { - return resolvedPathBounds; + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; } private: - BezierPath resolvedPath; - - bool isPathBoundsResolved = false; - CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr resolvedPath; }; class ShapePathOutput : public PathOutput { public: explicit ShapePathOutput(Shape const &shape) : - path(shape.path.keyframes) { + path(shape.path.keyframes), + resolvedPath(std::make_shared(BezierPath())) { } - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override { + virtual void update(AnimationFrameTime frameTime) override { if (!hasValidData || path.hasUpdate(frameTime)) { - path.update(frameTime, resolvedPath); - resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath); + path.update(frameTime, resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; } - hasValidData = true; } - virtual BezierPath const *currentPath() override { - return &resolvedPath; - } - - virtual CGRect const ¤tPathBounds() override { - return resolvedPathBounds; + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; } private: @@ -567,8 +551,7 @@ public: BezierPathKeyframeInterpolator path; - BezierPath resolvedPath; - CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr resolvedPath; }; class RectanglePathOutput : public PathOutput { @@ -577,10 +560,11 @@ public: direction(rectangle.direction.value_or(PathDirection::Clockwise)), position(rectangle.position.keyframes), size(rectangle.size.keyframes), - cornerRadius(rectangle.cornerRadius.keyframes) { + cornerRadius(rectangle.cornerRadius.keyframes), + resolvedPath(std::make_shared(BezierPath())) { } - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override { + virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { @@ -597,19 +581,15 @@ public: } if (hasUpdates) { - ValueInterpolator::setInplace(makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction), resolvedPath); - resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath); + ValueInterpolator::setInplace(makeRectangleBezierPath(Vector2D(positionValue.x, positionValue.y), Vector2D(sizeValue.x, sizeValue.y), cornerRadiusValue, direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; } hasValidData = true; } - virtual BezierPath const *currentPath() override { - return &resolvedPath; - } - - virtual CGRect const ¤tPathBounds() override { - return resolvedPathBounds; + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; } private: @@ -624,10 +604,9 @@ public: Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator cornerRadius; - double cornerRadiusValue = 0.0; + float cornerRadiusValue = 0.0; - BezierPath resolvedPath; - CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr resolvedPath; }; class EllipsePathOutput : public PathOutput { @@ -635,10 +614,11 @@ public: explicit EllipsePathOutput(Ellipse const &ellipse) : direction(ellipse.direction.value_or(PathDirection::Clockwise)), position(ellipse.position.keyframes), - size(ellipse.size.keyframes) { + size(ellipse.size.keyframes), + resolvedPath(std::make_shared(BezierPath())) { } - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override { + virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { @@ -651,19 +631,15 @@ public: } if (hasUpdates) { - ValueInterpolator::setInplace(makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction), resolvedPath); - resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath); + ValueInterpolator::setInplace(makeEllipseBezierPath(Vector2D(sizeValue.x, sizeValue.y), Vector2D(positionValue.x, positionValue.y), direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; } hasValidData = true; } - virtual BezierPath const *currentPath() override { - return &resolvedPath; - } - - virtual CGRect const ¤tPathBounds() override { - return resolvedPathBounds; + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; } private: @@ -677,8 +653,7 @@ public: KeyframeInterpolator size; Vector3D sizeValue = Vector3D(0.0, 0.0, 0.0); - BezierPath resolvedPath; - CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr resolvedPath; }; class StarPathOutput : public PathOutput { @@ -689,7 +664,8 @@ public: outerRadius(star.outerRadius.keyframes), outerRoundedness(star.outerRoundness.keyframes), rotation(star.rotation.keyframes), - points(star.points.keyframes) { + points(star.points.keyframes), + resolvedPath(std::make_shared(BezierPath())) { if (star.innerRadius.has_value()) { innerRadius = std::make_unique>(std::make_shared>(star.innerRadius->keyframes)); } else { @@ -703,7 +679,7 @@ public: } } - virtual void update(AnimationFrameTime frameTime, BezierPathsBoundingBoxContext &boundingBoxContext) override { + virtual void update(AnimationFrameTime frameTime) override { bool hasUpdates = false; if (!hasValidData || position.hasUpdate(frameTime)) { @@ -744,19 +720,15 @@ public: } if (hasUpdates) { - ValueInterpolator::setInplace(makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction), resolvedPath); - resolvedPathBounds = bezierPathsBoundingBoxParallel(boundingBoxContext, resolvedPath); + ValueInterpolator::setInplace(makeStarBezierPath(Vector2D(positionValue.x, positionValue.y), outerRadiusValue, innerRadiusValue, outerRoundednessValue, innerRoundednessValue, pointsValue, rotationValue, direction), resolvedPath->path); + resolvedPath->needsBoundsRecalculation = true; } hasValidData = true; } - virtual BezierPath const *currentPath() override { - return &resolvedPath; - } - - virtual CGRect const ¤tPathBounds() override { - return resolvedPathBounds; + virtual std::shared_ptr ¤tPath() override { + return resolvedPath; } private: @@ -768,25 +740,24 @@ public: Vector3D positionValue = Vector3D(0.0, 0.0, 0.0); KeyframeInterpolator outerRadius; - double outerRadiusValue = 0.0; + float outerRadiusValue = 0.0; KeyframeInterpolator outerRoundedness; - double outerRoundednessValue = 0.0; + float outerRoundednessValue = 0.0; std::unique_ptr> innerRadius; - double innerRadiusValue = 0.0; + float innerRadiusValue = 0.0; std::unique_ptr> innerRoundedness; - double innerRoundednessValue = 0.0; + float innerRoundednessValue = 0.0; KeyframeInterpolator rotation; - double rotationValue = 0.0; + float rotationValue = 0.0; KeyframeInterpolator points; - double pointsValue = 0.0; + float pointsValue = 0.0; - BezierPath resolvedPath; - CGRect resolvedPathBounds = CGRect(0.0, 0.0, 0.0, 0.0); + std::shared_ptr resolvedPath; }; class TransformOutput { @@ -861,17 +832,17 @@ public: scaleValue = _scale->value(frameTime); } - double rotationValue = 0.0; + float rotationValue = 0.0; if (_rotation) { rotationValue = _rotation->value(frameTime).value; } - double skewValue = 0.0; + float skewValue = 0.0; if (_skew) { skewValue = _skew->value(frameTime).value; } - double skewAxisValue = 0.0; + float skewAxisValue = 0.0; if (_skewAxis) { skewAxisValue = _skewAxis->value(frameTime).value; } @@ -882,17 +853,17 @@ public: _opacityValue = 1.0; } - _transformValue = CATransform3D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); + _transformValue = Transform2D::identity().translated(Vector2D(positionValue.x, positionValue.y)).rotated(rotationValue).skewed(-skewValue, skewAxisValue).scaled(Vector2D(scaleValue.x * 0.01, scaleValue.y * 0.01)).translated(Vector2D(-anchorValue.x, -anchorValue.y)); hasValidData = true; } } - CATransform3D const &transform() { + Transform2D const &transform() { return _transformValue; } - double opacity() { + float opacity() { return _opacityValue; } @@ -907,8 +878,8 @@ public: std::unique_ptr> _skewAxis; std::unique_ptr> _opacity; - CATransform3D _transformValue = CATransform3D::identity(); - double _opacityValue = 1.0; + Transform2D _transformValue = Transform2D::identity(); + float _opacityValue = 1.0; }; class ContentItem { @@ -939,11 +910,11 @@ public: std::shared_ptr _contentItem; private: - std::vector collectPaths(size_t subItemLimit, CATransform3D const &parentTransform, bool skipApplyTransform) { + std::vector collectPaths(size_t subItemLimit, Transform2D const &parentTransform, bool skipApplyTransform) { std::vector mappedPaths; //TODO:remove skipApplyTransform - CATransform3D effectiveTransform = parentTransform; + Transform2D effectiveTransform = parentTransform; if (!skipApplyTransform && isGroup && transform) { effectiveTransform = transform->transform() * effectiveTransform; } @@ -951,7 +922,7 @@ public: size_t maxSubitem = std::min(subItems.size(), subItemLimit); if (_contentItem->path) { - mappedPaths.emplace_back(_contentItem->path.value(), effectiveTransform); + mappedPaths.emplace_back(_contentItem->path->path, effectiveTransform); } for (size_t i = 0; i < maxSubitem; i++) { @@ -971,7 +942,7 @@ public: } CompoundBezierPath trimmedPath = trimCompoundPath(tempPath, currentTrim->start, currentTrim->end, currentTrim->offset, currentTrim->type); for (auto &path : trimmedPath.paths) { - mappedPaths.emplace_back(path, CATransform3D::identity()); + mappedPaths.emplace_back(path, Transform2D::identity()); } } else { for (auto &path : subItemPaths) { @@ -1012,7 +983,7 @@ public: _contentItem->isGroup = isGroup; if (path) { - _contentItem->path = *path->currentPath(); + _contentItem->path = path->currentPath(); } if (!shadings.empty()) { @@ -1023,6 +994,8 @@ public: continue; } + _contentItem->drawContentCount++; + auto itemShadingVariant = std::make_shared(); if (shadingVariant.fill) { itemShadingVariant->fill = shadingVariant.fill->fill(); @@ -1040,6 +1013,7 @@ public: std::vector> subItemNodes; for (const auto &subItem : subItems) { subItem->initializeRenderChildren(); + _contentItem->drawContentCount += subItem->_contentItem->drawContentCount; _contentItem->subItems.push_back(subItem->_contentItem); } } @@ -1052,8 +1026,7 @@ public: } if (path) { - path->update(frameTime, boundingBoxContext); - _contentItem->pathBoundingBox = path->currentPathBounds(); + path->update(frameTime); } for (const auto &trim : trims) { trim->update(frameTime); @@ -1088,8 +1061,8 @@ public: } void updateContents(std::optional parentTrim) { - CATransform3D containerTransform = CATransform3D::identity(); - double containerOpacity = 1.0; + Transform2D containerTransform = Transform2D::identity(); + float containerOpacity = 1.0; if (transform) { containerTransform = transform->transform(); containerOpacity = transform->opacity(); @@ -1116,7 +1089,7 @@ public: if (parentTrim) { CompoundBezierPath compoundPath; - auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true); + auto paths = collectPaths(shadingVariant.subItemLimit, Transform2D::identity(), true); for (const auto &path : paths) { compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); } @@ -1131,7 +1104,7 @@ public: } else { if (hasTrims()) { CompoundBezierPath compoundPath; - auto paths = collectPaths(shadingVariant.subItemLimit, CATransform3D::identity(), true); + auto paths = collectPaths(shadingVariant.subItemLimit, Transform2D::identity(), true); for (const auto &path : paths) { compoundPath.appendPath(path.path.copyUsingTransform(path.transform)); } @@ -1333,7 +1306,7 @@ CompositionLayer(solidLayer, Vector2D::Zero()) { _contentTree = std::make_shared(solidLayer); } -void ShapeCompositionLayer::displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { +void ShapeCompositionLayer::displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) { _frameTime = frame; _frameTimeInitialized = true; _contentTree->itemTree->updateFrame(_frameTime, boundingBoxContext); @@ -1350,9 +1323,8 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode(BezierPath if (!_renderTreeNode) { _contentRenderTreeNode = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), Vector2D(0.0, 0.0), - CATransform3D::identity(), + Transform2D::identity(), 1.0, false, false, @@ -1361,9 +1333,9 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode(BezierPath false ); _contentRenderTreeNode->_contentItem = _contentTree->itemTree->_contentItem; + _contentRenderTreeNode->drawContentCount = _contentTree->itemTree->_contentItem->drawContentCount; std::vector> subnodes; - //subnodes.push_back(_contentTree->itemTree->renderTree()); subnodes.push_back(_contentRenderTreeNode); std::shared_ptr maskNode; @@ -1376,9 +1348,8 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode(BezierPath } _renderTreeNode = std::make_shared( - CGRect(0.0, 0.0, 0.0, 0.0), Vector2D(0.0, 0.0), - CATransform3D::identity(), + Transform2D::identity(), 1.0, false, false, @@ -1388,27 +1359,20 @@ std::shared_ptr ShapeCompositionLayer::renderTreeNode(BezierPath ); } + _contentRenderTreeNode->_size = _contentsLayer->size(); + _contentRenderTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); + + _renderTreeNode->_masksToBounds = masksToBounds(); + + _renderTreeNode->_size = size(); + return _renderTreeNode; } -void ShapeCompositionLayer::updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) { - if (_matteLayer) { - _matteLayer->updateRenderTree(boundingBoxContext); - } - - _contentRenderTreeNode->_bounds = _contentsLayer->bounds(); - _contentRenderTreeNode->_position = _contentsLayer->position(); +void ShapeCompositionLayer::updateContentsLayerParameters() { _contentRenderTreeNode->_transform = _contentsLayer->transform(); _contentRenderTreeNode->_alpha = _contentsLayer->opacity(); - _contentRenderTreeNode->_masksToBounds = _contentsLayer->masksToBounds(); _contentRenderTreeNode->_isHidden = _contentsLayer->isHidden(); - - _renderTreeNode->_bounds = bounds(); - _renderTreeNode->_position = position(); - _renderTreeNode->_transform = transform(); - _renderTreeNode->_alpha = opacity(); - _renderTreeNode->_masksToBounds = masksToBounds(); - _renderTreeNode->_isHidden = isHidden(); } } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp index 27fed46a4e..8aff5c77a6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeCompositionLayer.hpp @@ -16,9 +16,10 @@ public: ShapeCompositionLayer(std::shared_ptr const &shapeLayer); ShapeCompositionLayer(std::shared_ptr const &solidLayer); - virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override; + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override; virtual std::shared_ptr renderTreeNode(BezierPathsBoundingBoxContext &boundingBoxContext) override; - virtual void updateRenderTree(BezierPathsBoundingBoxContext &boundingBoxContext) override; + void initializeContentsLayerParameters(); + virtual void updateContentsLayerParameters() override; private: std::shared_ptr _contentTree; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp index cd129d2606..71fc3cc1d7 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.cpp @@ -7,7 +7,7 @@ BezierPath makeEllipseBezierPath( Vector2D const ¢er, PathDirection direction ) { - const double ControlPointConstant = 0.55228; + const float ControlPointConstant = 0.55228; Vector2D half = size * 0.5; if (direction == PathDirection::CounterClockwise) { @@ -51,13 +51,13 @@ BezierPath makeEllipseBezierPath( BezierPath makeRectangleBezierPath( Vector2D const &position, Vector2D const &inputSize, - double cornerRadius, + float cornerRadius, PathDirection direction ) { - const double ControlPointConstant = 0.55228; + const float ControlPointConstant = 0.55228; Vector2D size = inputSize * 0.5; - double radius = std::min(std::min(cornerRadius, size.x), size.y); + float radius = std::min(std::min(cornerRadius, (float)size.x), (float)size.y); BezierPath bezierPath; std::vector points; @@ -97,7 +97,7 @@ BezierPath makeRectangleBezierPath( .translated(position) }; } else { - double controlPoint = radius * ControlPointConstant; + float controlPoint = radius * ControlPointConstant; points = { /// Lead In CurveVertex::absolute( @@ -184,28 +184,28 @@ BezierPath makeRectangleBezierPath( } /// Magic number needed for building path data -static constexpr double StarNodePolystarConstant = 0.47829; +static constexpr float StarNodePolystarConstant = 0.47829; BezierPath makeStarBezierPath( Vector2D const &position, - double outerRadius, - double innerRadius, - double inputOuterRoundedness, - double inputInnerRoundedness, - double numberOfPoints, - double rotation, + float outerRadius, + float innerRadius, + float inputOuterRoundedness, + float inputInnerRoundedness, + float numberOfPoints, + float rotation, PathDirection direction ) { - double currentAngle = degreesToRadians(rotation - 90.0); - double anglePerPoint = (2.0 * M_PI) / numberOfPoints; - double halfAnglePerPoint = anglePerPoint / 2.0; - double partialPointAmount = numberOfPoints - floor(numberOfPoints); - double outerRoundedness = inputOuterRoundedness * 0.01; - double innerRoundedness = inputInnerRoundedness * 0.01; + float currentAngle = degreesToRadians(rotation - 90.0); + float anglePerPoint = (2.0 * M_PI) / numberOfPoints; + float halfAnglePerPoint = anglePerPoint / 2.0; + float partialPointAmount = numberOfPoints - floor(numberOfPoints); + float outerRoundedness = inputOuterRoundedness * 0.01; + float innerRoundedness = inputInnerRoundedness * 0.01; Vector2D point = Vector2D::Zero(); - double partialPointRadius = 0.0; + float partialPointRadius = 0.0; if (partialPointAmount != 0.0) { currentAngle += halfAnglePerPoint * (1 - partialPointAmount); partialPointRadius = innerRadius + partialPointAmount * (outerRadius - innerRadius); @@ -225,8 +225,8 @@ BezierPath makeStarBezierPath( bool longSegment = false; int numPoints = (int)(ceil(numberOfPoints) * 2.0); for (int i = 0; i < numPoints; i++) { - double radius = longSegment ? outerRadius : innerRadius; - double dTheta = halfAnglePerPoint; + float radius = longSegment ? outerRadius : innerRadius; + float dTheta = halfAnglePerPoint; if (partialPointRadius != 0.0 && i == numPoints - 2) { dTheta = anglePerPoint * partialPointAmount / 2; } @@ -240,18 +240,18 @@ BezierPath makeStarBezierPath( if (innerRoundedness == 0.0 && outerRoundedness == 0.0) { vertices.push_back(CurveVertex::relative(point + position, Vector2D::Zero(), Vector2D::Zero())); } else { - double cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0); - double cp1Dx = cos(cp1Theta); - double cp1Dy = sin(cp1Theta); + float cp1Theta = (atan2(previousPoint.y, previousPoint.x) - M_PI / 2.0); + float cp1Dx = cos(cp1Theta); + float cp1Dy = sin(cp1Theta); - double cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0); - double cp2Dx = cos(cp2Theta); - double cp2Dy = sin(cp2Theta); + float cp2Theta = (atan2(point.y, point.x) - M_PI / 2.0); + float cp2Dx = cos(cp2Theta); + float cp2Dy = sin(cp2Theta); - double cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness; - double cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness; - double cp1Radius = longSegment ? innerRadius : outerRadius; - double cp2Radius = longSegment ? outerRadius : innerRadius; + float cp1Roundedness = longSegment ? innerRoundedness : outerRoundedness; + float cp2Roundedness = longSegment ? outerRoundedness : innerRoundedness; + float cp1Radius = longSegment ? innerRadius : outerRadius; + float cp2Radius = longSegment ? outerRadius : innerRadius; Vector2D cp1( cp1Radius * cp1Roundedness * StarNodePolystarConstant * cp1Dx, @@ -295,7 +295,7 @@ BezierPath makeStarBezierPath( return path; } -CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, double end, double offset, TrimType type) { +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type) { /// No need to trim, it's a full path if (start == 0.0 && end == 1.0) { return sourcePath; @@ -328,8 +328,8 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, /// Brace yourself for the below code. /// Normalize lengths with offset. - double startPosition = fmod(start + offset, 1.0); - double endPosition = fmod(end + offset, 1.0); + float startPosition = fmod(start + offset, 1.0); + float endPosition = fmod(end + offset, 1.0); if (startPosition < 0.0) { startPosition = 1.0 + startPosition; @@ -346,15 +346,15 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, } /// First get the total length of all paths. - double totalLength = 0.0; + float totalLength = 0.0; for (auto &upstreamPath : sourcePath.paths) { totalLength += upstreamPath.length(); } /// Now determine the start and end cut lengths - double startLength = startPosition * totalLength; - double endLength = endPosition * totalLength; - double pathStart = 0.0; + float startLength = startPosition * totalLength; + float endLength = endPosition * totalLength; + float pathStart = 0.0; CompoundBezierPath result; @@ -367,12 +367,12 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, // pathStart|=======E----------------------|pathEnd // Cut path components, removing after end. - double pathCutLength = endLength - pathStart; - double subpathStart = 0.0; - double subpathEnd = subpathStart + pathContainer.length(); + float pathCutLength = endLength - pathStart; + float subpathStart = 0.0; + float subpathEnd = subpathStart + pathContainer.length(); if (pathCutLength < subpathEnd) { /// This is the subpath that needs to be cut. - double cutLength = pathCutLength - subpathStart; + float cutLength = pathCutLength - subpathStart; CompoundBezierPath tempPath; tempPath.appendPath(pathContainer); @@ -395,14 +395,14 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, // // Cut path components, removing before beginning. - double pathCutLength = startLength - pathStart; + float pathCutLength = startLength - pathStart; // Clear paths from container - double subpathStart = 0.0; - double subpathEnd = subpathStart + pathContainer.length(); + float subpathStart = 0.0; + float subpathEnd = subpathStart + pathContainer.length(); if (subpathStart < pathCutLength && pathCutLength < subpathEnd) { /// This is the subpath that needs to be cut. - double cutLength = pathCutLength - subpathStart; + float cutLength = pathCutLength - subpathStart; CompoundBezierPath tempPath; tempPath.appendPath(pathContainer); auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); @@ -420,12 +420,12 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, // trim from path beginning to endLength. // Cut path components, removing before beginnings. - double startCutLength = startLength - pathStart; - double endCutLength = endLength - pathStart; + float startCutLength = startLength - pathStart; + float endCutLength = endLength - pathStart; - double subpathStart = 0.0; + float subpathStart = 0.0; - double subpathEnd = subpathStart + pathContainer.length(); + float subpathEnd = subpathStart + pathContainer.length(); if (!isInRange(startCutLength, subpathStart, subpathEnd) && !isInRange(endCutLength, subpathStart, subpathEnd)) @@ -437,7 +437,7 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, !isInRange(endCutLength, subpathStart, subpathEnd)) { /// The start of the path needs to be trimmed // |-------S======================|E - double cutLength = startCutLength - subpathStart; + float cutLength = startCutLength - subpathStart; CompoundBezierPath tempPath; tempPath.appendPath(pathContainer); auto newPaths = tempPath.trim(cutLength / pathContainer.length(), 1, 0); @@ -447,7 +447,7 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, } else if (!isInRange(startCutLength, subpathStart, subpathEnd) && isInRange(endCutLength, subpathStart, subpathEnd)) { // S|=======E----------------------| - double cutLength = endCutLength - subpathStart; + float cutLength = endCutLength - subpathStart; CompoundBezierPath tempPath; tempPath.appendPath(pathContainer); auto newPaths = tempPath.trim(0, cutLength / pathContainer.length(), 0); @@ -457,8 +457,8 @@ CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, } else if (isInRange(startCutLength, subpathStart, subpathEnd) && isInRange(endCutLength, subpathStart, subpathEnd)) { // |-------S============E---------| - double cutFromLength = startCutLength - subpathStart; - double cutToLength = endCutLength - subpathStart; + float cutFromLength = startCutLength - subpathStart; + float cutToLength = endCutLength - subpathStart; CompoundBezierPath tempPath; tempPath.appendPath(pathContainer); auto newPaths = tempPath.trim( diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp index 6b223adc3d..677dbe96bd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/ShapeUtils/BezierPathUtils.hpp @@ -17,22 +17,22 @@ BezierPath makeEllipseBezierPath( BezierPath makeRectangleBezierPath( Vector2D const &position, Vector2D const &inputSize, - double cornerRadius, + float cornerRadius, PathDirection direction ); BezierPath makeStarBezierPath( Vector2D const &position, - double outerRadius, - double innerRadius, - double inputOuterRoundedness, - double inputInnerRoundedness, - double numberOfPoints, - double rotation, + float outerRadius, + float innerRadius, + float inputOuterRoundedness, + float inputInnerRoundedness, + float numberOfPoints, + float rotation, PathDirection direction ); -CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, double start, double end, double offset, TrimType type); +CompoundBezierPath trimCompoundPath(CompoundBezierPath sourcePath, float start, float end, float offset, TrimType type); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp index 9d7e916705..98d7edbbc2 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/CompLayers/TextCompositionLayer.hpp @@ -42,7 +42,7 @@ public: _fontProvider = fontProvider; } - virtual void displayContentsWithFrame(double frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { + virtual void displayContentsWithFrame(float frame, bool forceUpdates, BezierPathsBoundingBoxContext &boundingBoxContext) override { if (!_textDocument) { return; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp index 3d6f999f6b..b9e47a7a60 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/MainThreadAnimationLayer.hpp @@ -42,7 +42,7 @@ public: _layerTextProvider = std::make_shared(textProvider); _layerFontProvider = std::make_shared(fontProvider); - setBounds(CGRect(0.0, 0.0, animation.width, animation.height)); + setSize(Vector2D(animation.width, animation.height)); auto layers = initializeCompositionLayers( animation.layers, @@ -60,7 +60,7 @@ public: for (auto layerIt = layers.rbegin(); layerIt != layers.rend(); layerIt++) { std::shared_ptr const &layer = *layerIt; - layer->setBounds(bounds()); + layer->setSize(size()); _animationLayers.push_back(layer); if (layer->isImageCompositionLayer()) { @@ -90,7 +90,7 @@ public: _layerFontProvider->addTextLayers(textLayers); _layerFontProvider->reloadTexts(); - setNeedsDisplay(true); + renderTreeNode(); } void setRespectAnimationFrameRate(bool respectAnimationFrameRate) { @@ -98,7 +98,7 @@ public: } void display() { - double newFrame = currentFrame(); + float newFrame = currentFrame(); if (_respectAnimationFrameRate) { newFrame = floor(newFrame); } @@ -140,7 +140,7 @@ public: }*/ } - std::optional getValue(AnimationKeypath const &keypath, std::optional atFrame) { + std::optional getValue(AnimationKeypath const &keypath, std::optional atFrame) { /*for (const auto &layer : _animationLayers) { assert(false); if @@ -153,7 +153,7 @@ public: return std::nullopt; } - std::optional getOriginalValue(AnimationKeypath const &keypath, std::optional atFrame) { + std::optional getOriginalValue(AnimationKeypath const &keypath, std::optional atFrame) { /*for (const auto &layer : _animationLayers) { assert(false); if @@ -186,10 +186,10 @@ public: return results; } - double currentFrame() const { + float currentFrame() const { return _currentFrame; } - void setCurrentFrame(double currentFrame) { + void setCurrentFrame(float currentFrame) { _currentFrame = currentFrame; for (size_t i = 0; i < _animationLayers.size(); i++) { @@ -237,9 +237,8 @@ public: } } _renderTreeNode = std::make_shared( - bounds(), - position(), - CATransform3D::identity(), + size(), + Transform2D::identity(), 1.0, false, false, @@ -249,31 +248,14 @@ public: ); } - updateRenderTree(); - return _renderTreeNode; } - void updateRenderTree() { - for (const auto &animationLayer : _animationLayers) { - bool found = false; - for (const auto &sublayer : sublayers()) { - if (animationLayer == sublayer) { - found = true; - break; - } - } - if (found) { - animationLayer->updateRenderTree(_boundingBoxContext); - } - } - } - private: // MARK: Internal /// The animatable Current Frame Property - double _currentFrame = 0.0; + float _currentFrame = 0.0; std::shared_ptr _imageProvider; std::shared_ptr _textProvider; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp index 1685790650..8a98548bd3 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.cpp @@ -14,7 +14,7 @@ std::vector> initializeCompositionLayers( std::shared_ptr const &layerImageProvider, std::shared_ptr const &textProvider, std::shared_ptr const &fontProvider, - double frameRate + float frameRate ) { std::vector> compositionLayers; std::map> layerMap; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp index 247d8d5631..74df3529d3 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/CompositionLayersInitializer.hpp @@ -15,7 +15,7 @@ std::vector> initializeCompositionLayers( std::shared_ptr const &layerImageProvider, std::shared_ptr const &textProvider, std::shared_ptr const &fontProvider, - double frameRate + float frameRate ); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp index de22024145..459558a2bd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/LayerContainers/Utility/LayerTransformNode.hpp @@ -136,11 +136,11 @@ public: return _transformProperties; } - virtual bool shouldRebuildOutputs(double frame) override { + virtual bool shouldRebuildOutputs(float frame) override { return hasLocalUpdates() || hasUpstreamUpdates(); } - virtual void rebuildOutputs(double frame) override { + virtual void rebuildOutputs(float frame) override { _opacity = ((float)_transformProperties->opacity()->value().value) * 0.01f; Vector2D position(0.0, 0.0); @@ -157,7 +157,7 @@ public: Vector3D anchor = _transformProperties->anchor()->value(); Vector3D scale = _transformProperties->scale()->value(); - _localTransform = CATransform3D::makeTransform( + _localTransform = Transform2D::makeTransform( Vector2D(anchor.x, anchor.y), position, Vector2D(scale.x, scale.y), @@ -181,7 +181,7 @@ public: return _opacity; } - CATransform3D const &globalTransform() { + Transform2D const &globalTransform() { return _globalTransform; } @@ -191,8 +191,8 @@ private: std::shared_ptr _transformProperties; float _opacity = 1.0; - CATransform3D _localTransform = CATransform3D::identity(); - CATransform3D _globalTransform = CATransform3D::identity(); + Transform2D _localTransform = Transform2D::identity(); + Transform2D _globalTransform = Transform2D::identity(); public: virtual LayerTransformNode *asLayerTransformNode() override { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp index 1a6f995c1f..f77cdc470b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/NodeProperty.hpp @@ -27,7 +27,7 @@ public: return _typedContainer.outputValue(); } - virtual bool needsUpdate(double frame) const override { + virtual bool needsUpdate(float frame) const override { return _typedContainer.needsUpdate() || _valueProvider->hasUpdate(frame); } @@ -39,7 +39,7 @@ public: _typedContainer.setNeedsUpdate();*/ } - virtual void update(double frame) override { + virtual void update(float frame) override { _typedContainer.setValue(_valueProvider->value(frame), frame); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp index f317e68b9f..ec682ceb93 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyNodeProperty.hpp @@ -16,10 +16,10 @@ public: public: /// Returns true if the property needs to recompute its stored value - virtual bool needsUpdate(double frame) const = 0; + virtual bool needsUpdate(float frame) const = 0; /// Updates the property for the frame - virtual void update(double frame) = 0; + virtual void update(float frame) = 0; /// The Type of the value provider virtual AnyValue::Type valueType() const = 0; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp index 55e6dac2e2..1776bd13bc 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/AnyValueContainer.hpp @@ -17,7 +17,7 @@ public: virtual bool needsUpdate() const = 0; /// The frame time of the last provided update - virtual double lastUpdateFrame() const = 0; + virtual float lastUpdateFrame() const = 0; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp index cf30fec769..bc6a13261e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/HasRenderUpdates.hpp @@ -5,7 +5,7 @@ namespace lottie { class HasRenderUpdates { public: - virtual bool hasRenderUpdates(double forFrame) = 0; + virtual bool hasRenderUpdates(float forFrame) = 0; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp index 64eff898f4..0879d00c82 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/Protocols/NodePropertyMap.hpp @@ -13,7 +13,7 @@ class NodePropertyMap: virtual public HasChildKeypaths { public: virtual std::vector> &properties() = 0; - bool needsLocalUpdate(double frame) { + bool needsLocalUpdate(float frame) { for (auto &property : properties()) { if (property->needsUpdate(frame)) { return true; @@ -22,7 +22,7 @@ public: return false; } - void updateNodeProperties(double frame) { + void updateNodeProperties(float frame) { for (auto &property : properties()) { property->update(frame); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp index 54f1548b14..563de16de5 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueContainer.hpp @@ -15,7 +15,7 @@ public: } public: - double _lastUpdateFrame = std::numeric_limits::infinity(); + float _lastUpdateFrame = std::numeric_limits::infinity(); bool _needsUpdate = true; virtual AnyValue value() const override { @@ -26,7 +26,7 @@ public: return _needsUpdate; } - virtual double lastUpdateFrame() const override { + virtual float lastUpdateFrame() const override { return _lastUpdateFrame; } @@ -40,7 +40,7 @@ public: _needsUpdate = false; } - void setValue(AnyValue value, double forFrame) { + void setValue(AnyValue value, float forFrame) { if (value.type() == AnyValueType::type()) { _needsUpdate = false; _lastUpdateFrame = forFrame; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp index 2200c4c113..5e744386dd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/DashPatternInterpolator.hpp @@ -23,14 +23,14 @@ public: } virtual DashPattern value(AnimationFrameTime frame) override { - std::vector values; + std::vector values; for (const auto &interpolator : _keyframeInterpolators) { values.push_back(interpolator->value(frame).value); } return DashPattern(std::move(values)); } - virtual bool hasUpdate(double frame) const override { + virtual bool hasUpdate(float frame) const override { for (const auto &interpolator : _keyframeInterpolators) { if (interpolator->hasUpdate(frame)) { return true; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp index 4ea16e331a..de35d4887a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/KeyframeInterpolator.hpp @@ -62,7 +62,7 @@ public: /// - If time is outside of the span, and there are more keyframes /// - If a value delegate is set /// - If leading and trailing are both nil. - virtual bool hasUpdate(double frame) const override { + virtual bool hasUpdate(float frame) const override { if (!lastUpdatedFrame.has_value()) { return true; } @@ -94,7 +94,7 @@ public: // MARK: Fileprivate - std::optional lastUpdatedFrame; + std::optional lastUpdatedFrame; std::optional leadingIndex; std::optional trailingIndex; @@ -102,7 +102,7 @@ public: std::optional> trailingKeyframe; /// Finds the appropriate Leading and Trailing keyframe index for the given time. - void updateSpanIndices(double frame) { + void updateSpanIndices(float frame) { if (keyframes.empty()) { leadingIndex = std::nullopt; trailingIndex = std::nullopt; @@ -270,7 +270,7 @@ public: /// - If time is outside of the span, and there are more keyframes /// - If a value delegate is set /// - If leading and trailing are both nil. - bool hasUpdate(double frame) const { + bool hasUpdate(float frame) const { if (!lastUpdatedFrame.has_value()) { return true; } @@ -302,7 +302,7 @@ public: // MARK: Fileprivate - std::optional lastUpdatedFrame; + std::optional lastUpdatedFrame; std::optional leadingIndex; std::optional trailingIndex; @@ -310,7 +310,7 @@ public: std::optional> trailingKeyframe; /// Finds the appropriate Leading and Trailing keyframe index for the given time. - void updateSpanIndices(double frame) { + void updateSpanIndices(float frame) { if (keyframes.empty()) { leadingIndex = std::nullopt; trailingIndex = std::nullopt; @@ -434,7 +434,7 @@ private: ValueInterpolator::setInplace(from.value, outPath); } - void interpolateInplace(Keyframe const &from, Keyframe const &to, double progress, BezierPath &outPath) { + void interpolateInplace(Keyframe const &from, Keyframe const &to, float progress, BezierPath &outPath) { std::optional spatialOutTangent2d; if (from.spatialOutTangent) { spatialOutTangent2d = Vector2D(from.spatialOutTangent->x, from.spatialOutTangent->y); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp index dc1212ba28..b6c3fe6a1f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/NodeProperties/ValueProviders/SingleValueProvider.hpp @@ -28,7 +28,7 @@ public: return AnyValueType::type(); } - virtual bool hasUpdate(double frame) const override { + virtual bool hasUpdate(float frame) const override { return _hasUpdate; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp index c1d2db8959..f7b73a550d 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/OutputNodes/PassThroughOutputNode.hpp @@ -40,7 +40,7 @@ public: return nullptr; } - virtual bool hasOutputUpdates(double forFrame) override { + virtual bool hasOutputUpdates(float forFrame) override { /// Changes to this node do not affect downstream nodes. bool parentUpdate = false; if (_parent) { @@ -51,7 +51,7 @@ public: return parentUpdate; } - virtual bool hasRenderUpdates(double forFrame) override { + virtual bool hasRenderUpdates(float forFrame) override { /// Return true if there are upstream updates or if this node has updates bool upstreamUpdates = false; if (_parent) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp index 6acdb2ee20..6e8b534b48 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Nodes/Text/TextAnimatorNode.hpp @@ -93,7 +93,7 @@ public: return _childKeypaths; } - CATransform3D caTransform() { + Transform2D caTransform() { Vector2D anchor = Vector2D::Zero(); if (_anchor) { auto anchor3d = _anchor->value(); @@ -112,21 +112,21 @@ public: scale = Vector2D(scale3d.x, scale3d.y); } - double rotation = 0.0; + float rotation = 0.0; if (_rotation) { rotation = _rotation->value().value; } - std::optional skew; + std::optional skew; if (_skew) { skew = _skew->value().value; } - std::optional skewAxis; + std::optional skewAxis; if (_skewAxis) { skewAxis = _skewAxis->value().value; } - return CATransform3D::makeTransform( + return Transform2D::makeTransform( anchor, position, scale, @@ -140,7 +140,7 @@ public: return nullptr; } - double opacity() { + float opacity() { if (_opacity) { return _opacity->value().value; } else { @@ -164,7 +164,7 @@ public: } } - double tracking() { + float tracking() { if (_tracking) { return _tracking->value().value; } else { @@ -172,7 +172,7 @@ public: } } - double strokeWidth() { + float strokeWidth() { if (_strokeWidth) { return _strokeWidth->value().value; } else { @@ -212,20 +212,20 @@ public: return _parentTextNode; } - CATransform3D xform() { + Transform2D xform() { if (_xform.has_value()) { return _xform.value(); } else if (_parentTextNode) { return _parentTextNode->xform(); } else { - return CATransform3D::identity(); + return Transform2D::identity(); } } - void setXform(CATransform3D const &xform) { + void setXform(Transform2D const &xform) { _xform = xform; } - double opacity() { + float opacity() { if (_opacity.has_value()) { return _opacity.value(); } else if (_parentTextNode) { @@ -234,7 +234,7 @@ public: return 1.0; } } - void setOpacity(double opacity) { + void setOpacity(float opacity) { _opacity = opacity; } @@ -264,7 +264,7 @@ public: _fillColor = fillColor; } - double tracking() { + float tracking() { if (_tracking.has_value()) { return _tracking.value(); } else if (_parentTextNode) { @@ -273,11 +273,11 @@ public: return 0.0; } } - void setTracking(double tracking) { + void setTracking(float tracking) { _tracking = tracking; } - double strokeWidth() { + float strokeWidth() { if (_strokeWidth.has_value()) { return _strokeWidth.value(); } else if (_parentTextNode) { @@ -286,11 +286,11 @@ public: return 0.0; } } - void setStrokeWidth(double strokeWidth) { + void setStrokeWidth(float strokeWidth) { _strokeWidth = strokeWidth; } - virtual bool hasOutputUpdates(double frame) override { + virtual bool hasOutputUpdates(float frame) override { // TODO Fix This return true; } @@ -312,12 +312,12 @@ private: std::shared_ptr _outputPath; - std::optional _xform; - std::optional _opacity; + std::optional _xform; + std::optional _opacity; std::optional _strokeColor; std::optional _fillColor; - std::optional _tracking; - std::optional _strokeWidth; + std::optional _tracking; + std::optional _strokeWidth; }; class TextAnimatorNode: public AnimatorNode { @@ -347,7 +347,7 @@ public: return true; } - virtual void rebuildOutputs(double frame) override { + virtual void rebuildOutputs(float frame) override { _textOutputNode->setXform(_textAnimatorProperties->caTransform()); _textOutputNode->setOpacity(((float)_textAnimatorProperties->opacity()) * 0.01f); _textOutputNode->setStrokeColor(_textAnimatorProperties->strokeColor()); diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp index 1d62e6c8d2..249c055665 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/AnimatorNode.hpp @@ -55,7 +55,7 @@ public: virtual std::shared_ptr outputNode() = 0; /// Update the outputs of the node. Called if local contents were update or if outputsNeedUpdate returns true. - virtual void rebuildOutputs(double frame) = 0; + virtual void rebuildOutputs(float frame) = 0; /// Setters for marking current node state. bool isEnabled() { @@ -79,10 +79,10 @@ public: _hasUpstreamUpdates = hasUpstreamUpdates; } - std::optional lastUpdateFrame() { + std::optional lastUpdateFrame() { return _lastUpdateFrame; } - virtual void setLastUpdateFrame(std::optional lastUpdateFrame) { + virtual void setLastUpdateFrame(std::optional lastUpdateFrame) { _lastUpdateFrame = lastUpdateFrame; } @@ -97,20 +97,20 @@ public: } /// Called at the end of this nodes update cycle. Always called. Optional. - virtual bool performAdditionalLocalUpdates(double frame, bool forceLocalUpdate) { + virtual bool performAdditionalLocalUpdates(float frame, bool forceLocalUpdate) { /// Optional return forceLocalUpdate; } - virtual void performAdditionalOutputUpdates(double frame, bool forceOutputUpdate) { + virtual void performAdditionalOutputUpdates(float frame, bool forceOutputUpdate) { /// Optional } /// The default simply returns `hasLocalUpdates` - virtual bool shouldRebuildOutputs(double frame) { + virtual bool shouldRebuildOutputs(float frame) { return hasLocalUpdates(); } - virtual bool updateOutputs(double frame, bool forceOutputUpdate) { + virtual bool updateOutputs(float frame, bool forceOutputUpdate) { if (!isEnabled()) { setLastUpdateFrame(frame); if (const auto parentNodeValue = parentNode()) { @@ -147,7 +147,7 @@ public: } /// Rebuilds the content of this node, and upstream nodes if necessary. - virtual bool updateContents(double frame, bool forceLocalUpdate) { + virtual bool updateContents(float frame, bool forceLocalUpdate) { if (!isEnabled()) { // Disabled node, pass through. if (const auto parentNodeValue = parentNode()) { @@ -185,9 +185,12 @@ public: return localUpdatesPermeateDownstream() ? hasUpstreamUpdates() || hasLocalUpdates() : hasUpstreamUpdates(); } - virtual void updateTree(double frame, bool forceUpdates) { - updateContents(frame, forceUpdates); - updateOutputs(frame, forceUpdates); + bool updateTree(float frame, bool forceUpdates) { + if (updateContents(frame, forceUpdates)) { + return updateOutputs(frame, forceUpdates); + } else { + return false; + } } /// The name of the Keypath @@ -227,7 +230,7 @@ private: bool _isEnabled = true; bool _hasLocalUpdates = false; bool _hasUpstreamUpdates = false; - std::optional _lastUpdateFrame; + std::optional _lastUpdateFrame; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp index ee400acf20..69d5dd81d5 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/Protocols/NodeOutput.hpp @@ -15,7 +15,7 @@ public: virtual std::shared_ptr parent() = 0; /// Returns true if there are any updates upstream. OutputPath must be built before returning. - virtual bool hasOutputUpdates(double forFrame) = 0; + virtual bool hasOutputUpdates(float forFrame) = 0; virtual std::shared_ptr outputPath() = 0; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp index a1b830cf13..a1ec1b2bed 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.cpp @@ -2,13 +2,13 @@ namespace lottie { -void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations) { +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations) { std::vector alphaColors; - std::vector alphaValues; - std::vector alphaLocations; + std::vector alphaValues; + std::vector alphaLocations; std::vector gradientColors; - std::vector colorLocations; + std::vector colorLocations; for (int i = 0; i < numberOfColors; i++) { int ix = i * 4; @@ -26,7 +26,7 @@ void getGradientParameters(int numberOfColors, GradientColorSet const &colors, s bool drawMask = false; for (int i = numberOfColors * 4; i < (int)colors.colors.size(); i += 2) { - double alpha = colors.colors[i + 1]; + float alpha = colors.colors[i + 1]; if (alpha < 1.0) { drawMask = true; } @@ -36,7 +36,7 @@ void getGradientParameters(int numberOfColors, GradientColorSet const &colors, s } if (drawMask) { - std::vector locations; + std::vector locations; for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()); i++) { if (std::find(locations.begin(), locations.end(), colorLocations[i]) == locations.end()) { locations.push_back(colorLocations[i]); @@ -62,23 +62,23 @@ void getGradientParameters(int numberOfColors, GradientColorSet const &colors, s Color color = gradientColors[0]; for (size_t i = 0; i < std::min(gradientColors.size(), colorLocations.size()) - 1; i++) { if (location >= colorLocations[i] && location <= colorLocations[i + 1]) { - double localLocation = 0.0; + float localLocation = 0.0; if (colorLocations[i] != colorLocations[i + 1]) { - localLocation = remapDouble(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0); + localLocation = remapFloat(location, colorLocations[i], colorLocations[i + 1], 0.0, 1.0); } color = ValueInterpolator::interpolate(gradientColors[i], gradientColors[i + 1], localLocation, std::nullopt, std::nullopt); break; } } - double alpha = 1.0; + float alpha = 1.0; for (size_t i = 0; i < std::min(alphaValues.size(), alphaLocations.size()) - 1; i++) { if (location >= alphaLocations[i] && location <= alphaLocations[i + 1]) { - double localLocation = 0.0; + float localLocation = 0.0; if (alphaLocations[i] != alphaLocations[i + 1]) { - localLocation = remapDouble(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0); + localLocation = remapFloat(location, alphaLocations[i], alphaLocations[i + 1], 0.0, 1.0); } - alpha = ValueInterpolator::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt); + alpha = ValueInterpolator::interpolate(alphaValues[i], alphaValues[i + 1], localLocation, std::nullopt, std::nullopt); break; } } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp index f14c578644..8319b8505b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/MainThread/NodeRenderSystem/RenderLayers/GetGradientParameters.hpp @@ -6,7 +6,7 @@ namespace lottie { -void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations); +void getGradientParameters(int numberOfColors, GradientColorSet const &colors, std::vector &outColors, std::vector &outLocations); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp index 289ef8cc09..ffeb67964b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Animation.hpp @@ -32,7 +32,7 @@ public: std::optional tgs_, AnimationFrameTime startFrame_, AnimationFrameTime endFrame_, - double framerate_, + float framerate_, std::string const &version_, std::optional type_, int width_, @@ -90,10 +90,10 @@ public: } } - AnimationFrameTime startFrame = getDouble(json, "ip"); - AnimationFrameTime endFrame = getDouble(json, "op"); + AnimationFrameTime startFrame = (float)getDouble(json, "ip"); + AnimationFrameTime endFrame = (float)getDouble(json, "op"); - double framerate = getDouble(json, "fr"); + float framerate = (float)getDouble(json, "fr"); int width = getInt(json, "w"); int height = getInt(json, "h"); @@ -256,7 +256,7 @@ public: AnimationFrameTime endFrame; /// The frame rate of the composition. - double framerate; + float framerate; /// Return all marker names, in order, or an empty list if none are specified std::vector markerNames() { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp index 992f8534df..5eea900309 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/ImageAsset.hpp @@ -18,8 +18,8 @@ public: std::string id_, std::string name_, std::string directory_, - double width_, - double height_ + float width_, + float height_ ) : Asset(id_), name(name_), directory(directory_), @@ -33,8 +33,8 @@ public: Asset(json) { name = getString(json, "p"); directory = getString(json, "u"); - width = getDouble(json, "w"); - height = getDouble(json, "h"); + width = (float)getDouble(json, "w"); + height = (float)getDouble(json, "h"); _e = getOptionalInt(json, "e"); _t = getOptionalString(json, "t"); @@ -64,8 +64,8 @@ public: std::string directory; /// Image Size - double width; - double height; + float width; + float height; std::optional _e; std::optional _t; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp index d79f0016b8..af169474a7 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Assets/PrecompAsset.hpp @@ -23,7 +23,9 @@ public: explicit PrecompAsset(lottiejson11::Json::object const &json) noexcept(false) : Asset(json) { - frameRate = getOptionalDouble(json, "fr"); + if (const auto frameRateValue = getOptionalDouble(json, "fr")) { + frameRate = (float)frameRateValue.value(); + } auto layerDictionaries = getObjectArray(json, "layers"); for (size_t i = 0; i < layerDictionaries.size(); i++) { @@ -56,7 +58,7 @@ public: /// Layers of the precomp std::vector> layers; - std::optional frameRate; + std::optional frameRate; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp index 2670043c44..16f84d0f10 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/LayerModel.hpp @@ -71,9 +71,9 @@ public: coordinateSpace = std::nullopt; } - inFrame = getDouble(json, "ip"); - outFrame = getDouble(json, "op"); - startTime = getDouble(json, "st"); + inFrame = (float)getDouble(json, "ip"); + outFrame = (float)getDouble(json, "op"); + startTime = (float)getDouble(json, "st"); transform = std::make_shared(getObject(json, "ks")); parent = getOptionalInt(json, "parent"); @@ -141,7 +141,7 @@ public: } if (const auto timeStretchData = getOptionalDouble(json, "sr")) { - _timeStretch = timeStretchData.value(); + _timeStretch = (float)timeStretchData.value(); } if (const auto matteRawValue = getOptionalInt(json, "tt")) { @@ -255,7 +255,7 @@ public: } } - double timeStretch() { + float timeStretch() { if (_timeStretch.has_value()) { return _timeStretch.value(); } else { @@ -279,12 +279,12 @@ public: std::optional coordinateSpace; /// The in time of the layer in frames. - double inFrame; + float inFrame; /// The out time of the layer in frames. - double outFrame; + float outFrame; /// The start time of the layer in frames. - double startTime; + float startTime; /// The transform of the layer std::shared_ptr transform; @@ -299,7 +299,7 @@ public: std::optional>> masks; /// A number that stretches time by a multiplier - std::optional _timeStretch; + std::optional _timeStretch; /// The type of matte if any. std::optional matte; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp index 44ca30ea0f..16a2195a0b 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/PreCompLayerModel.hpp @@ -19,8 +19,8 @@ public: if (const auto timeRemappingData = getOptionalObject(json, "tm")) { timeRemapping = KeyframeGroup(timeRemappingData.value()); } - width = getDouble(json, "w"); - height = getDouble(json, "h"); + width = (float)getDouble(json, "w"); + height = (float)getDouble(json, "h"); } virtual ~PreCompLayerModel() = default; @@ -46,10 +46,10 @@ public: std::optional> timeRemapping; /// Precomp Width - double width; + float width; /// Precomp Height - double height; + float height; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp index 922d3e6342..cea0671b49 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Layers/SolidLayerModel.hpp @@ -12,8 +12,8 @@ public: explicit SolidLayerModel(lottiejson11::Json::object const &json) noexcept(false) : LayerModel(json) { colorHex = getString(json, "sc"); - width = getDouble(json, "sw"); - height = getDouble(json, "sh"); + width = (float)getDouble(json, "sw"); + height = (float)getDouble(json, "sh"); } virtual ~SolidLayerModel() = default; @@ -31,10 +31,10 @@ public: std::string colorHex; /// The Width of the color layer - double width; + float width; /// The height of the color layer - double height; + float height; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp index 06553a99b6..632a2374f9 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/FitzModifier.hpp @@ -19,33 +19,33 @@ public: lottiejson11::Json::object toJson() const { lottiejson11::Json::object result; - result.insert(std::make_pair("o", (double)original)); + result.insert(std::make_pair("o", (float)original)); if (type12.has_value()) { - result.insert(std::make_pair("f12", (double)type12.value())); + result.insert(std::make_pair("f12", (float)type12.value())); } if (type3.has_value()) { - result.insert(std::make_pair("f3", (double)type3.value())); + result.insert(std::make_pair("f3", (float)type3.value())); } if (type4.has_value()) { - result.insert(std::make_pair("f4", (double)type4.value())); + result.insert(std::make_pair("f4", (float)type4.value())); } if (type5.has_value()) { - result.insert(std::make_pair("f5", (double)type5.value())); + result.insert(std::make_pair("f5", (float)type5.value())); } if (type6.has_value()) { - result.insert(std::make_pair("f6", (double)type6.value())); + result.insert(std::make_pair("f6", (float)type6.value())); } return result; } public: - double original; - std::optional type12; - std::optional type3; - std::optional type4; - std::optional type5; - std::optional type6; + float original; + std::optional type12; + std::optional type3; + std::optional type4; + std::optional type5; + std::optional type6; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp index 4f715e9ac7..bd1e5584dd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Objects/Marker.hpp @@ -20,7 +20,7 @@ public: explicit Marker(lottiejson11::Json::object const &json) noexcept(false) { name = getString(json, "cm"); - frameTime = getDouble(json, "tm"); + frameTime = (float)getDouble(json, "tm"); dr = getOptionalInt(json, "dr"); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp index 4110e7cf78..95bed62b51 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/GradientStroke.hpp @@ -91,7 +91,7 @@ public: } if (const auto miterLimitData = getOptionalDouble(json, "ml")) { - miterLimit = miterLimitData.value(); + miterLimit = (float)miterLimitData.value(); } auto colorsContainer = getObject(json, "g"); @@ -182,7 +182,7 @@ public: LineJoin lineJoin; /// Miter Limit - std::optional miterLimit; + std::optional miterLimit; /// The dash pattern of the stroke std::optional> dashPattern; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp index b3d8ff3066..e306fcd6a6 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/ShapeItems/Stroke.hpp @@ -65,7 +65,7 @@ public: } if (const auto miterLimitData = getOptionalDouble(json, "ml")) { - miterLimit = miterLimitData.value(); + miterLimit = (float)miterLimitData.value(); } if (const auto dashElementsData = getOptionalObjectArray(json, "d")) { @@ -128,7 +128,7 @@ public: LineJoin lineJoin; /// Miter Limit - std::optional miterLimit; + std::optional miterLimit; /// The dash pattern of the stroke std::optional> dashPattern; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp index 229a85c9dd..b321983e71 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Font.hpp @@ -14,7 +14,7 @@ public: std::string const &name_, std::string const &familyName_, std::string const &style_, - double ascent_ + float ascent_ ) : name(name_), familyName(familyName_), @@ -29,7 +29,7 @@ public: weight = getOptionalString(json, "fWeight"); fontClass = getOptionalString(json, "fClass"); style = getString(json, "fStyle"); - ascent = getDouble(json, "ascent"); + ascent = (float)getDouble(json, "ascent"); origin = getOptionalInt(json, "origin"); } @@ -63,7 +63,7 @@ public: std::optional weight; std::optional fontClass; std::string style; - double ascent; + float ascent; std::optional origin; }; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp index 17b09c0ce0..d5c1522850 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/Glyph.hpp @@ -13,10 +13,10 @@ class Glyph { public: Glyph( std::string const &character_, - double fontSize_, + float fontSize_, std::string const &fontFamily_, std::string const &fontStyle_, - double width_, + float width_, std::optional>> shapes_ ) : character(character_), @@ -34,10 +34,10 @@ public: fontStyle(""), width(0.0) { character = getString(json, "ch"); - fontSize = getDouble(json, "size"); + fontSize = (float)getDouble(json, "size"); fontFamily = getString(json, "fFamily"); fontStyle = getString(json, "style"); - width = getDouble(json, "w"); + width = (float)getDouble(json, "w"); if (const auto shapeContainer = getOptionalObject(json, "data")) { internalHasData = true; @@ -86,7 +86,7 @@ public: std::string character; /// The font size of the character - double fontSize; + float fontSize; /// The font family of the character std::string fontFamily; @@ -95,7 +95,7 @@ public: std::string fontStyle; /// The Width of the character - double width; + float width; /// The Shape Data of the Character std::optional>> shapes; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp index d692dd0d77..83ea39ebcc 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Model/Text/TextDocument.hpp @@ -20,15 +20,15 @@ class TextDocument { public: TextDocument( std::string const &text_, - double fontSize_, + float fontSize_, std::string const &fontFamily_, TextJustification justification_, int tracking_, - double lineHeight_, - std::optional baseline_, + float lineHeight_, + std::optional baseline_, std::optional fillColorData_, std::optional strokeColorData_, - std::optional strokeWidth_, + std::optional strokeWidth_, std::optional strokeOverFill_, std::optional textFramePosition_, std::optional textFrameSize_ @@ -62,7 +62,7 @@ public: lottiejson11::Json::object const &json = jsonAny.object_items(); text = getString(json, "t"); - fontSize = getDouble(json, "s"); + fontSize = (float)getDouble(json, "s"); fontFamily = getString(json, "f"); auto justificationRawValue = getInt(json, "j"); @@ -81,8 +81,10 @@ public: } tracking = getInt(json, "tr"); - lineHeight = getDouble(json, "lh"); - baseline = getOptionalDouble(json, "ls"); + lineHeight = (float)getDouble(json, "lh"); + if (const auto baselineValue = getOptionalDouble(json, "ls")) { + baseline = (float)baselineValue.value(); + } if (const auto fillColorDataValue = getOptionalAny(json, "fc")) { fillColorData = Color(fillColorDataValue.value()); @@ -92,8 +94,12 @@ public: strokeColorData = Color(strokeColorDataValue.value()); } - strokeWidth = getOptionalDouble(json, "sw"); - strokeOverFill = getOptionalBool(json, "of"); + if (const auto strokeWidthValue = getOptionalDouble(json, "sw")) { + strokeWidth = (float)strokeWidthValue.value(); + } + if (const auto strokeOverFillValue = getOptionalBool(json, "of")) { + strokeOverFill = (float)strokeOverFillValue.value(); + } if (const auto textFramePositionData = getOptionalAny(json, "ps")) { textFramePosition = Vector3D(textFramePositionData.value()); @@ -145,7 +151,7 @@ public: std::string text; /// The Font size - double fontSize; + float fontSize; /// The Font Family std::string fontFamily; @@ -157,10 +163,10 @@ public: int tracking; /// Line Height - double lineHeight; + float lineHeight; /// Baseline - std::optional baseline; + std::optional baseline; /// Fill Color data std::optional fillColorData; @@ -169,7 +175,7 @@ public: std::optional strokeColorData; /// Stroke Width - std::optional strokeWidth; + std::optional strokeWidth; /// Stroke Over Fill std::optional strokeOverFill; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp index b5c6065ecd..85c50dba1e 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/BezierPath.cpp @@ -7,7 +7,7 @@ namespace lottie { -BezierTrimPathPosition::BezierTrimPathPosition(double start_, double end_) : +BezierTrimPathPosition::BezierTrimPathPosition(float start_, float end_) : start(start_), end(end_) { } @@ -129,11 +129,11 @@ std::shared_ptr BezierPathContents::cgPath() const { return cgPath; } -double BezierPathContents::length() { +float BezierPathContents::length() { if (_length.has_value()) { return _length.value(); } else { - double result = 0.0; + float result = 0.0; for (size_t i = 1; i < elements.size(); i++) { result += elements[i].length(elements[i - 1]); } @@ -221,7 +221,7 @@ void BezierPathContents::updateVertex(CurveVertex const &vertex, int atIndex, bo } } -std::vector> BezierPathContents::trim(double fromLength, double toLength, double offsetLength) { +std::vector> BezierPathContents::trim(float fromLength, float toLength, float offsetLength) { if (elements.size() <= 1) { return {}; } @@ -230,7 +230,7 @@ std::vector> BezierPathContents::trim(double return {}; } - double lengthValue = length(); + float lengthValue = length(); /// Normalize lengths to the curve length. auto start = fmod(fromLength + offsetLength, lengthValue); @@ -284,7 +284,7 @@ std::vector> BezierPathContents::trimPathAtL std::vector> paths; - double runningLength = 0.0; + float runningLength = 0.0; bool finishedTrimming = false; auto pathElements = elements; @@ -301,7 +301,7 @@ std::vector> BezierPathContents::trimPathAtL /// Loop through and add elements within start->end range. /// Get current element auto element = pathElements[i]; - double elementLength = 0.0; + float elementLength = 0.0; if (i != 0) { elementLength = element.length(pathElements[i - 1]); } @@ -412,7 +412,7 @@ lottiejson11::Json BezierPath::toJson() const { return _contents->toJson(); } -double BezierPath::length() { +float BezierPath::length() { return _contents->length(); } @@ -456,7 +456,7 @@ void BezierPath::updateVertex(CurveVertex const &vertex, int atIndex, bool remea _contents->updateVertex(vertex, atIndex, remeasure); } -std::vector BezierPath::trim(double fromLength, double toLength, double offsetLength) { +std::vector BezierPath::trim(float fromLength, float toLength, float offsetLength) { std::vector result; auto resultContents = _contents->trim(fromLength, toLength, offsetLength); @@ -486,8 +486,8 @@ std::shared_ptr BezierPath::cgPath() const { return _contents->cgPath(); } -BezierPath BezierPath::copyUsingTransform(CATransform3D const &transform) const { - if (transform == CATransform3D::identity()) { +BezierPath BezierPath::copyUsingTransform(Transform2D const &transform) const { + if (transform == Transform2D::identity()) { return (*this); } BezierPath result; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp index 5a95f4f02d..58d02eb283 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Private/Utility/Primitives/CompoundBezierPath.hpp @@ -17,7 +17,7 @@ public: paths({ path }) { } - CompoundBezierPath(std::vector paths_, std::optional length_) : + CompoundBezierPath(std::vector paths_, std::optional length_) : paths(paths_), _length(length_) { } @@ -28,11 +28,11 @@ public: public: std::vector paths; - double length() { + float length() { if (_length.has_value()) { return _length.value(); } else { - double l = 0.0; + float l = 0.0; for (auto &path : paths) { l += path.length(); } @@ -42,7 +42,7 @@ public: } private: - std::optional _length; + std::optional _length; public: std::shared_ptr addingPath(BezierPath const &path) const { @@ -64,29 +64,16 @@ public: return std::make_shared(newPaths); } - std::shared_ptr trim(double fromPosition, double toPosition, double offset) { + std::shared_ptr trim(float fromPosition, float toPosition, float offset) { if (fromPosition == toPosition) { return std::make_shared(); } - /*bool trimSimultaneously = false; - if (trimSimultaneously) { - /// Trim each path individually. - std::vector newPaths; - for (auto &path : paths) { - auto trimmedPaths = path.trim(fromPosition * path.length(), toPosition * path.length(), offset * path.length()); - for (const auto &trimmedPath : trimmedPaths) { - newPaths.push_back(trimmedPath); - } - } - return std::make_shared(newPaths); - }*/ - - double lengthValue = length(); + float lengthValue = length(); /// Normalize lengths to the curve length. - double startPosition = fmod(fromPosition + offset, 1.0); - double endPosition = fmod(toPosition + offset, 1.0); + float startPosition = fmod(fromPosition + offset, 1.0); + float endPosition = fmod(toPosition + offset, 1.0); if (startPosition < 0.0) { startPosition = 1.0 + startPosition; @@ -123,7 +110,7 @@ public: auto compoundPath = std::make_shared(); auto trim = positions[0]; positions.erase(positions.begin()); - double pathStartPosition = 0.0; + float pathStartPosition = 0.0; bool finishedTrimming = false; int i = 0; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp index f03a137db4..c14d46f8d9 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/FontProvider/AnimationFontProvider.hpp @@ -11,7 +11,7 @@ namespace lottie { /// class AnimationFontProvider { public: - virtual std::shared_ptr fontFor(std::string const &family, double size) = 0; + virtual std::shared_ptr fontFor(std::string const &family, float size) = 0; }; /// Default Font provider. @@ -22,7 +22,7 @@ public: virtual ~DefaultFontProvider() = default; - virtual std::shared_ptr fontFor(std::string const &family, double size) override { + virtual std::shared_ptr fontFor(std::string const &family, float size) override { //CTFontCreateWithName(family as CFString, size, nil) return nullptr; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp index 8165d87d71..aa58f615c4 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.cpp @@ -2,13 +2,13 @@ namespace lottie { -double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh) { +float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh) { return toLow + (value - fromLow) * (toHigh - toLow) / (fromHigh - fromLow); } -double clampDouble(double value, double a, double b) { - double minValue = a <= b ? a : b; - double maxValue = a <= b ? b : a; +float clampFloat(float value, float a, float b) { + float minValue = a <= b ? a : b; + float maxValue = a <= b ? b : a; return std::max(std::min(value, maxValue), minValue); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp index 2bfabdca07..b57223466a 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Interpolatable.hpp @@ -5,9 +5,9 @@ namespace lottie { -double remapDouble(double value, double fromLow, double fromHigh, double toLow, double toHigh); +float remapFloat(float value, float fromLow, float fromHigh, float toLow, float toHigh); -double clampDouble(double value, double a, double b); +float clampFloat(float value, float a, float b); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp index 3d5797db3f..a3d12b226f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/Keyframe.hpp @@ -65,7 +65,7 @@ public: } public: - T interpolate(Keyframe const &to, double progress) { + T interpolate(Keyframe const &to, float progress) { std::optional spatialOutTangent2d; if (spatialOutTangent) { spatialOutTangent2d = Vector2D(spatialOutTangent->x, spatialOutTangent->y); @@ -78,9 +78,9 @@ public: } /// Interpolates the keyTime into a value from 0-1 - double interpolatedProgress(Keyframe const &to, double keyTime) { - double startTime = time; - double endTime = to.time; + float interpolatedProgress(Keyframe const &to, float keyTime) { + float startTime = time; + float endTime = to.time; if (keyTime <= startTime) { return 0.0; } @@ -100,8 +100,8 @@ public: if (to.inTangent.has_value()) { inTanPoint = to.inTangent.value(); } - double progress = remapDouble(keyTime, startTime, endTime, 0.0, 1.0); - if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0, 1.0)) { + float progress = remapFloat(keyTime, startTime, endTime, 0.0f, 1.0f); + if (!outTanPoint.isZero() || inTanPoint != Vector2D(1.0f, 1.0f)) { /// Cubic interpolation progress = cubicBezierInterpolate(progress, Vector2D::Zero(), outTanPoint, inTanPoint, Vector2D(1.0, 1.0)); } @@ -162,7 +162,10 @@ public: endValue = T(endValueData.value()); } - time = getOptionalDouble(json.object_items(), "t"); + if (const auto timeValue = getOptionalDouble(json.object_items(), "t")) { + time = (float)timeValue.value(); + } + hold = getOptionalInt(json.object_items(), "h"); if (const auto inTangentData = getOptionalObject(json.object_items(), "i")) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp index 6e91c29572..650d25e041 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.cpp @@ -4,16 +4,17 @@ namespace lottie { -void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, double amount) { +void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, float amount) { int elementCount = (int)from.size(); if (elementCount > (int)to.size()) { elementCount = (int)to.size(); } - static_assert(sizeof(PathElement) == 8 * 2 * 3); + static_assert(sizeof(PathElement) == 4 * 2 * 3); resultPath.setElementCount(elementCount); - vDSP_vintbD((double *)&from[0], 1, (double *)&to[0], 1, &amount, (double *)&resultPath.elements()[0], 1, elementCount * 2 * 3); + float floatAmount = (float)amount; + vDSP_vintb((float *)&from[0], 1, (float *)&to[0], 1, &floatAmount, (float *)&resultPath.elements()[0], 1, elementCount * 2 * 3); } } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp index d3c098a6a3..4cc473bfc9 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Keyframes/ValueInterpolators.hpp @@ -18,9 +18,9 @@ struct ValueInterpolator { }; template<> -struct ValueInterpolator { +struct ValueInterpolator { public: - static double interpolate(double value, double to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static float interpolate(float value, float to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { return value + ((to - value) * amount); } }; @@ -28,22 +28,22 @@ public: template<> struct ValueInterpolator { public: - static Vector1D interpolate(Vector1D const &value, Vector1D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { - return Vector1D(ValueInterpolator::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent)); + static Vector1D interpolate(Vector1D const &value, Vector1D const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + return Vector1D(ValueInterpolator::interpolate(value.value, to.value, amount, spatialOutTangent, spatialInTangent)); } }; template<> struct ValueInterpolator { public: - static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { auto cp1 = value + spatialOutTangent; auto cp2 = to + spatialInTangent; return value.interpolate(to, cp1, cp2, amount); } - static Vector2D interpolate(Vector2D const &value, Vector2D const &to, double amount) { + static Vector2D interpolate(Vector2D const &value, Vector2D const &to, float amount) { return value.interpolate(to, amount); } }; @@ -51,7 +51,7 @@ public: template<> struct ValueInterpolator { public: - static Vector3D interpolate(Vector3D const &value, Vector3D const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static Vector3D interpolate(Vector3D const &value, Vector3D const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { if (spatialOutTangent && spatialInTangent) { Vector2D from2d(value.x, value.y); Vector2D to2d(to.x, to.y); @@ -64,14 +64,14 @@ public: return Vector3D( result2d.x, result2d.y, - ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) ); } return Vector3D( - ValueInterpolator::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent), - ValueInterpolator::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent), - ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) + ValueInterpolator::interpolate(value.x, to.x, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.y, to.y, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.z, to.z, amount, spatialOutTangent, spatialInTangent) ); } }; @@ -79,22 +79,22 @@ public: template<> struct ValueInterpolator { public: - static Color interpolate(Color const &value, Color const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static Color interpolate(Color const &value, Color const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { return Color( - ValueInterpolator::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent), - ValueInterpolator::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent), - ValueInterpolator::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent), - ValueInterpolator::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent) + ValueInterpolator::interpolate(value.r, to.r, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.g, to.g, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.b, to.b, amount, spatialOutTangent, spatialInTangent), + ValueInterpolator::interpolate(value.a, to.a, amount, spatialOutTangent, spatialInTangent) ); } }; -void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, double amount); +void batchInterpolate(std::vector const &from, std::vector const &to, BezierPath &resultPath, float amount); template<> struct ValueInterpolator { public: - static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount, Vector2D spatialOutTangent, Vector2D spatialInTangent) { return CurveVertex::absolute( ValueInterpolator::interpolate(value.point, to.point, amount, spatialOutTangent, spatialInTangent), ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount, spatialOutTangent, spatialInTangent), @@ -102,7 +102,7 @@ public: ); } - static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, double amount) { + static CurveVertex interpolate(CurveVertex const &value, CurveVertex const &to, float amount) { return CurveVertex::absolute( ValueInterpolator::interpolate(value.point, to.point, amount), ValueInterpolator::interpolate(value.inTangent, to.inTangent, amount), @@ -114,7 +114,7 @@ public: template<> struct ValueInterpolator { public: - static BezierPath interpolate(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static BezierPath interpolate(BezierPath const &value, BezierPath const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { BezierPath newPath; newPath.reserveCapacity(std::max(value.elements().size(), to.elements().size())); //TODO:probably a bug in the upstream code, uncomment @@ -150,7 +150,7 @@ public: memcpy(resultPath.mutableElements().data(), value.elements().data(), value.elements().size() * sizeof(PathElement)); } - static void interpolateInplace(BezierPath const &value, BezierPath const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent, BezierPath &resultPath) { + static void interpolateInplace(BezierPath const &value, BezierPath const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent, BezierPath &resultPath) { /*if (value.elements().size() != to.elements().size()) { return to; }*/ @@ -185,7 +185,7 @@ public: template<> struct ValueInterpolator { public: - static TextDocument interpolate(TextDocument const &value, TextDocument const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static TextDocument interpolate(TextDocument const &value, TextDocument const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { if (amount == 1.0) { return to; } else { @@ -197,12 +197,12 @@ public: template<> struct ValueInterpolator { public: - static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static GradientColorSet interpolate(GradientColorSet const &value, GradientColorSet const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { assert(value.colors.size() == to.colors.size()); - std::vector colors; + std::vector colors; size_t colorCount = std::min(value.colors.size(), to.colors.size()); for (size_t i = 0; i < colorCount; i++) { - colors.push_back(ValueInterpolator::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent)); + colors.push_back(ValueInterpolator::interpolate(value.colors[i], to.colors[i], amount, spatialOutTangent, spatialInTangent)); } return GradientColorSet(colors); } @@ -211,12 +211,12 @@ public: template<> struct ValueInterpolator { public: - static DashPattern interpolate(DashPattern const &value, DashPattern const &to, double amount, std::optional spatialOutTangent, std::optional spatialInTangent) { + static DashPattern interpolate(DashPattern const &value, DashPattern const &to, float amount, std::optional spatialOutTangent, std::optional spatialInTangent) { assert(value.values.size() == to.values.size()); - std::vector values; + std::vector values; size_t colorCount = std::min(value.values.size(), to.values.size()); for (size_t i = 0; i < colorCount; i++) { - values.push_back(ValueInterpolator::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent)); + values.push_back(ValueInterpolator::interpolate(value.values[i], to.values[i], amount, spatialOutTangent, spatialInTangent)); } return DashPattern(std::move(values)); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp index 02a3e558d3..a2a7ec3fdd 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnimationTime.hpp @@ -4,10 +4,10 @@ namespace lottie { /// Defines animation time in Frames (Seconds * Framerate). -typedef double AnimationFrameTime; +typedef float AnimationFrameTime; /// Defines animation time by a progress from 0 (beginning of the animation) to 1 (end of the animation) -typedef double AnimationProgressTime; +typedef float AnimationProgressTime; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp index 0cd69ea851..f730fad652 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/AnyValue.hpp @@ -16,7 +16,7 @@ namespace lottie { class AnyValue { public: enum class Type { - Double, + Float, Vector1D, Vector2D, Vector3D, @@ -28,9 +28,9 @@ public: }; public: - AnyValue(double value) : - _type(Type::Double), - _doubleValue(value) { + AnyValue(float value) : + _type(Type::Float), + _floatValue(value) { } AnyValue(Vector1D const &value) : @@ -73,9 +73,9 @@ public: _dashPatternValue(value) { } - template::value>> - double get() { - return asDouble(); + template::value>> + float get() { + return asFloat(); } template::value>> @@ -123,8 +123,8 @@ public: return _type; } - double asDouble() { - return _doubleValue.value(); + float asFloat() { + return _floatValue.value(); } Vector1D asVector1D() { @@ -162,7 +162,7 @@ public: private: Type _type; - std::optional _doubleValue; + std::optional _floatValue; std::optional _vector1DValue; std::optional _vector2DValue; std::optional _vector3DValue; @@ -178,9 +178,9 @@ struct AnyValueType { }; template<> -struct AnyValueType { +struct AnyValueType { static AnyValue::Type type() { - return AnyValue::Type::Double; + return AnyValue::Type::Float; } }; diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp index 9f9fd4641f..c0c0a61006 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CALayer.hpp @@ -24,34 +24,13 @@ public: virtual ~CALayer() = default; void addSublayer(std::shared_ptr layer) { - if (layer->_superlayer) { - layer->_superlayer->removeSublayer(layer.get()); - } - layer->_superlayer = this; _sublayers.push_back(layer); } void insertSublayer(std::shared_ptr layer, int index) { - if (layer->_superlayer) { - layer->_superlayer->removeSublayer(layer.get()); - } - layer->_superlayer = this; _sublayers.insert(_sublayers.begin() + index, layer); } - void removeFromSuperlayer() { - if (_superlayer) { - _superlayer->removeSublayer(this); - } - } - - bool needsDisplay() const { - return _needsDisplay; - } - void setNeedsDisplay(bool needsDisplay) { - _needsDisplay = true; - } - virtual bool implementsDraw() const { return false; } @@ -78,28 +57,17 @@ public: _opacity = opacity; } - Vector2D const &position() const { - return _position; + Vector2D const &size() const { + return _size; } - void setPosition(Vector2D const &position) { - _position = position; + void setSize(Vector2D const &size) { + _size = size; } - CGRect const &bounds() const { - return _bounds; - } - void setBounds(CGRect const &bounds) { - _bounds = bounds; - } - - virtual CGRect effectiveBounds() const { - return bounds(); - } - - CATransform3D const &transform() const { + Transform2D const &transform() const { return _transform; } - void setTransform(CATransform3D const &transform) { + void setTransform(Transform2D const &transform) { _transform = transform; } @@ -138,7 +106,6 @@ private: void removeSublayer(CALayer *layer) { for (auto it = _sublayers.begin(); it != _sublayers.end(); it++) { if (it->get() == layer) { - layer->_superlayer = nullptr; _sublayers.erase(it); break; } @@ -146,14 +113,11 @@ private: } private: - CALayer *_superlayer = nullptr; std::vector> _sublayers; - bool _needsDisplay = false; bool _isHidden = false; float _opacity = 1.0; - Vector2D _position = Vector2D(0.0, 0.0); - CGRect _bounds = CGRect(0.0, 0.0, 0.0, 0.0); - CATransform3D _transform = CATransform3D::identity(); + Vector2D _size = Vector2D(0.0, 0.0); + Transform2D _transform = Transform2D::identity(); std::shared_ptr _mask; bool _masksToBounds = false; std::optional _compositingFilter; @@ -194,10 +158,10 @@ public: _path = path; } - double lineWidth() const { + float lineWidth() const { return _lineWidth; } - void setLineWidth(double lineWidth) { + void setLineWidth(float lineWidth) { _lineWidth = lineWidth; } @@ -215,35 +179,20 @@ public: _lineCap = lineCap; } - double lineDashPhase() const { + float lineDashPhase() const { return _lineDashPhase; } - void setLineDashPhase(double lineDashPhase) { + void setLineDashPhase(float lineDashPhase) { _lineDashPhase = lineDashPhase; } - std::vector const &dashPattern() const { + std::vector const &dashPattern() const { return _dashPattern; } - void setDashPattern(std::vector const &dashPattern) { + void setDashPattern(std::vector const &dashPattern) { _dashPattern = dashPattern; } - virtual CGRect effectiveBounds() const override { - if (_path) { - CGRect boundingBox = _path->boundingBox(); - if (_strokeColor) { - boundingBox.x -= _lineWidth / 2.0; - boundingBox.y -= _lineWidth / 2.0; - boundingBox.width += _lineWidth; - boundingBox.height += _lineWidth; - } - return boundingBox; - } else { - return CGRect(0.0, 0.0, 0.0, 0.0); - } - } - std::shared_ptr renderableItem() override; private: @@ -251,11 +200,11 @@ private: std::optional _fillColor = Color(0.0, 0.0, 0.0, 1.0); FillRule _fillRule = FillRule::NonZeroWinding; std::shared_ptr _path; - double _lineWidth = 1.0; + float _lineWidth = 1.0; LineJoin _lineJoin = LineJoin::Miter; LineCap _lineCap = LineCap::Butt; - double _lineDashPhase = 0.0; - std::vector _dashPattern; + float _lineDashPhase = 0.0; + std::vector _dashPattern; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp index 9b174bed67..e985e18392 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.cpp @@ -35,11 +35,10 @@ void addPointToBoundingRect(bool *isFirst, CGRect *rect, Vector2D const *point) } -Vector2D transformVector(Vector2D const &v, CATransform3D const &m) { - return Vector2D( - m.m11 * v.x + m.m21 * v.y + m.m41 * 1.0, - m.m12 * v.x + m.m22 * v.y + m.m42 * 1.0 - ); +Vector2D transformVector(Vector2D const &v, Transform2D const &m) { + float transformedX = m.rows().columns[0][0] * v.x + m.rows().columns[1][0] * v.y + m.rows().columns[2][0] * 1.0f; + float transformedY = m.rows().columns[0][1] * v.x + m.rows().columns[1][1] * v.y + m.rows().columns[2][1] * 1.0f; + return Vector2D(transformedX, transformedY); } class CGPathImpl: public CGPath { @@ -51,7 +50,7 @@ public: virtual bool empty() const override; - virtual std::shared_ptr copyUsingTransform(CATransform3D const &transform) const override; + virtual std::shared_ptr copyUsingTransform(Transform2D const &transform) const override; virtual void addLineTo(Vector2D const &point) override; virtual void addCurveTo(Vector2D const &point, Vector2D const &control1, Vector2D const &control2) override; @@ -108,10 +107,10 @@ bool CGPathImpl::empty() const { return _items.empty(); } -std::shared_ptr CGPathImpl::copyUsingTransform(CATransform3D const &transform) const { +std::shared_ptr CGPathImpl::copyUsingTransform(Transform2D const &transform) const { auto result = std::make_shared(); - if (transform == CATransform3D::identity()) { + if (transform == Transform2D::identity()) { result->_items = _items; return result; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm index e80342007f..93a5e242a1 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/CGPath.mm @@ -92,29 +92,12 @@ bool CGPathCocoaImpl::empty() const { return CGPathIsEmpty(_path); } -std::shared_ptr CGPathCocoaImpl::copyUsingTransform(CATransform3D const &transform) const { - ::CATransform3D nativeTransform; - nativeTransform.m11 = transform.m11; - nativeTransform.m12 = transform.m12; - nativeTransform.m13 = transform.m13; - nativeTransform.m14 = transform.m14; - - nativeTransform.m21 = transform.m21; - nativeTransform.m22 = transform.m22; - nativeTransform.m23 = transform.m23; - nativeTransform.m24 = transform.m24; - - nativeTransform.m31 = transform.m31; - nativeTransform.m32 = transform.m32; - nativeTransform.m33 = transform.m33; - nativeTransform.m34 = transform.m34; - - nativeTransform.m41 = transform.m41; - nativeTransform.m42 = transform.m42; - nativeTransform.m43 = transform.m43; - nativeTransform.m44 = transform.m44; - - auto affineTransform = CATransform3DGetAffineTransform(nativeTransform); +std::shared_ptr CGPathCocoaImpl::copyUsingTransform(Transform2D const &transform) const { + CGAffineTransform affineTransform = CGAffineTransformMake( + transform.rows().columns[0][0], transform.rows().columns[0][1], + transform.rows().columns[1][0], transform.rows().columns[1][1], + transform.rows().columns[2][0], transform.rows().columns[2][1] + ); CGPathRef resultPath = CGPathCreateCopyByTransformingPath(_path, &affineTransform); if (resultPath == nil) { diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp index e7c6b9996e..d627e71da4 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Color.cpp @@ -6,8 +6,8 @@ namespace lottie { -Color::Color(double r_, double g_, double b_, double a_, ColorFormatDenominator denominator) { - double denominatorValue = 1.0; +Color::Color(float r_, float g_, float b_, float a_, ColorFormatDenominator denominator) { + float denominatorValue = 1.0; switch (denominator) { case ColorFormatDenominator::One: { denominatorValue = 1.0; @@ -43,7 +43,7 @@ Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) : size_t index = 0; - double r1 = 0.0; + float r1 = 0.0; if (index < jsonAny.array_items().size()) { if (!jsonAny.array_items()[index].is_number()) { throw LottieParsingException(); @@ -52,7 +52,7 @@ Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) : index++; } - double g1 = 0.0; + float g1 = 0.0; if (index < jsonAny.array_items().size()) { if (!jsonAny.array_items()[index].is_number()) { throw LottieParsingException(); @@ -61,7 +61,7 @@ Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) : index++; } - double b1 = 0.0; + float b1 = 0.0; if (index < jsonAny.array_items().size()) { if (!jsonAny.array_items()[index].is_number()) { throw LottieParsingException(); @@ -70,7 +70,7 @@ Color::Color(lottiejson11::Json const &jsonAny) noexcept(false) : index++; } - double a1 = 0.0; + float a1 = 0.0; if (index < jsonAny.array_items().size()) { if (!jsonAny.array_items()[index].is_number()) { throw LottieParsingException(); @@ -118,9 +118,9 @@ Color Color::fromString(std::string const &string) { converter >> std::hex >> rgbValue; return Color( - ((double)((rgbValue & 0xFF0000) >> 16)) / 255.0, - ((double)((rgbValue & 0x00FF00) >> 8)) / 255.0, - ((double)(rgbValue & 0x0000FF)) / 255.0, + ((float)((rgbValue & 0xFF0000) >> 16)) / 255.0, + ((float)((rgbValue & 0x00FF00) >> 8)) / 255.0, + ((float)(rgbValue & 0x0000FF)) / 255.0, 1.0 ); } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp index d22ee1e4d8..e6255ec821 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/DashPattern.hpp @@ -6,11 +6,11 @@ namespace lottie { struct DashPattern { - DashPattern(std::vector &&values_) : + DashPattern(std::vector &&values_) : values(std::move(values_)) { } - std::vector values; + std::vector values; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp index d37448999a..abf576690f 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/GradientColorSet.hpp @@ -34,7 +34,7 @@ struct GradientColorSet { return result; } - std::vector colors; + std::vector colors; }; } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp deleted file mode 100644 index eec19c1596..0000000000 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "RenderTree.hpp" - -namespace lottie { - -BoundingBoxNode::BoundingBoxNode( - LayerParams const &layer_, - CGRect const &globalRect_, - CGRect const &localRect_, - CATransform3D const &globalTransform_, - bool drawsContent_, - std::shared_ptr renderableItem_, - bool isInvertedMatte_, - std::vector> const &subnodes_, - std::shared_ptr const &mask_ -) : -layer(layer_), -globalRect(globalRect_), -localRect(localRect_), -globalTransform(globalTransform_), -drawsContent(drawsContent_), -renderableItem(renderableItem_), -isInvertedMatte(isInvertedMatte_), -subnodes(subnodes_), -mask(mask_) { -} - -std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform) { - if (layer->isHidden() || layer->opacity() == 0.0f) { - return nullptr; - } - - if (layer->masksToBounds()) { - if (layer->bounds().empty()) { - return nullptr; - } - } - - auto currentTransform = parentTransform; - - currentTransform = currentTransform.translated(Vector2D(layer->position().x, layer->position().y)); - currentTransform = currentTransform.translated(Vector2D(-layer->bounds().x, -layer->bounds().y)); - currentTransform = layer->transform() * currentTransform; - - if (!currentTransform.isInvertible()) { - return nullptr; - } - - std::optional effectiveLocalBounds; - - auto renderableItem = layer->renderableItem(); - if (renderableItem) { - effectiveLocalBounds = renderableItem->boundingRect(); - } else if (layer->implementsDraw()) { - effectiveLocalBounds = layer->bounds(); - } - - bool isInvertedMatte = layer->isInvertedMatte(); - if (isInvertedMatte) { - effectiveLocalBounds = layer->bounds(); - } - - if (effectiveLocalBounds && effectiveLocalBounds->empty()) { - effectiveLocalBounds = std::nullopt; - } - - std::vector> subnodes; - std::optional subnodesGlobalRect; - - for (const auto &sublayer : layer->sublayers()) { - if (const auto subnode = boundingBoxTree(sublayer, globalSize, currentTransform)) { - subnodes.push_back(subnode); - - if (subnodesGlobalRect) { - subnodesGlobalRect = subnodesGlobalRect->unionWith(subnode->globalRect); - } else { - subnodesGlobalRect = subnode->globalRect; - } - } - } - - std::optional fuzzyGlobalRect; - - if (effectiveLocalBounds) { - CGRect effectiveGlobalBounds = effectiveLocalBounds->applyingTransform(currentTransform); - if (fuzzyGlobalRect) { - fuzzyGlobalRect = fuzzyGlobalRect->unionWith(effectiveGlobalBounds); - } else { - fuzzyGlobalRect = effectiveGlobalBounds; - } - } - - if (subnodesGlobalRect) { - if (fuzzyGlobalRect) { - fuzzyGlobalRect = fuzzyGlobalRect->unionWith(subnodesGlobalRect.value()); - } else { - fuzzyGlobalRect = subnodesGlobalRect; - } - } - - if (!fuzzyGlobalRect) { - return nullptr; - } - - CGRect globalRect( - std::floor(fuzzyGlobalRect->x), - std::floor(fuzzyGlobalRect->y), - std::ceil(fuzzyGlobalRect->width + fuzzyGlobalRect->x - floor(fuzzyGlobalRect->x)), - std::ceil(fuzzyGlobalRect->height + fuzzyGlobalRect->y - floor(fuzzyGlobalRect->y)) - ); - - if (!CGRect(0.0, 0.0, globalSize.x, globalSize.y).intersects(globalRect)) { - return nullptr; - } - - std::shared_ptr maskNode; - if (layer->mask()) { - if (const auto maskNodeValue = boundingBoxTree(layer->mask(), globalSize, currentTransform)) { - if (!maskNodeValue->globalRect.intersects(globalRect)) { - return nullptr; - } - maskNode = maskNodeValue; - } else { - return nullptr; - } - } - - return std::make_shared( - layer, - globalRect, - CGRect(0.0, 0.0, 0.0, 0.0), - currentTransform, - effectiveLocalBounds.has_value(), - renderableItem, - isInvertedMatte, - subnodes, - maskNode - ); -} - -} diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp deleted file mode 100644 index 3f190522a0..0000000000 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/RenderTree.hpp +++ /dev/null @@ -1,172 +0,0 @@ -#ifndef RenderTree_hpp -#define RenderTree_hpp - -#include - -#include -#include "Lottie/Public/Primitives/CALayer.hpp" - -namespace lottie { - -struct BoundingBoxNode { - struct LayerParams { - CGRect _bounds; - Vector2D _position; - CATransform3D _transform; - double _opacity; - bool _masksToBounds; - bool _isHidden; - - LayerParams( - CGRect bounds_, - Vector2D position_, - CATransform3D transform_, - double opacity_, - bool masksToBounds_, - bool isHidden_ - ) : - _bounds(bounds_), - _position(position_), - _transform(transform_), - _opacity(opacity_), - _masksToBounds(masksToBounds_), - _isHidden(isHidden_) { - } - - LayerParams(std::shared_ptr const &layer) : - _bounds(layer->bounds()), - _position(layer->position()), - _transform(layer->transform()), - _opacity(layer->opacity()), - _masksToBounds(layer->masksToBounds()), - _isHidden(layer->isHidden()) { - } - - bool operator==(LayerParams const &rhs) const { - if (_bounds != rhs._bounds) { - return false; - } - if (_position != rhs._position) { - return false; - } - if (_transform != rhs._transform) { - return false; - } - if (_opacity != rhs._opacity) { - return false; - } - if (_masksToBounds != rhs._masksToBounds) { - return false; - } - if (_isHidden != rhs._isHidden) { - return false; - } - return true; - } - - bool operator!=(LayerParams const &rhs) const { - return !(*this == rhs); - } - - CGRect bounds() const { - return _bounds; - } - - Vector2D position() const { - return _position; - } - - CATransform3D transform() const { - return _transform; - } - - double opacity() const { - return _opacity; - } - - bool masksToBounds() const { - return _masksToBounds; - } - - bool isHidden() const { - return _isHidden; - } - }; - - LayerParams layer; - CGRect globalRect; - CGRect localRect; - CATransform3D globalTransform; - bool drawsContent; - std::shared_ptr renderableItem; - bool isInvertedMatte; - std::vector> subnodes; - std::shared_ptr mask; - - explicit BoundingBoxNode( - LayerParams const &layer_, - CGRect const &globalRect_, - CGRect const &localRect_, - CATransform3D const &globalTransform_, - bool drawsContent_, - std::shared_ptr renderableItem_, - bool isInvertedMatte_, - std::vector> const &subnodes_, - std::shared_ptr const &mask_ - ); - - bool operator==(BoundingBoxNode const &rhs) const { - if (layer != rhs.layer) { - return false; - } - if (globalRect != rhs.globalRect) { - return false; - } - if (localRect != rhs.localRect) { - return false; - } - if (globalTransform != rhs.globalTransform) { - return false; - } - if (drawsContent != rhs.drawsContent) { - return false; - } - if ((renderableItem == nullptr) != (rhs.renderableItem == nullptr)) { - return false; - } else if (renderableItem) { - if (!renderableItem->isEqual(rhs.renderableItem)) { - return false; - } - } - if (isInvertedMatte != rhs.isInvertedMatte) { - return false; - } - if (subnodes.size() != rhs.subnodes.size()) { - return false; - } else { - for (size_t i = 0; i < subnodes.size(); i++) { - if ((*subnodes[i].get()) != (*rhs.subnodes[i].get())) { - return false; - } - } - } - if ((mask == nullptr) != (rhs.mask == nullptr)) { - return false; - } else if (mask) { - if ((*mask.get()) != *(rhs.mask.get())) { - return false; - } - } - return true; - } - - bool operator!=(BoundingBoxNode const &rhs) const { - return !(*this == rhs); - } -}; - -std::shared_ptr boundingBoxTree(std::shared_ptr const &layer, Vector2D const &globalSize, CATransform3D const &parentTransform); - -} - -#endif /* RenderTree_hpp */ diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm index 81a0f4ff73..3695d35aa0 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/Lottie/Public/Primitives/Vectors.mm @@ -8,10 +8,261 @@ #import -#import - namespace lottie { +/*explicit Transform2D(Transform3D const &t) { + CGAffineTransform at = CATransform3DGetAffineTransform(nativeTransform(t)); + _rows.columns[0] = simd_make_float3(at.a, at.b, 0.0); + _rows.columns[1] = simd_make_float3(at.c, at.d, 0.0); + _rows.columns[2] = simd_make_float3(at.tx, at.ty, 1.0); +} + + Transform3D transform3D() { + CGAffineTransform at = CGAffineTransformMake( + _rows.columns[0][0], _rows.columns[0][1], + _rows.columns[1][0], _rows.columns[1][1], + _rows.columns[2][0], _rows.columns[2][1] + ); + return fromNativeTransform(CATransform3DMakeAffineTransform(at)); + }*/ + +/*struct Transform3D { + float m11, m12, m13, m14; + float m21, m22, m23, m24; + float m31, m32, m33, m34; + float m41, m42, m43, m44; + + Transform3D( + float m11_, float m12_, float m13_, float m14_, + float m21_, float m22_, float m23_, float m24_, + float m31_, float m32_, float m33_, float m34_, + float m41_, float m42_, float m43_, float m44_ + ) : + m11(m11_), m12(m12_), m13(m13_), m14(m14_), + m21(m21_), m22(m22_), m23(m23_), m24(m24_), + m31(m31_), m32(m32_), m33(m33_), m34(m34_), + m41(m41_), m42(m42_), m43(m43_), m44(m44_) { + } + + bool operator==(Transform3D const &rhs) const { + return m11 == rhs.m11 && m12 == rhs.m12 && m13 == rhs.m13 && m14 == rhs.m14 && + m21 == rhs.m21 && m22 == rhs.m22 && m23 == rhs.m23 && m24 == rhs.m24 && + m31 == rhs.m31 && m32 == rhs.m32 && m33 == rhs.m33 && m34 == rhs.m34 && + m41 == rhs.m41 && m42 == rhs.m42 && m43 == rhs.m43 && m44 == rhs.m44; + } + + bool operator!=(Transform3D const &rhs) const { + return !(*this == rhs); + } + + inline bool isIdentity() const { + return m11 == 1.0 && m12 == 0.0 && m13 == 0.0 && m14 == 0.0 && + m21 == 0.0 && m22 == 1.0 && m23 == 0.0 && m24 == 0.0 && + m31 == 0.0 && m32 == 0.0 && m33 == 1.0 && m34 == 0.0 && + m41 == 0.0 && m42 == 0.0 && m43 == 0.0 && m44 == 1.0; + } + + static Transform3D makeTranslation(float tx, float ty, float tz) { + return Transform3D( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + tx, ty, tz, 1 + ); + } + + static Transform3D makeScale(float sx, float sy, float sz) { + return Transform3D( + sx, 0, 0, 0, + 0, sy, 0, 0, + 0, 0, sz, 0, + 0, 0, 0, 1 + ); + } + + static Transform3D makeRotation(float radians); + + static Transform3D makeSkew(float skew, float skewAxis) { + float mCos = cos(degreesToRadians(skewAxis)); + float mSin = sin(degreesToRadians(skewAxis)); + float aTan = tan(degreesToRadians(skew)); + + Transform3D transform1( + mCos, + mSin, + 0.0, + 0.0, + -mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + Transform3D transform2( + 1.0, + 0.0, + 0.0, + 0.0, + aTan, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + Transform3D transform3( + mCos, + -mSin, + 0.0, + 0.0, + mSin, + mCos, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ); + + return transform3 * transform2 * transform1; + } + + static Transform3D makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis + ) { + Transform3D result = Transform3D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = Transform3D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = Transform3D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; + } + + Transform3D rotated(float degrees) const; + + Transform3D translated(Vector2D const &translation) const; + + Transform3D scaled(Vector2D const &scale) const; + + Transform3D skewed(float skew, float skewAxis) const { + return Transform3D::makeSkew(skew, skewAxis) * (*this); + } + + static Transform3D identity() { + return Transform3D( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + ); + } + + Transform3D operator*(Transform3D const &b) const; +};*/ + +/*Transform2D t2d(Transform3D const &testMatrix) { + ::CATransform3D nativeTest; + + nativeTest.m11 = testMatrix.m11; + nativeTest.m12 = testMatrix.m12; + nativeTest.m13 = testMatrix.m13; + nativeTest.m14 = testMatrix.m14; + + nativeTest.m21 = testMatrix.m21; + nativeTest.m22 = testMatrix.m22; + nativeTest.m23 = testMatrix.m23; + nativeTest.m24 = testMatrix.m24; + + nativeTest.m31 = testMatrix.m31; + nativeTest.m32 = testMatrix.m32; + nativeTest.m33 = testMatrix.m33; + nativeTest.m34 = testMatrix.m34; + + nativeTest.m41 = testMatrix.m41; + nativeTest.m42 = testMatrix.m42; + nativeTest.m43 = testMatrix.m43; + nativeTest.m44 = testMatrix.m44; + + CGAffineTransform at = CATransform3DGetAffineTransform(nativeTest); + Transform2D result = Transform2D::identity(); + simd_float3x3 *rows = (simd_float3x3 *)&result.rows(); + rows->columns[0] = simd_make_float3(at.a, at.b, 0.0); + rows->columns[1] = simd_make_float3(at.c, at.d, 0.0); + rows->columns[2] = simd_make_float3(at.tx, at.ty, 1.0); + + return result; +} + +Transform3D t3d(Transform2D const &t) { + CGAffineTransform at = CGAffineTransformMake( + t.rows().columns[0][0], t.rows().columns[0][1], + t.rows().columns[1][0], t.rows().columns[1][1], + t.rows().columns[2][0], t.rows().columns[2][1] + ); + ::CATransform3D value = CATransform3DMakeAffineTransform(at); + + Transform3D result = Transform3D::identity(); + result.m11 = value.m11; + result.m12 = value.m12; + result.m13 = value.m13; + result.m14 = value.m14; + + result.m21 = value.m21; + result.m22 = value.m22; + result.m23 = value.m23; + result.m24 = value.m24; + + result.m31 = value.m31; + result.m32 = value.m32; + result.m33 = value.m33; + result.m34 = value.m34; + + result.m41 = value.m41; + result.m42 = value.m42; + result.m43 = value.m43; + result.m44 = value.m44; + + return result; +} + +Transform3D Transform3D::operator*(Transform3D const &b) const { + if (isIdentity()) { + return b; + } + if (b.isIdentity()) { + return *this; + } + + return t3d((t2d(*this) * t2d(b))); +}*/ + Vector1D::Vector1D(lottiejson11::Json const &json) noexcept(false) { if (json.is_number()) { value = json.number_value(); @@ -140,21 +391,116 @@ lottiejson11::Json Vector3D::toJson() const { return lottiejson11::Json(result); } -CATransform3D CATransform3D::_identity = CATransform3D( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 1.0, 0.0, - 0.0, 0.0, 0.0, 1.0 +Transform2D Transform2D::_identity = Transform2D( + simd_float3x3({ + simd_make_float3(1.0f, 0.0f, 0.0f), + simd_make_float3(0.0f, 1.0f, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + }) ); -double interpolate(double value, double to, double amount) { +Transform2D Transform2D::makeTranslation(float tx, float ty) { + return Transform2D(simd_float3x3({ + simd_make_float3(1.0f, 0.0f, 0.0f), + simd_make_float3(0.0f, 1.0f, 0.0f), + simd_make_float3(tx, ty, 1.0f) + })); +} + +Transform2D Transform2D::makeScale(float sx, float sy) { + return Transform2D(simd_float3x3({ + simd_make_float3(sx, 0.0f, 0.0f), + simd_make_float3(0.0f, sy, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + })); +} + +Transform2D Transform2D::makeRotation(float radians) { + float c = cos(radians); + float s = sin(radians); + + return Transform2D(simd_float3x3({ + simd_make_float3(c, s, 0.0f), + simd_make_float3(-s, c, 0.0f), + simd_make_float3(0.0f, 0.0f, 1.0f) + })); +} + +Transform2D Transform2D::makeSkew(float skew, float skewAxis) { + if (std::abs(skew) <= FLT_EPSILON && std::abs(skewAxis) <= FLT_EPSILON) { + return Transform2D::identity(); + } + + float mCos = cos(degreesToRadians(skewAxis)); + float mSin = sin(degreesToRadians(skewAxis)); + float aTan = tan(degreesToRadians(skew)); + + simd_float3x3 simd1 = simd_float3x3({ + simd_make_float3(mCos, -mSin, 0.0), + simd_make_float3(mSin, mCos, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 simd2 = simd_float3x3({ + simd_make_float3(1.0, 0.0, 0.0), + simd_make_float3(aTan, 1.0, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 simd3 = simd_float3x3({ + simd_make_float3(mCos, mSin, 0.0), + simd_make_float3(-mSin, mCos, 0.0), + simd_make_float3(0.0, 0.0, 1.0) + }); + + simd_float3x3 result = simd_mul(simd_mul(simd3, simd2), simd1); + Transform2D resultTransform(result); + + return resultTransform; +} + +Transform2D Transform2D::makeTransform( + Vector2D const &anchor, + Vector2D const &position, + Vector2D const &scale, + float rotation, + std::optional skew, + std::optional skewAxis +) { + Transform2D result = Transform2D::identity(); + if (skew.has_value() && skewAxis.has_value()) { + result = Transform2D::identity().translated(position).rotated(rotation).skewed(-skew.value(), skewAxis.value()).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } else { + result = Transform2D::identity().translated(position).rotated(rotation).scaled(Vector2D(scale.x * 0.01, scale.y * 0.01)).translated(Vector2D(-anchor.x, -anchor.y)); + } + + return result; +} + +Transform2D Transform2D::rotated(float degrees) const { + return Transform2D::makeRotation(degreesToRadians(degrees)) * (*this); +} + +Transform2D Transform2D::translated(Vector2D const &translation) const { + return Transform2D::makeTranslation(translation.x, translation.y) * (*this); +} + +Transform2D Transform2D::scaled(Vector2D const &scale) const { + return Transform2D::makeScale(scale.x, scale.y) * (*this); +} + +Transform2D Transform2D::skewed(float skew, float skewAxis) const { + return Transform2D::makeSkew(skew, skewAxis) * (*this); +} + +float interpolate(float value, float to, float amount) { return value + ((to - value) * amount); } Vector1D interpolate( Vector1D const &from, Vector1D const &to, - double amount + float amount ) { return Vector1D(interpolate(from.value, to.value, amount)); } @@ -162,7 +508,7 @@ Vector1D interpolate( Vector2D interpolate( Vector2D const &from, Vector2D const &to, - double amount + float amount ) { return Vector2D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount)); } @@ -171,17 +517,17 @@ Vector2D interpolate( Vector3D interpolate( Vector3D const &from, Vector3D const &to, - double amount + float amount ) { return Vector3D(interpolate(from.x, to.x, amount), interpolate(from.y, to.y, amount), interpolate(from.z, to.z, amount)); } -static double cubicRoot(double value) { +static float cubicRoot(float value) { return pow(value, 1.0 / 3.0); } -static double SolveQuadratic(double a, double b, double c) { - double result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a); +static float SolveQuadratic(float a, float b, float c) { + float result = (-b + sqrt((b * b) - 4 * a * c)) / (2 * a); if (isInRangeOrEqual(result, 0.0, 1.0)) { return result; } @@ -194,35 +540,39 @@ static double SolveQuadratic(double a, double b, double c) { return -1.0; } -static double SolveCubic(double a, double b, double c, double d) { - if (a == 0.0) { +inline bool isApproximatelyEqual(float value, float other) { + return std::abs(value - other) <= FLT_EPSILON; +} + +static float SolveCubic(float a, float b, float c, float d) { + if (isApproximatelyEqual(a, 0.0f)) { return SolveQuadratic(b, c, d); } - if (d == 0.0) { + if (isApproximatelyEqual(d, 0.0f)) { return 0.0; } b /= a; c /= a; d /= a; - double q = (3.0 * c - (b * b)) / 9.0; - double r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0; - double disc = (q * q * q) + (r * r); - double term1 = b / 3.0; + float q = (3.0 * c - (b * b)) / 9.0; + float r = (-27.0 * d + b * (9.0 * c - 2.0 * (b * b))) / 54.0; + float disc = (q * q * q) + (r * r); + float term1 = b / 3.0; if (disc > 0.0) { - double s = r + sqrt(disc); + float s = r + sqrt(disc); s = (s < 0) ? -cubicRoot(-s) : cubicRoot(s); - double t = r - sqrt(disc); + float t = r - sqrt(disc); t = (t < 0) ? -cubicRoot(-t) : cubicRoot(t); - double result = -term1 + s + t; + float result = -term1 + s + t; if (isInRangeOrEqual(result, 0.0, 1.0)) { return result; } - } else if (disc == 0) { - double r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r); + } else if (isApproximatelyEqual(disc, 0.0f)) { + float r13 = (r < 0) ? -cubicRoot(-r) : cubicRoot(r); - double result = -term1 + 2.0 * r13; + float result = -term1 + 2.0 * r13; if (isInRangeOrEqual(result, 0.0, 1.0)) { return result; } @@ -233,11 +583,11 @@ static double SolveCubic(double a, double b, double c, double d) { } } else { q = -q; - double dum1 = q * q * q; + float dum1 = q * q * q; dum1 = acos(r / sqrt(dum1)); - double r13 = 2.0 * sqrt(q); + float r13 = 2.0 * sqrt(q); - double result = -term1 + r13 * cos(dum1 / 3.0); + float result = -term1 + r13 * cos(dum1 / 3.0); if (isInRangeOrEqual(result, 0.0, 1.0)) { return result; } @@ -251,50 +601,50 @@ static double SolveCubic(double a, double b, double c, double d) { } } - return -1; + return -1.0; } -double cubicBezierInterpolate(double value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) { - double t = 0.0; - if (value == P0.x) { +float cubicBezierInterpolate(float value, Vector2D const &P0, Vector2D const &P1, Vector2D const &P2, Vector2D const &P3) { + float t = 0.0; + if (isApproximatelyEqual(value, P0.x)) { // Handle corner cases explicitly to prevent rounding errors t = 0.0; - } else if (value == P3.x) { + } else if (isApproximatelyEqual(value, P3.x)) { t = 1.0; } else { // Calculate t - double a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; - double b = 3 * P0.x - 6 * P1.x + 3 * P2.x; - double c = -3 * P0.x + 3 * P1.x; - double d = P0.x - value; - double tTemp = SolveCubic(a, b, c, d); - if (tTemp == -1.0) { + float a = -P0.x + 3 * P1.x - 3 * P2.x + P3.x; + float b = 3 * P0.x - 6 * P1.x + 3 * P2.x; + float c = -3 * P0.x + 3 * P1.x; + float d = P0.x - value; + float tTemp = SolveCubic(a, b, c, d); + if (isApproximatelyEqual(tTemp, -1.0f)) { return -1.0; } t = tTemp; } // Calculate y from t - double oneMinusT = 1.0 - t; + float oneMinusT = 1.0 - t; return (oneMinusT * oneMinusT * oneMinusT) * P0.y + 3 * t * (oneMinusT * oneMinusT) * P1.y + 3 * (t * t) * (1 - t) * P2.y + (t * t * t) * P3.y; } struct InterpolationPoint2D { - InterpolationPoint2D(Vector2D const point_, double distance_) : + InterpolationPoint2D(Vector2D const point_, float distance_) : point(point_), distance(distance_) { } Vector2D point; - double distance; + float distance; }; namespace { - double interpolateDouble(double value, double to, double amount) { + float interpolateFloat(float value, float to, float amount) { return value + ((to - value) * amount); } } -Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, double amount) const { +Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, float amount) const { auto a = interpolate(outTangent, amount); auto b = outTangent.interpolate(inTangent, amount); auto c = inTangent.interpolate(to, amount); @@ -304,10 +654,10 @@ Vector2D Vector2D::pointOnPath(Vector2D const &to, Vector2D const &outTangent, V return f; } -Vector2D Vector2D::interpolate(Vector2D const &to, double amount) const { +Vector2D Vector2D::interpolate(Vector2D const &to, float amount) const { return Vector2D( - interpolateDouble(x, to.x, amount), - interpolateDouble(y, to.y, amount) + interpolateFloat(x, to.x, amount), + interpolateFloat(y, to.y, amount) ); } @@ -315,10 +665,10 @@ Vector2D Vector2D::interpolate( Vector2D const &to, Vector2D const &outTangent, Vector2D const &inTangent, - double amount, + float amount, int maxIterations, int samples, - double accuracy + float accuracy ) const { if (amount == 0.0) { return *this; @@ -331,14 +681,14 @@ Vector2D Vector2D::interpolate( return interpolate(to, amount); } - double step = 1.0 / (double)samples; + float step = 1.0 / (float)samples; std::vector points; points.push_back(InterpolationPoint2D(*this, 0.0)); - double totalLength = 0.0; + float totalLength = 0.0; Vector2D previousPoint = *this; - double previousAmount = 0.0; + float previousAmount = 0.0; int closestPoint = 0; @@ -356,13 +706,13 @@ Vector2D Vector2D::interpolate( previousPoint = newPoint; } - double accurateDistance = amount * totalLength; + float accurateDistance = amount * totalLength; auto point = points[closestPoint]; bool foundPoint = false; - double pointAmount = ((double)closestPoint) * step; - double nextPointAmount = pointAmount + step; + float pointAmount = ((float)closestPoint) * step; + float nextPointAmount = pointAmount + step; int refineIterations = 0; while (!foundPoint) { @@ -372,7 +722,7 @@ Vector2D Vector2D::interpolate( if (nextPoint.distance < accurateDistance) { point = nextPoint; closestPoint = closestPoint + 1; - pointAmount = ((double)closestPoint) * step; + pointAmount = ((float)closestPoint) * step; nextPointAmount = pointAmount + step; if (closestPoint == (int)points.size()) { foundPoint = true; @@ -386,14 +736,14 @@ Vector2D Vector2D::interpolate( continue; } point = points[closestPoint]; - pointAmount = ((double)closestPoint) * step; + pointAmount = ((float)closestPoint) * step; nextPointAmount = pointAmount + step; continue; } /// Now we are certain the point is the closest point under the distance auto pointDiff = nextPoint.distance - point.distance; - auto proposedPointAmount = remapDouble((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount); + auto proposedPointAmount = remapFloat((accurateDistance - point.distance) / pointDiff, 0.0, 1.0, pointAmount, nextPointAmount); auto newPoint = pointOnPath(to, outTangent, inTangent, proposedPointAmount); auto newDistance = point.distance + point.point.distanceTo(newPoint); @@ -411,8 +761,15 @@ Vector2D Vector2D::interpolate( return point.point; } -::CATransform3D nativeTransform(CATransform3D const &value) { - ::CATransform3D result; +::CATransform3D nativeTransform(Transform2D const &value) { + CGAffineTransform at = CGAffineTransformMake( + value.rows().columns[0][0], value.rows().columns[0][1], + value.rows().columns[1][0], value.rows().columns[1][1], + value.rows().columns[2][0], value.rows().columns[2][1] + ); + return CATransform3DMakeAffineTransform(at); + + /*::CATransform3D result; result.m11 = value.m11; result.m12 = value.m12; @@ -434,11 +791,20 @@ Vector2D Vector2D::interpolate( result.m43 = value.m43; result.m44 = value.m44; - return result; + return result;*/ } -CATransform3D fromNativeTransform(::CATransform3D const &value) { - CATransform3D result = CATransform3D::identity(); +Transform2D fromNativeTransform(::CATransform3D const &value) { + CGAffineTransform at = CATransform3DGetAffineTransform(value); + return Transform2D( + simd_float3x3({ + simd_make_float3(at.a, at.b, 0.0), + simd_make_float3(at.c, at.d, 0.0), + simd_make_float3(at.tx, at.ty, 1.0) + }) + ); + + /*Transform2D result = Transform2D::identity(); result.m11 = value.m11; result.m12 = value.m12; @@ -460,103 +826,41 @@ CATransform3D fromNativeTransform(::CATransform3D const &value) { result.m43 = value.m43; result.m44 = value.m44; - return result; + return result;*/ } -CATransform3D CATransform3D::makeRotation(double radians, double x, double y, double z) { - return fromNativeTransform(CATransform3DMakeRotation(radians, x, y, z)); - - /*if (x == 0.0 && y == 0.0 && z == 0.0) { - return CATransform3D::identity(); +/*Transform3D Transform3D::makeRotation(float radians) { + if (std::abs(radians) <= FLT_EPSILON) { + return Transform3D::identity(); } float s = sin(radians); float c = cos(radians); - float len = sqrt(x*x + y*y + z*z); - x /= len; y /= len; z /= len; - - CATransform3D returnValue = CATransform3D::identity(); - - returnValue.m11 = c + (1-c) * x*x; - returnValue.m12 = (1-c) * x*y + s*z; - returnValue.m13 = (1-c) * x*z - s*y; - returnValue.m14 = 0; - - returnValue.m21 = (1-c) * y*x - s*z; - returnValue.m22 = c + (1-c) * y*y; - returnValue.m23 = (1-c) * y*z + s*x; - returnValue.m24 = 0; - - returnValue.m31 = (1-c) * z*x + s*y; - returnValue.m32 = (1-c) * y*z - s*x; - returnValue.m33 = c + (1-c) * z*z; - returnValue.m34 = 0; - - returnValue.m41 = 0; - returnValue.m42 = 0; - returnValue.m43 = 0; - returnValue.m44 = 1; - - return returnValue;*/ + ::CGAffineTransform t = CGAffineTransformMake(c, s, -s, c, 0.0f, 0.0f); + return fromNativeTransform(CATransform3DMakeAffineTransform(t)); } -CATransform3D CATransform3D::rotated(double degrees) const { - return fromNativeTransform(CATransform3DRotate(nativeTransform(*this), degreesToRadians(degrees), 0.0, 0.0, 1.0)); - //return CATransform3D::makeRotation(degreesToRadians(degrees), 0.0, 0.0, 1.0) * (*this); +Transform3D Transform3D::rotated(float degrees) const { + return Transform3D::makeRotation(degreesToRadians(degrees)) * (*this); } -CATransform3D CATransform3D::translated(Vector2D const &translation) const { - return fromNativeTransform(CATransform3DTranslate(nativeTransform(*this), translation.x, translation.y, 0.0)); +Transform3D Transform3D::translated(Vector2D const &translation) const { + return Transform3D::makeTranslation(translation.x, translation.y, 0.0f) * (*this); } -CATransform3D CATransform3D::scaled(Vector2D const &scale) const { - return fromNativeTransform(CATransform3DScale(nativeTransform(*this), scale.x, scale.y, 1.0)); - //return CATransform3D::makeScale(scale.x, scale.y, 1.0) * (*this); +Transform3D Transform3D::scaled(Vector2D const &scale) const { + return Transform3D::makeScale(scale.x, scale.y, 1.0) * (*this); } -CATransform3D CATransform3D::operator*(CATransform3D const &b) const { - if (isIdentity()) { - return b; - } - if (b.isIdentity()) { - return *this; - } - - const CATransform3D lhs = b; - const CATransform3D &rhs = *this; - CATransform3D result = CATransform3D::identity(); - - result.m11 = (lhs.m11*rhs.m11)+(lhs.m21*rhs.m12)+(lhs.m31*rhs.m13)+(lhs.m41*rhs.m14); - result.m12 = (lhs.m12*rhs.m11)+(lhs.m22*rhs.m12)+(lhs.m32*rhs.m13)+(lhs.m42*rhs.m14); - result.m13 = (lhs.m13*rhs.m11)+(lhs.m23*rhs.m12)+(lhs.m33*rhs.m13)+(lhs.m43*rhs.m14); - result.m14 = (lhs.m14*rhs.m11)+(lhs.m24*rhs.m12)+(lhs.m34*rhs.m13)+(lhs.m44*rhs.m14); - - result.m21 = (lhs.m11*rhs.m21)+(lhs.m21*rhs.m22)+(lhs.m31*rhs.m23)+(lhs.m41*rhs.m24); - result.m22 = (lhs.m12*rhs.m21)+(lhs.m22*rhs.m22)+(lhs.m32*rhs.m23)+(lhs.m42*rhs.m24); - result.m23 = (lhs.m13*rhs.m21)+(lhs.m23*rhs.m22)+(lhs.m33*rhs.m23)+(lhs.m43*rhs.m24); - result.m24 = (lhs.m14*rhs.m21)+(lhs.m24*rhs.m22)+(lhs.m34*rhs.m23)+(lhs.m44*rhs.m24); - - result.m31 = (lhs.m11*rhs.m31)+(lhs.m21*rhs.m32)+(lhs.m31*rhs.m33)+(lhs.m41*rhs.m34); - result.m32 = (lhs.m12*rhs.m31)+(lhs.m22*rhs.m32)+(lhs.m32*rhs.m33)+(lhs.m42*rhs.m34); - result.m33 = (lhs.m13*rhs.m31)+(lhs.m23*rhs.m32)+(lhs.m33*rhs.m33)+(lhs.m43*rhs.m34); - result.m34 = (lhs.m14*rhs.m31)+(lhs.m24*rhs.m32)+(lhs.m34*rhs.m33)+(lhs.m44*rhs.m34); - - result.m41 = (lhs.m11*rhs.m41)+(lhs.m21*rhs.m42)+(lhs.m31*rhs.m43)+(lhs.m41*rhs.m44); - result.m42 = (lhs.m12*rhs.m41)+(lhs.m22*rhs.m42)+(lhs.m32*rhs.m43)+(lhs.m42*rhs.m44); - result.m43 = (lhs.m13*rhs.m41)+(lhs.m23*rhs.m42)+(lhs.m33*rhs.m43)+(lhs.m43*rhs.m44); - result.m44 = (lhs.m14*rhs.m41)+(lhs.m24*rhs.m42)+(lhs.m34*rhs.m43)+(lhs.m44*rhs.m44); - - return result; +bool Transform3D::isInvertible() const { + return Transform2D(*this).isInvertible(); + //return std::abs(m11 * m22 - m12 * m21) >= 0.00000001; } -bool CATransform3D::isInvertible() const { - return std::abs(m11 * m22 - m12 * m21) >= 0.00000001; -} - -CATransform3D CATransform3D::inverted() const { - return fromNativeTransform(CATransform3DMakeAffineTransform(CGAffineTransformInvert(CATransform3DGetAffineTransform(nativeTransform(*this))))); -} +Transform3D Transform3D::inverted() const { + return Transform2D(*this).inverted().transform3D(); +}*/ bool CGRect::intersects(CGRect const &other) const { return CGRectIntersectsRect(CGRectMake(x, y, width, height), CGRectMake(other.x, other.y, other.width, other.height)); @@ -576,71 +880,33 @@ CGRect CGRect::unionWith(CGRect const &other) const { return CGRect(result.origin.x, result.origin.y, result.size.width, result.size.height); } -static inline Vector2D applyingTransformToPoint(CATransform3D const &transform, Vector2D const &point) { - double newX = point.x * transform.m11 + point.y * transform.m21 + transform.m41; - double newY = point.x * transform.m12 + point.y * transform.m22 + transform.m42; - double newW = point.x * transform.m14 + point.y * transform.m24 + transform.m44; - - return Vector2D(newX / newW, newY / newW); -} - -CGRect CGRect::applyingTransform(CATransform3D const &transform) const { +CGRect CGRect::applyingTransform(Transform2D const &transform) const { if (transform.isIdentity()) { return *this; } - Vector2D topLeft = applyingTransformToPoint(transform, Vector2D(x, y)); - Vector2D topRight = applyingTransformToPoint(transform, Vector2D(x + width, y)); - Vector2D bottomLeft = applyingTransformToPoint(transform, Vector2D(x, y + height)); - Vector2D bottomRight = applyingTransformToPoint(transform, Vector2D(x + width, y + height)); + Vector2D sourceTopLeft = Vector2D(x, y); + Vector2D sourceTopRight = Vector2D(x + width, y); + Vector2D sourceBottomLeft = Vector2D(x, y + height); + Vector2D sourceBottomRight = Vector2D(x + width, y + height); - double minX = topLeft.x; - if (topRight.x < minX) { - minX = topRight.x; - } - if (bottomLeft.x < minX) { - minX = bottomLeft.x; - } - if (bottomRight.x < minX) { - minX = bottomRight.x; - } + simd_float4 xs = simd_make_float4(sourceTopLeft.x, sourceTopRight.x, sourceBottomLeft.x, sourceBottomRight.x); + simd_float4 ys = simd_make_float4(sourceTopLeft.y, sourceTopRight.y, sourceBottomLeft.y, sourceBottomRight.y); - double minY = topLeft.y; - if (topRight.y < minY) { - minY = topRight.y; - } - if (bottomLeft.y < minY) { - minY = bottomLeft.y; - } - if (bottomRight.y < minY) { - minY = bottomRight.y; - } + simd_float4 rx = xs * transform.rows().columns[0][0] + ys * transform.rows().columns[1][0] + transform.rows().columns[2][0]; + simd_float4 ry = xs * transform.rows().columns[0][1] + ys * transform.rows().columns[1][1] + transform.rows().columns[2][1]; - double maxX = topLeft.x; - if (topRight.x > maxX) { - maxX = topRight.x; - } - if (bottomLeft.x > maxX) { - maxX = bottomLeft.x; - } - if (bottomRight.x > maxX) { - maxX = bottomRight.x; - } + Vector2D topLeft = Vector2D(rx[0], ry[0]); + Vector2D topRight = Vector2D(rx[1], ry[1]); + Vector2D bottomLeft = Vector2D(rx[2], ry[2]); + Vector2D bottomRight = Vector2D(rx[3], ry[3]); - double maxY = topLeft.y; - if (topRight.y > maxY) { - maxY = topRight.y; - } - if (bottomLeft.y > maxY) { - maxY = bottomLeft.y; - } - if (bottomRight.y > maxY) { - maxY = bottomRight.y; - } + float minX = simd_reduce_min(simd_make_float4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)); + float minY = simd_reduce_min(simd_make_float4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)); + float maxX = simd_reduce_max(simd_make_float4(topLeft.x, topRight.x, bottomLeft.x, bottomRight.x)); + float maxY = simd_reduce_max(simd_make_float4(topLeft.y, topRight.y, bottomLeft.y, bottomRight.y)); - CGRect result(minX, minY, maxX - minX, maxY - minY); - - return result; + return CGRect(minX, minY, maxX - minX, maxY - minY); } } diff --git a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm index d155af122e..5f06c01222 100644 --- a/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm +++ b/submodules/TelegramUI/Components/LottieCpp/Sources/LottieAnimationContainer.mm @@ -56,18 +56,8 @@ result.internalId = nodeId; result.isValid = node->renderData.isValid; + - result.layer.bounds = CGRectMake(node->renderData.layer._bounds.x, node->renderData.layer._bounds.y, node->renderData.layer._bounds.width, node->renderData.layer._bounds.height); - result.layer.position = CGPointMake(node->renderData.layer._position.x, node->renderData.layer._position.y); - result.layer.transform = lottie::nativeTransform(node->renderData.layer._transform); - result.layer.opacity = node->renderData.layer._opacity; - result.layer.masksToBounds = node->renderData.layer._masksToBounds; - result.layer.isHidden = node->renderData.layer._isHidden; - - result.globalRect = CGRectMake(node->renderData.globalRect.x, node->renderData.globalRect.y, node->renderData.globalRect.width, node->renderData.globalRect.height); - result.globalTransform = lottie::nativeTransform(node->renderData.globalTransform); - result.hasSimpleContents = node->renderData.drawContentDescendants <= 1; - result.drawContentDescendants = node->renderData.drawContentDescendants; result.isInvertedMatte = node->renderData.isInvertedMatte; if (node->mask()) { result.maskId = (int64_t)node->mask().get(); diff --git a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift index 8b4ea6042e..9e08a17d94 100644 --- a/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift +++ b/submodules/TelegramUI/Components/LottieMetal/Sources/LottieMetalAnimatedStickerNode.swift @@ -468,7 +468,7 @@ private final class RenderFrameState { restoreState() } - func renderNode(animationContainer: LottieAnimationContainer, node: LottieRenderNodeProxy, globalSize: CGSize, parentAlpha: CGFloat) { + func renderNode(animationContainer: LottieAnimationContainer, node: LottieRenderNodeProxy, globalSize: CGSize, parentAlpha: Float) { let normalizedOpacity = node.layer.opacity let layerAlpha = normalizedOpacity * parentAlpha @@ -534,7 +534,7 @@ private final class RenderFrameState { concat(node.layer.transform) } - var renderAlpha: CGFloat = 1.0 + var renderAlpha: Float = 1.0 if needsTempContext { renderAlpha = 1.0 } else { diff --git a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift index 1d7a582d77..df49f1a264 100644 --- a/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift +++ b/submodules/TelegramUI/Components/PeerAllowedReactionsScreen/Sources/EmojiListInputComponent.swift @@ -105,7 +105,7 @@ final class EmojiListInputComponent: Component { private var component: EmojiListInputComponent? private weak var state: EmptyComponentState? - private var itemLayers: [Int64: EmojiPagerContentComponent.View.ItemLayer] = [:] + private var itemLayers: [Int64: EmojiKeyboardItemLayer] = [:] private let trailingPlaceholder = ComponentView() private let caretIndicator: CaretIndicatorView @@ -239,7 +239,7 @@ final class EmojiListInputComponent: Component { var itemTransition = transition var animateIn = false - let itemLayer: EmojiPagerContentComponent.View.ItemLayer + let itemLayer: EmojiKeyboardItemLayer if let current = self.itemLayers[itemKey] { itemLayer = current } else { @@ -249,7 +249,7 @@ final class EmojiListInputComponent: Component { let animationData = EntityKeyboardAnimationData( file: item.file ) - itemLayer = EmojiPagerContentComponent.View.ItemLayer( + itemLayer = EmojiKeyboardItemLayer( item: EmojiPagerContentComponent.Item( animationData: animationData, content: .animation(animationData), diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 9effaaac6b..8721352f45 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 9effaaac6b3372b12cc183702d839376038cec15 +Subproject commit 8721352f452128adec41c254b8407a4cb18cbbeb