Files
booster 44b1605252 Fixed animator values update
commit_hash:18ef5fd912cf24497e9691f9cd971495dff2b7c5
2026-05-20 12:24:49 +03:00

407 lines
12 KiB
Swift

import CoreGraphics
import LayoutKit
import VGSL
extension DivBase {
func applyBaseProperties(
to makeBlock: () throws -> Block,
context: DivBlockModelingContext,
actionsHolder: DivActionsHolder?,
customAccessibilityParams: CustomAccessibilityParams = .default,
applyPaddings: Bool = true,
clipToBounds: Bool = true,
isFocused: Bool = false
) throws -> Block {
let identity = context.currentDivId
?? context.path.description
setupContext(context: context, identity: identity)
let extensionHandlers = setupExtensionsHandlers(context: context)
let expressionResolver = context.expressionResolver
let externalInsets = margins.resolve(context)
let alpha = CGFloat(resolveAlpha(expressionResolver))
let reuseId = resolveReuseId(expressionResolver)
let visibility: DivBaseBlockBuilder.Visibility = try {
let visibility = DivVisibility.min(
context.parentVisibility,
resolveVisibility(expressionResolver)
)
switch visibility {
case .gone:
return .gone
case .invisible:
return try .invisible(makeBlock())
case .visible:
return try .visible(makeBlock(), alpha: alpha)
}
}()
let blockBuilder = try DivBaseBlockBuilder(
context: context,
visibility: visibility,
div: self,
clipToBounds: clipToBounds,
extensionHandlers: extensionHandlers,
identity: identity,
isFocused: isFocused || context.blockStateStorage.isFocused(path: context.path)
)
switch visibility {
case .gone:
return blockBuilder
.build()
case .invisible:
return blockBuilder
.applyExtensionHandlers(stage: .beforeBaseProperties)
.applyEdgeInsets(
applyPaddings: applyPaddings
)
.setupContextForBlockVisibility()
.applyExternalEdgeInsets(
externalInsets: externalInsets
)
.applyExtensionHandlers(stage: .afterBaseProperties)
.applyBlockFinalDecorations()
.build()
case .visible:
return try blockBuilder
.applyExtensionHandlers(stage: .beforeBaseProperties)
.applyEdgeInsets(
applyPaddings: applyPaddings
)
.applyBackground()
.applyVisibilityActionsAndDecorations(
customAccessibilityParams: customAccessibilityParams
)
.applyTransformations()
.applyTransitioningAnimations()
.setupContextForBlockVisibility() // should be after apply animations
.applyActions(
actionsHolder: actionsHolder
)
.applyExternalEdgeInsets(
externalInsets: externalInsets
)
.applyBlockFinalDecorations()
.setBlockLayoutProvider(layoutProvider)
.setBlockReuseId(reuseId)
.applyExtensionHandlers(stage: .afterBaseProperties)
.build()
}
}
func resolveAlignment(
_ context: DivBlockModelingContext,
defaultAlignment: BlockAlignment2D
) -> BlockAlignment2D {
let expressionResolver = context.expressionResolver
return BlockAlignment2D(
horizontal: resolveAlignmentHorizontal(expressionResolver)?
.makeContentAlignment(layoutDirection: context.layoutDirection)
?? defaultAlignment.horizontal,
vertical: resolveAlignmentVertical(expressionResolver)?.alignment
?? defaultAlignment.vertical
)
}
func makeVisibilityActions(
actionsType: VisibilityActionType,
context: DivBlockModelingContext
) -> [VisibilityAction] {
let divActions: [DivVisibilityActionBase]? = switch actionsType {
case .appear:
visibilityActions ?? visibilityAction.asArray()
case .disappear:
disappearActions
}
let blockActions: [VisibilityAction] = divActions?.compactMap {
$0.makeVisibilityAction(
context: context
)
} ?? []
let logIds = blockActions.map(\.logId)
let nonUniqueLogIds = logIds.nonUniqueElements
if !nonUniqueLogIds.isEmpty {
context.addWarning(
message: "\(actionsType.rawValue) actions array contains non-unique log_id values: \(nonUniqueLogIds)"
)
}
return blockActions
}
func setupContextWithVariablesAndFunctions(
context: DivBlockModelingContext
) {
context.functionsStorage?.setIfNeeded(
path: context.path,
functions: functions ?? []
)
context.variablesStorage.initializeIfNeeded(
path: context.path,
variables: variables?.extractDivVariableValues(context.expressionResolver) ?? [:],
properties: variables?.extractDivPropertiesValues(
expressionResolver: context.expressionResolver,
variablesStorage: context.variablesStorage,
path: context.path,
actionHandler: context.actionHandler
) ?? [:]
)
}
private func setupContext(
context: DivBlockModelingContext,
identity: String
) {
setupContextWithVariablesAndFunctions(context: context)
let path = context.path
context.triggersStorage?.setIfNeeded(
path: path,
triggers: variableTriggers ?? []
)
context.idToPath[path.cardId.path + identity] = path
animators?.forEach { animator in
context.animatorController?.initializeIfNeeded(
path: path,
id: animator.id,
definition: animator,
animator: Variable { animator.resolve(context) }
)
}
let expressionResolver = context.expressionResolver
if let forwardId = focus?.nextFocusIds?.resolveForward(expressionResolver),
let currentId = self.id {
context.accessibilityElementsStorage.put(id: currentId, nextId: forwardId)
}
}
private func setupExtensionsHandlers(
context: DivBlockModelingContext
) -> [DivExtensionHandler] {
let extensionHandlers = context.getExtensionHandlers(for: self)
for extensionHandler in extensionHandlers {
extensionHandler.accept(div: self, context: context)
}
return extensionHandlers
}
}
enum ApplyExtensionHandlersStage {
case beforeBaseProperties, afterBaseProperties
}
extension DivBase {
var transitionTriggersOrDefault: [DivTransitionTrigger] {
transitionTriggers ?? [.stateChange, .visibilityChange]
}
func getTransformedWidth(_ context: DivBlockModelingContext) -> DivSize {
context.sizeModifier?.transformWidth(width) ?? width
}
func resolveWidthTrait(_ context: DivBlockModelingContext) -> LayoutTrait {
getTransformedWidth(context).resolveLayoutTrait(context.expressionResolver)
}
func resolveContentWidthTrait(_ context: DivBlockModelingContext) -> LayoutTrait {
resolveContentWidthTrait(context, paddings: paddings.resolve(context))
}
func resolveContentWidthTrait(
_ context: DivBlockModelingContext,
paddings: EdgeInsets
) -> LayoutTrait {
resolveWidthTrait(context).trim(paddings.horizontal)
}
func getTransformedHeight(_ context: DivBlockModelingContext) -> DivSize {
context.sizeModifier?.transformHeight(height) ?? height
}
func resolveHeightTrait(_ context: DivBlockModelingContext) -> LayoutTrait {
getTransformedHeight(context).resolveLayoutTrait(context.expressionResolver)
}
func resolveContentHeightTrait(_ context: DivBlockModelingContext) -> LayoutTrait {
resolveContentHeightTrait(context, paddings: paddings.resolve(context))
}
func resolveContentHeightTrait(
_ context: DivBlockModelingContext,
paddings: EdgeInsets
) -> LayoutTrait {
resolveHeightTrait(context).trim(paddings.vertical)
}
}
extension Block {
func applyExtensionHandlers(
order: ApplyExtensionHandlersStage,
div: DivBase,
extensionHandlers: [DivExtensionHandler],
context: DivBlockModelingContext
) -> Block {
var newBlock: Block = self
for extensionHandler in extensionHandlers {
switch order {
case .beforeBaseProperties:
newBlock = extensionHandler.applyBeforeBaseProperties(
to: newBlock,
div: div,
context: context
)
case .afterBaseProperties:
newBlock = extensionHandler.applyAfterBaseProperties(
to: newBlock,
div: div,
context: context
)
}
}
return newBlock
}
func addingTransformations(
transform: DivTransform?,
rotation: Double?,
expressionResolver: ExpressionResolver
) -> Block {
guard let transform else { return self }
return self
.addingTransform(
transform: rotation
.flatMap { CGAffineTransform(rotationAngle: CGFloat($0) * .pi / 180) } ?? .identity,
anchorPoint: transform.resolveAnchorPoint(expressionResolver)
)
}
}
extension LayoutTrait {
fileprivate func trim(_ insets: SideInsets) -> LayoutTrait {
switch self {
case let .fixed(value):
.fixed(value - insets.sum)
case let .intrinsic(constrained, minSize, maxSize):
.intrinsic(
constrained: constrained,
minSize: minSize - insets.sum,
maxSize: maxSize - insets.sum
)
case .weighted:
self
}
}
}
extension DivAlignmentHorizontal {
fileprivate func makeContentAlignment(
layoutDirection: UserInterfaceLayoutDirection
) -> Alignment {
switch self {
case .left:
.leading
case .right:
.trailing
case .start:
layoutDirection == .leftToRight ? .leading : .trailing
case .center:
.center
case .end:
layoutDirection == .rightToLeft ? .leading : .trailing
}
}
}
extension DivBorder {
func resolveBorder(_ expressionResolver: ExpressionResolver) -> BlockBorder? {
guard let stroke else {
return nil
}
return BlockBorder(
style: stroke.resolveStyle(expressionResolver),
color: stroke.resolveColor(expressionResolver) ?? .black,
width: stroke.resolveUnit(expressionResolver)
.makeScaledValue(stroke.resolveWidth(expressionResolver))
)
}
func resolveShadow(_ expressionResolver: ExpressionResolver) -> BlockShadow? {
guard resolveHasShadow(expressionResolver) else {
return nil
}
let cornerRadii = resolveCornerRadii(expressionResolver)
guard let shadow else {
return BlockShadow(
cornerRadii: cornerRadii,
blurRadius: 2,
offset: .zero,
opacity: BlockShadow.Defaults.opacity,
color: BlockShadow.Defaults.color
)
}
return shadow.resolve(expressionResolver, cornerRadii: cornerRadii)
}
func resolveCornerRadii(_ expressionResolver: ExpressionResolver) -> CornerRadii {
let cornerRadius = resolveCornerRadius(expressionResolver)
let topLeft = cornersRadius?.resolveTopLeft(expressionResolver)
?? cornerRadius ?? 0
let topRight = cornersRadius?.resolveTopRight(expressionResolver)
?? cornerRadius ?? 0
let bottomLeft = cornersRadius?.resolveBottomLeft(expressionResolver)
?? cornerRadius ?? 0
let bottomRight = cornersRadius?.resolveBottomRight(expressionResolver)
?? cornerRadius ?? 0
return CornerRadii(
topLeft: CGFloat(topLeft),
topRight: CGFloat(topRight),
bottomLeft: CGFloat(bottomLeft),
bottomRight: CGFloat(bottomRight)
)
}
}
extension DivStroke {
fileprivate func resolveStyle(_: ExpressionResolver) -> BlockBorder.Style {
switch style {
case .divStrokeStyleDashed: BlockBorder.Style.dashed
case .divStrokeStyleSolid: BlockBorder.Style.solid
}
}
}
extension DivBlockModelingContext {
func makeVisibilityParams(
actions: [VisibilityAction],
isVisible: Bool
) -> VisibilityParams? {
if actions.isEmpty {
return nil
}
let path = parentPath
return VisibilityParams(
actions: actions,
isVisible: isVisible,
lastVisibleArea: Property<Int>(
getter: { lastVisibleBoundsCache.lastVisibleArea(for: path) },
setter: { lastVisibleBoundsCache.updateLastVisibleArea(for: path, area: $0) }
),
scheduler: scheduler
)
}
}