From ea33e8a65c82467eeaf71aaa4a150399732bf3ba Mon Sep 17 00:00:00 2001 From: dashapalshau Date: Wed, 18 Jan 2023 16:17:37 +0300 Subject: [PATCH] Add border in page indicator --- .../Extensions/DivIndicatorExtensions.swift | 105 ++++++++++++++---- .../generated_sources/DivIndicator.swift | 44 ++++++-- .../DivIndicatorTemplate.swift | 51 +++++++++ .../DivRoundedRectangleShape.swift | 30 ++++- .../DivRoundedRectangleShapeTemplate.swift | 49 ++++++-- .../LayoutKit/Blocks/PageControlBlock.swift | 4 +- .../IndicatorStateAnimator.swift | 35 +++++- .../PageIndicatorConfiguration.swift | 48 +++++++- .../PageIndicator/PageIndicatorLayer.swift | 66 ++++++++--- schema/div-indicator.json | 12 +- 10 files changed, 372 insertions(+), 72 deletions(-) diff --git a/client/ios/DivKit/Extensions/DivIndicatorExtensions.swift b/client/ios/DivKit/Extensions/DivIndicatorExtensions.swift index 5d9346b9c..a0ff745d4 100644 --- a/client/ios/DivKit/Extensions/DivIndicatorExtensions.swift +++ b/client/ios/DivKit/Extensions/DivIndicatorExtensions.swift @@ -21,35 +21,76 @@ extension DivIndicator: DivBlockModeling { context.addError(level: .error, message: "circle shape unsupported.") return EmptyBlock() } + + let expressionResolver = context.expressionResolver + let activeRect = makeRoundedRectangle(with: activeShape, resolver: expressionResolver) + let inactiveRect = makeRoundedRectangle(with: inactiveShape, resolver: expressionResolver) + let inactiveMinimumRect = makeRoundedRectangle( + with: inactiveMinimumShape, + resolver: expressionResolver + ) + + let pageSize = CGSize( + width: inactiveRect?.size + .width ?? CGFloat(rectangle.itemWidth.resolveValue(expressionResolver) ?? 0), + height: inactiveRect?.size + .height ?? CGFloat(rectangle.itemHeight.resolveValue(expressionResolver) ?? 0) + ) + + let hightightedScaleX: CGFloat + let hightightedScaleY: CGFloat + if let activeRect = activeRect, let inactiveRect = inactiveRect { + hightightedScaleX = activeRect.size.width / inactiveRect.size.width + hightightedScaleY = activeRect.size.height / inactiveRect.size.height + } else { + let scale = CGFloat(resolveActiveItemSize(expressionResolver)) + hightightedScaleX = scale + hightightedScaleY = scale + } + + let disappearingScaleX: CGFloat + let disappearingScaleY: CGFloat + if let inactiveRect = inactiveRect, let inactiveMinimumRect = inactiveMinimumRect { + disappearingScaleX = inactiveMinimumRect.size.width / inactiveRect.size.width + disappearingScaleY = inactiveMinimumRect.size.height / inactiveRect.size.height + } else { + let scale = CGFloat(resolveMinimumItemSize(expressionResolver)) + disappearingScaleX = scale + disappearingScaleY = scale + } + let pagerPath = pagerId.map { PagerPath( cardId: context.cardId.rawValue, pagerId: $0 ) } - let state = context.blockStateStorage.states.pagerViewState(for: pagerPath) ?? .default - - let expressionResolver = context.expressionResolver - let pageSize = - CGSize( - width: rectangle.itemWidth.resolveValue(expressionResolver) ?? 0, - height: rectangle.itemHeight.resolveValue(expressionResolver) ?? 0 - ) let spaceBetweenCenters = CGFloat(spaceBetweenCenters.resolveValue(expressionResolver) ?? 0) - let configuration = - PageIndicatorConfiguration( - highlightedColor: resolveActiveItemColor(expressionResolver), - normalColor: resolveInactiveItemColor(expressionResolver), - highlightingScale: CGFloat(resolveActiveItemSize(expressionResolver)), - disappearingScale: CGFloat(resolveMinimumItemSize(expressionResolver)), - pageSize: pageSize, - pageCornerRadius: CGFloat( - rectangle.cornerRadius.resolveValue(expressionResolver) ?? 0 - ), - animation: resolveAnimation(expressionResolver).asBlockAnimation, - itemPlacement: itemsPlacement?.makeItemPlacement(with: expressionResolver) ?? .fixed(spaceBetweenCenters: spaceBetweenCenters) - ) + + let configuration = PageIndicatorConfiguration( + highlightedColor: activeRect?.backgroundColor ?? resolveActiveItemColor(expressionResolver), + normalColor: inactiveRect?.backgroundColor ?? resolveInactiveItemColor(expressionResolver), + highlightedBorder: BlockBorder( + color: activeRect?.borderColor ?? .clear, + width: activeRect?.borderWidth ?? 0 + ), + normalBorder: BlockBorder( + color: inactiveRect?.borderColor ?? .clear, + width: inactiveRect?.borderWidth ?? 0 + ), + highlightedHeightScale: hightightedScaleY, + highlightedWidthScale: hightightedScaleX, + disappearingHeightScale: disappearingScaleY, + disappearingWidthScale: disappearingScaleX, + pageSize: pageSize, + pageCornerRadius: inactiveRect? + .cornerRadius ?? CGFloat(rectangle.cornerRadius.resolveValue(expressionResolver) ?? 0), + animation: resolveAnimation(expressionResolver).asBlockAnimation, + itemPlacement: itemsPlacement? + .makeItemPlacement(with: expressionResolver) ?? + .fixed(spaceBetweenCenters: spaceBetweenCenters) + ) return PageControlBlock( @@ -60,6 +101,25 @@ extension DivIndicator: DivBlockModeling { state: state ) } + + private func makeRoundedRectangle( + with shape: DivRoundedRectangleShape?, + resolver: ExpressionResolver + ) -> PageIndicatorConfiguration.RoundedRectangleIndicator? { + guard let shape = shape else { + return nil + } + return PageIndicatorConfiguration.RoundedRectangleIndicator( + size: CGSize( + width: shape.itemWidth.resolveValue(resolver) ?? 0, + height: shape.itemHeight.resolveValue(resolver) ?? 0 + ), + cornerRadius: CGFloat(shape.cornerRadius.resolveValue(resolver) ?? 0), + backgroundColor: shape.resolveBackgroundColor(resolver) ?? .clear, + borderWidth: CGFloat(shape.stroke?.resolveWidth(resolver) ?? 0), + borderColor: shape.stroke?.resolveColor(resolver) ?? .clear + ) + } } extension DivShape { @@ -87,7 +147,8 @@ extension DivIndicator.Animation { } extension DivIndicatorItemPlacement { - func makeItemPlacement(with expressionResolver: ExpressionResolver) -> PageIndicatorConfiguration.ItemPlacement { + func makeItemPlacement(with expressionResolver: ExpressionResolver) -> PageIndicatorConfiguration + .ItemPlacement { switch self { case let .divDefaultIndicatorItemPlacement(defaultPlacement): return .fixed(spaceBetweenCenters: CGFloat( diff --git a/client/ios/DivKit/generated_sources/DivIndicator.swift b/client/ios/DivKit/generated_sources/DivIndicator.swift index cba7c9be7..c3e4b29cc 100644 --- a/client/ios/DivKit/generated_sources/DivIndicator.swift +++ b/client/ios/DivKit/generated_sources/DivIndicator.swift @@ -17,6 +17,7 @@ public final class DivIndicator: DivBase { public let accessibility: DivAccessibility public let activeItemColor: Expression // default value: #ffdc60 public let activeItemSize: Expression // constraint: number > 0; default value: 1.3 + public let activeShape: DivRoundedRectangleShape? public let alignmentHorizontal: Expression? public let alignmentVertical: Expression? public let alpha: Expression // constraint: number >= 0.0 && number <= 1.0; default value: 1.0 @@ -29,6 +30,8 @@ public final class DivIndicator: DivBase { public let height: DivSize // default value: .divWrapContentSize(DivWrapContentSize()) public let id: String? // at least 1 char public let inactiveItemColor: Expression // default value: #33919cb5 + public let inactiveMinimumShape: DivRoundedRectangleShape? + public let inactiveShape: DivRoundedRectangleShape? public let itemsPlacement: DivIndicatorItemPlacement? public let margins: DivEdgeInsets public let minimumItemSize: Expression // constraint: number > 0; default value: 0.5 @@ -102,6 +105,9 @@ public final class DivIndicator: DivBase { static let activeItemSizeValidator: AnyValueValidator = makeValueValidator(valueValidator: { $0 > 0 }) + static let activeShapeValidator: AnyValueValidator = + makeNoOpValueValidator() + static let alignmentHorizontalValidator: AnyValueValidator = makeNoOpValueValidator() @@ -138,6 +144,12 @@ public final class DivIndicator: DivBase { static let inactiveItemColorValidator: AnyValueValidator = makeNoOpValueValidator() + static let inactiveMinimumShapeValidator: AnyValueValidator = + makeNoOpValueValidator() + + static let inactiveShapeValidator: AnyValueValidator = + makeNoOpValueValidator() + static let itemsPlacementValidator: AnyValueValidator = makeNoOpValueValidator() @@ -199,6 +211,7 @@ public final class DivIndicator: DivBase { accessibility: DivAccessibility? = nil, activeItemColor: Expression? = nil, activeItemSize: Expression? = nil, + activeShape: DivRoundedRectangleShape? = nil, alignmentHorizontal: Expression? = nil, alignmentVertical: Expression? = nil, alpha: Expression? = nil, @@ -211,6 +224,8 @@ public final class DivIndicator: DivBase { height: DivSize? = nil, id: String? = nil, inactiveItemColor: Expression? = nil, + inactiveMinimumShape: DivRoundedRectangleShape? = nil, + inactiveShape: DivRoundedRectangleShape? = nil, itemsPlacement: DivIndicatorItemPlacement? = nil, margins: DivEdgeInsets? = nil, minimumItemSize: Expression? = nil, @@ -234,6 +249,7 @@ public final class DivIndicator: DivBase { self.accessibility = accessibility ?? DivAccessibility() self.activeItemColor = activeItemColor ?? .value(Color.colorWithARGBHexCode(0xFFFFDC60)) self.activeItemSize = activeItemSize ?? .value(1.3) + self.activeShape = activeShape self.alignmentHorizontal = alignmentHorizontal self.alignmentVertical = alignmentVertical self.alpha = alpha ?? .value(1.0) @@ -246,6 +262,8 @@ public final class DivIndicator: DivBase { self.height = height ?? .divWrapContentSize(DivWrapContentSize()) self.id = id self.inactiveItemColor = inactiveItemColor ?? .value(Color.colorWithARGBHexCode(0x33919CB5)) + self.inactiveMinimumShape = inactiveMinimumShape + self.inactiveShape = inactiveShape self.itemsPlacement = itemsPlacement self.margins = margins ?? DivEdgeInsets() self.minimumItemSize = minimumItemSize ?? .value(0.5) @@ -279,30 +297,37 @@ extension DivIndicator: Equatable { return false } guard + lhs.activeShape == rhs.activeShape, lhs.alignmentHorizontal == rhs.alignmentHorizontal, - lhs.alignmentVertical == rhs.alignmentVertical, - lhs.alpha == rhs.alpha + lhs.alignmentVertical == rhs.alignmentVertical else { return false } guard + lhs.alpha == rhs.alpha, lhs.animation == rhs.animation, - lhs.background == rhs.background, - lhs.border == rhs.border + lhs.background == rhs.background else { return false } guard + lhs.border == rhs.border, lhs.columnSpan == rhs.columnSpan, - lhs.extensions == rhs.extensions, - lhs.focus == rhs.focus + lhs.extensions == rhs.extensions else { return false } guard + lhs.focus == rhs.focus, lhs.height == rhs.height, - lhs.id == rhs.id, - lhs.inactiveItemColor == rhs.inactiveItemColor + lhs.id == rhs.id + else { + return false + } + guard + lhs.inactiveItemColor == rhs.inactiveItemColor, + lhs.inactiveMinimumShape == rhs.inactiveMinimumShape, + lhs.inactiveShape == rhs.inactiveShape else { return false } @@ -365,6 +390,7 @@ extension DivIndicator: Serializable { result["accessibility"] = accessibility.toDictionary() result["active_item_color"] = activeItemColor.toValidSerializationValue() result["active_item_size"] = activeItemSize.toValidSerializationValue() + result["active_shape"] = activeShape?.toDictionary() result["alignment_horizontal"] = alignmentHorizontal?.toValidSerializationValue() result["alignment_vertical"] = alignmentVertical?.toValidSerializationValue() result["alpha"] = alpha.toValidSerializationValue() @@ -377,6 +403,8 @@ extension DivIndicator: Serializable { result["height"] = height.toDictionary() result["id"] = id result["inactive_item_color"] = inactiveItemColor.toValidSerializationValue() + result["inactive_minimum_shape"] = inactiveMinimumShape?.toDictionary() + result["inactive_shape"] = inactiveShape?.toDictionary() result["items_placement"] = itemsPlacement?.toDictionary() result["margins"] = margins.toDictionary() result["minimum_item_size"] = minimumItemSize.toValidSerializationValue() diff --git a/client/ios/DivKit/generated_sources/DivIndicatorTemplate.swift b/client/ios/DivKit/generated_sources/DivIndicatorTemplate.swift index 0a4fc12e4..0f37c4ef8 100644 --- a/client/ios/DivKit/generated_sources/DivIndicatorTemplate.swift +++ b/client/ios/DivKit/generated_sources/DivIndicatorTemplate.swift @@ -13,6 +13,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { public let accessibility: Field? public let activeItemColor: Field>? // default value: #ffdc60 public let activeItemSize: Field>? // constraint: number > 0; default value: 1.3 + public let activeShape: Field? public let alignmentHorizontal: Field>? public let alignmentVertical: Field>? public let alpha: Field>? // constraint: number >= 0.0 && number <= 1.0; default value: 1.0 @@ -25,6 +26,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { public let height: Field? // default value: .divWrapContentSize(DivWrapContentSize()) public let id: Field? // at least 1 char public let inactiveItemColor: Field>? // default value: #33919cb5 + public let inactiveMinimumShape: Field? + public let inactiveShape: Field? public let itemsPlacement: Field? public let margins: Field? public let minimumItemSize: Field>? // constraint: number > 0; default value: 0.5 @@ -54,6 +57,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: try dictionary.getOptionalField("accessibility", templateToType: templateToType), activeItemColor: try dictionary.getOptionalExpressionField("active_item_color", transform: Color.color(withHexString:)), activeItemSize: try dictionary.getOptionalExpressionField("active_item_size"), + activeShape: try dictionary.getOptionalField("active_shape", templateToType: templateToType), alignmentHorizontal: try dictionary.getOptionalExpressionField("alignment_horizontal"), alignmentVertical: try dictionary.getOptionalExpressionField("alignment_vertical"), alpha: try dictionary.getOptionalExpressionField("alpha"), @@ -66,6 +70,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: try dictionary.getOptionalField("height", templateToType: templateToType), id: try dictionary.getOptionalField("id"), inactiveItemColor: try dictionary.getOptionalExpressionField("inactive_item_color", transform: Color.color(withHexString:)), + inactiveMinimumShape: try dictionary.getOptionalField("inactive_minimum_shape", templateToType: templateToType), + inactiveShape: try dictionary.getOptionalField("inactive_shape", templateToType: templateToType), itemsPlacement: try dictionary.getOptionalField("items_placement", templateToType: templateToType), margins: try dictionary.getOptionalField("margins", templateToType: templateToType), minimumItemSize: try dictionary.getOptionalExpressionField("minimum_item_size"), @@ -93,6 +99,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: Field? = nil, activeItemColor: Field>? = nil, activeItemSize: Field>? = nil, + activeShape: Field? = nil, alignmentHorizontal: Field>? = nil, alignmentVertical: Field>? = nil, alpha: Field>? = nil, @@ -105,6 +112,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: Field? = nil, id: Field? = nil, inactiveItemColor: Field>? = nil, + inactiveMinimumShape: Field? = nil, + inactiveShape: Field? = nil, itemsPlacement: Field? = nil, margins: Field? = nil, minimumItemSize: Field>? = nil, @@ -129,6 +138,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { self.accessibility = accessibility self.activeItemColor = activeItemColor self.activeItemSize = activeItemSize + self.activeShape = activeShape self.alignmentHorizontal = alignmentHorizontal self.alignmentVertical = alignmentVertical self.alpha = alpha @@ -141,6 +151,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { self.height = height self.id = id self.inactiveItemColor = inactiveItemColor + self.inactiveMinimumShape = inactiveMinimumShape + self.inactiveShape = inactiveShape self.itemsPlacement = itemsPlacement self.margins = margins self.minimumItemSize = minimumItemSize @@ -166,6 +178,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { let accessibilityValue = parent?.accessibility?.resolveOptionalValue(context: context, validator: ResolvedValue.accessibilityValidator, useOnlyLinks: true) ?? .noValue let activeItemColorValue = parent?.activeItemColor?.resolveOptionalValue(context: context, transform: Color.color(withHexString:), validator: ResolvedValue.activeItemColorValidator) ?? .noValue let activeItemSizeValue = parent?.activeItemSize?.resolveOptionalValue(context: context, validator: ResolvedValue.activeItemSizeValidator) ?? .noValue + let activeShapeValue = parent?.activeShape?.resolveOptionalValue(context: context, validator: ResolvedValue.activeShapeValidator, useOnlyLinks: true) ?? .noValue let alignmentHorizontalValue = parent?.alignmentHorizontal?.resolveOptionalValue(context: context, validator: ResolvedValue.alignmentHorizontalValidator) ?? .noValue let alignmentVerticalValue = parent?.alignmentVertical?.resolveOptionalValue(context: context, validator: ResolvedValue.alignmentVerticalValidator) ?? .noValue let alphaValue = parent?.alpha?.resolveOptionalValue(context: context, validator: ResolvedValue.alphaValidator) ?? .noValue @@ -178,6 +191,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { let heightValue = parent?.height?.resolveOptionalValue(context: context, validator: ResolvedValue.heightValidator, useOnlyLinks: true) ?? .noValue let idValue = parent?.id?.resolveOptionalValue(context: context, validator: ResolvedValue.idValidator) ?? .noValue let inactiveItemColorValue = parent?.inactiveItemColor?.resolveOptionalValue(context: context, transform: Color.color(withHexString:), validator: ResolvedValue.inactiveItemColorValidator) ?? .noValue + let inactiveMinimumShapeValue = parent?.inactiveMinimumShape?.resolveOptionalValue(context: context, validator: ResolvedValue.inactiveMinimumShapeValidator, useOnlyLinks: true) ?? .noValue + let inactiveShapeValue = parent?.inactiveShape?.resolveOptionalValue(context: context, validator: ResolvedValue.inactiveShapeValidator, useOnlyLinks: true) ?? .noValue let itemsPlacementValue = parent?.itemsPlacement?.resolveOptionalValue(context: context, validator: ResolvedValue.itemsPlacementValidator, useOnlyLinks: true) ?? .noValue let marginsValue = parent?.margins?.resolveOptionalValue(context: context, validator: ResolvedValue.marginsValidator, useOnlyLinks: true) ?? .noValue let minimumItemSizeValue = parent?.minimumItemSize?.resolveOptionalValue(context: context, validator: ResolvedValue.minimumItemSizeValidator) ?? .noValue @@ -201,6 +216,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibilityValue.errorsOrWarnings?.map { .nestedObjectError(field: "accessibility", error: $0) }, activeItemColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_item_color", error: $0) }, activeItemSizeValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_item_size", error: $0) }, + activeShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_shape", error: $0) }, alignmentHorizontalValue.errorsOrWarnings?.map { .nestedObjectError(field: "alignment_horizontal", error: $0) }, alignmentVerticalValue.errorsOrWarnings?.map { .nestedObjectError(field: "alignment_vertical", error: $0) }, alphaValue.errorsOrWarnings?.map { .nestedObjectError(field: "alpha", error: $0) }, @@ -213,6 +229,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { heightValue.errorsOrWarnings?.map { .nestedObjectError(field: "height", error: $0) }, idValue.errorsOrWarnings?.map { .nestedObjectError(field: "id", error: $0) }, inactiveItemColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_item_color", error: $0) }, + inactiveMinimumShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_minimum_shape", error: $0) }, + inactiveShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_shape", error: $0) }, itemsPlacementValue.errorsOrWarnings?.map { .nestedObjectError(field: "items_placement", error: $0) }, marginsValue.errorsOrWarnings?.map { .nestedObjectError(field: "margins", error: $0) }, minimumItemSizeValue.errorsOrWarnings?.map { .nestedObjectError(field: "minimum_item_size", error: $0) }, @@ -237,6 +255,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: accessibilityValue.value, activeItemColor: activeItemColorValue.value, activeItemSize: activeItemSizeValue.value, + activeShape: activeShapeValue.value, alignmentHorizontal: alignmentHorizontalValue.value, alignmentVertical: alignmentVerticalValue.value, alpha: alphaValue.value, @@ -249,6 +268,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: heightValue.value, id: idValue.value, inactiveItemColor: inactiveItemColorValue.value, + inactiveMinimumShape: inactiveMinimumShapeValue.value, + inactiveShape: inactiveShapeValue.value, itemsPlacement: itemsPlacementValue.value, margins: marginsValue.value, minimumItemSize: minimumItemSizeValue.value, @@ -279,6 +300,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { var accessibilityValue: DeserializationResult = .noValue var activeItemColorValue: DeserializationResult> = parent?.activeItemColor?.value() ?? .noValue var activeItemSizeValue: DeserializationResult> = parent?.activeItemSize?.value() ?? .noValue + var activeShapeValue: DeserializationResult = .noValue var alignmentHorizontalValue: DeserializationResult> = parent?.alignmentHorizontal?.value() ?? .noValue var alignmentVerticalValue: DeserializationResult> = parent?.alignmentVertical?.value() ?? .noValue var alphaValue: DeserializationResult> = parent?.alpha?.value() ?? .noValue @@ -291,6 +313,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { var heightValue: DeserializationResult = .noValue var idValue: DeserializationResult = parent?.id?.value(validatedBy: ResolvedValue.idValidator) ?? .noValue var inactiveItemColorValue: DeserializationResult> = parent?.inactiveItemColor?.value() ?? .noValue + var inactiveMinimumShapeValue: DeserializationResult = .noValue + var inactiveShapeValue: DeserializationResult = .noValue var itemsPlacementValue: DeserializationResult = .noValue var marginsValue: DeserializationResult = .noValue var minimumItemSizeValue: DeserializationResult> = parent?.minimumItemSize?.value() ?? .noValue @@ -318,6 +342,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { activeItemColorValue = deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.activeItemColorValidator).merged(with: activeItemColorValue) case "active_item_size": activeItemSizeValue = deserialize(__dictValue, validator: ResolvedValue.activeItemSizeValidator).merged(with: activeItemSizeValue) + case "active_shape": + activeShapeValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.activeShapeValidator, type: DivRoundedRectangleShapeTemplate.self).merged(with: activeShapeValue) case "alignment_horizontal": alignmentHorizontalValue = deserialize(__dictValue, validator: ResolvedValue.alignmentHorizontalValidator).merged(with: alignmentHorizontalValue) case "alignment_vertical": @@ -342,6 +368,10 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { idValue = deserialize(__dictValue, validator: ResolvedValue.idValidator).merged(with: idValue) case "inactive_item_color": inactiveItemColorValue = deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.inactiveItemColorValidator).merged(with: inactiveItemColorValue) + case "inactive_minimum_shape": + inactiveMinimumShapeValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.inactiveMinimumShapeValidator, type: DivRoundedRectangleShapeTemplate.self).merged(with: inactiveMinimumShapeValue) + case "inactive_shape": + inactiveShapeValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.inactiveShapeValidator, type: DivRoundedRectangleShapeTemplate.self).merged(with: inactiveShapeValue) case "items_placement": itemsPlacementValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemsPlacementValidator, type: DivIndicatorItemPlacementTemplate.self).merged(with: itemsPlacementValue) case "margins": @@ -386,6 +416,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { activeItemColorValue = activeItemColorValue.merged(with: deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.activeItemColorValidator)) case parent?.activeItemSize?.link: activeItemSizeValue = activeItemSizeValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.activeItemSizeValidator)) + case parent?.activeShape?.link: + activeShapeValue = activeShapeValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.activeShapeValidator, type: DivRoundedRectangleShapeTemplate.self)) case parent?.alignmentHorizontal?.link: alignmentHorizontalValue = alignmentHorizontalValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.alignmentHorizontalValidator)) case parent?.alignmentVertical?.link: @@ -410,6 +442,10 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { idValue = idValue.merged(with: deserialize(__dictValue, validator: ResolvedValue.idValidator)) case parent?.inactiveItemColor?.link: inactiveItemColorValue = inactiveItemColorValue.merged(with: deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.inactiveItemColorValidator)) + case parent?.inactiveMinimumShape?.link: + inactiveMinimumShapeValue = inactiveMinimumShapeValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.inactiveMinimumShapeValidator, type: DivRoundedRectangleShapeTemplate.self)) + case parent?.inactiveShape?.link: + inactiveShapeValue = inactiveShapeValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.inactiveShapeValidator, type: DivRoundedRectangleShapeTemplate.self)) case parent?.itemsPlacement?.link: itemsPlacementValue = itemsPlacementValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemsPlacementValidator, type: DivIndicatorItemPlacementTemplate.self)) case parent?.margins?.link: @@ -453,11 +489,14 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { } if let parent = parent { accessibilityValue = accessibilityValue.merged(with: parent.accessibility?.resolveOptionalValue(context: context, validator: ResolvedValue.accessibilityValidator, useOnlyLinks: true)) + activeShapeValue = activeShapeValue.merged(with: parent.activeShape?.resolveOptionalValue(context: context, validator: ResolvedValue.activeShapeValidator, useOnlyLinks: true)) backgroundValue = backgroundValue.merged(with: parent.background?.resolveOptionalValue(context: context, validator: ResolvedValue.backgroundValidator, useOnlyLinks: true)) borderValue = borderValue.merged(with: parent.border?.resolveOptionalValue(context: context, validator: ResolvedValue.borderValidator, useOnlyLinks: true)) extensionsValue = extensionsValue.merged(with: parent.extensions?.resolveOptionalValue(context: context, validator: ResolvedValue.extensionsValidator, useOnlyLinks: true)) focusValue = focusValue.merged(with: parent.focus?.resolveOptionalValue(context: context, validator: ResolvedValue.focusValidator, useOnlyLinks: true)) heightValue = heightValue.merged(with: parent.height?.resolveOptionalValue(context: context, validator: ResolvedValue.heightValidator, useOnlyLinks: true)) + inactiveMinimumShapeValue = inactiveMinimumShapeValue.merged(with: parent.inactiveMinimumShape?.resolveOptionalValue(context: context, validator: ResolvedValue.inactiveMinimumShapeValidator, useOnlyLinks: true)) + inactiveShapeValue = inactiveShapeValue.merged(with: parent.inactiveShape?.resolveOptionalValue(context: context, validator: ResolvedValue.inactiveShapeValidator, useOnlyLinks: true)) itemsPlacementValue = itemsPlacementValue.merged(with: parent.itemsPlacement?.resolveOptionalValue(context: context, validator: ResolvedValue.itemsPlacementValidator, useOnlyLinks: true)) marginsValue = marginsValue.merged(with: parent.margins?.resolveOptionalValue(context: context, validator: ResolvedValue.marginsValidator, useOnlyLinks: true)) paddingsValue = paddingsValue.merged(with: parent.paddings?.resolveOptionalValue(context: context, validator: ResolvedValue.paddingsValidator, useOnlyLinks: true)) @@ -477,6 +516,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibilityValue.errorsOrWarnings?.map { .nestedObjectError(field: "accessibility", error: $0) }, activeItemColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_item_color", error: $0) }, activeItemSizeValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_item_size", error: $0) }, + activeShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "active_shape", error: $0) }, alignmentHorizontalValue.errorsOrWarnings?.map { .nestedObjectError(field: "alignment_horizontal", error: $0) }, alignmentVerticalValue.errorsOrWarnings?.map { .nestedObjectError(field: "alignment_vertical", error: $0) }, alphaValue.errorsOrWarnings?.map { .nestedObjectError(field: "alpha", error: $0) }, @@ -489,6 +529,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { heightValue.errorsOrWarnings?.map { .nestedObjectError(field: "height", error: $0) }, idValue.errorsOrWarnings?.map { .nestedObjectError(field: "id", error: $0) }, inactiveItemColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_item_color", error: $0) }, + inactiveMinimumShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_minimum_shape", error: $0) }, + inactiveShapeValue.errorsOrWarnings?.map { .nestedObjectError(field: "inactive_shape", error: $0) }, itemsPlacementValue.errorsOrWarnings?.map { .nestedObjectError(field: "items_placement", error: $0) }, marginsValue.errorsOrWarnings?.map { .nestedObjectError(field: "margins", error: $0) }, minimumItemSizeValue.errorsOrWarnings?.map { .nestedObjectError(field: "minimum_item_size", error: $0) }, @@ -513,6 +555,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: accessibilityValue.value, activeItemColor: activeItemColorValue.value, activeItemSize: activeItemSizeValue.value, + activeShape: activeShapeValue.value, alignmentHorizontal: alignmentHorizontalValue.value, alignmentVertical: alignmentVerticalValue.value, alpha: alphaValue.value, @@ -525,6 +568,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: heightValue.value, id: idValue.value, inactiveItemColor: inactiveItemColorValue.value, + inactiveMinimumShape: inactiveMinimumShapeValue.value, + inactiveShape: inactiveShapeValue.value, itemsPlacement: itemsPlacementValue.value, margins: marginsValue.value, minimumItemSize: minimumItemSizeValue.value, @@ -560,6 +605,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: accessibility ?? mergedParent.accessibility, activeItemColor: activeItemColor ?? mergedParent.activeItemColor, activeItemSize: activeItemSize ?? mergedParent.activeItemSize, + activeShape: activeShape ?? mergedParent.activeShape, alignmentHorizontal: alignmentHorizontal ?? mergedParent.alignmentHorizontal, alignmentVertical: alignmentVertical ?? mergedParent.alignmentVertical, alpha: alpha ?? mergedParent.alpha, @@ -572,6 +618,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: height ?? mergedParent.height, id: id ?? mergedParent.id, inactiveItemColor: inactiveItemColor ?? mergedParent.inactiveItemColor, + inactiveMinimumShape: inactiveMinimumShape ?? mergedParent.inactiveMinimumShape, + inactiveShape: inactiveShape ?? mergedParent.inactiveShape, itemsPlacement: itemsPlacement ?? mergedParent.itemsPlacement, margins: margins ?? mergedParent.margins, minimumItemSize: minimumItemSize ?? mergedParent.minimumItemSize, @@ -602,6 +650,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { accessibility: merged.accessibility?.tryResolveParent(templates: templates), activeItemColor: merged.activeItemColor, activeItemSize: merged.activeItemSize, + activeShape: merged.activeShape?.tryResolveParent(templates: templates), alignmentHorizontal: merged.alignmentHorizontal, alignmentVertical: merged.alignmentVertical, alpha: merged.alpha, @@ -614,6 +663,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable { height: merged.height?.tryResolveParent(templates: templates), id: merged.id, inactiveItemColor: merged.inactiveItemColor, + inactiveMinimumShape: merged.inactiveMinimumShape?.tryResolveParent(templates: templates), + inactiveShape: merged.inactiveShape?.tryResolveParent(templates: templates), itemsPlacement: merged.itemsPlacement?.tryResolveParent(templates: templates), margins: merged.margins?.tryResolveParent(templates: templates), minimumItemSize: merged.minimumItemSize, diff --git a/client/ios/DivKit/generated_sources/DivRoundedRectangleShape.swift b/client/ios/DivKit/generated_sources/DivRoundedRectangleShape.swift index 546e4163a..ac0507fd1 100644 --- a/client/ios/DivKit/generated_sources/DivRoundedRectangleShape.swift +++ b/client/ios/DivKit/generated_sources/DivRoundedRectangleShape.swift @@ -7,9 +7,18 @@ import TemplatesSupport public final class DivRoundedRectangleShape { public static let type: String = "rounded_rectangle" + public let backgroundColor: Expression? public let cornerRadius: DivFixedSize // default value: DivFixedSize(value: .value(5)) public let itemHeight: DivFixedSize // default value: DivFixedSize(value: .value(10)) public let itemWidth: DivFixedSize // default value: DivFixedSize(value: .value(10)) + public let stroke: DivStroke? + + public func resolveBackgroundColor(_ resolver: ExpressionResolver) -> Color? { + resolver.resolveStringBasedValue(expression: backgroundColor, initializer: Color.color(withHexString:)) + } + + static let backgroundColorValidator: AnyValueValidator = + makeNoOpValueValidator() static let cornerRadiusValidator: AnyValueValidator = makeNoOpValueValidator() @@ -20,14 +29,21 @@ public final class DivRoundedRectangleShape { static let itemWidthValidator: AnyValueValidator = makeNoOpValueValidator() + static let strokeValidator: AnyValueValidator = + makeNoOpValueValidator() + init( + backgroundColor: Expression? = nil, cornerRadius: DivFixedSize? = nil, itemHeight: DivFixedSize? = nil, - itemWidth: DivFixedSize? = nil + itemWidth: DivFixedSize? = nil, + stroke: DivStroke? = nil ) { + self.backgroundColor = backgroundColor self.cornerRadius = cornerRadius ?? DivFixedSize(value: .value(5)) self.itemHeight = itemHeight ?? DivFixedSize(value: .value(10)) self.itemWidth = itemWidth ?? DivFixedSize(value: .value(10)) + self.stroke = stroke } } @@ -35,9 +51,15 @@ public final class DivRoundedRectangleShape { extension DivRoundedRectangleShape: Equatable { public static func ==(lhs: DivRoundedRectangleShape, rhs: DivRoundedRectangleShape) -> Bool { guard + lhs.backgroundColor == rhs.backgroundColor, lhs.cornerRadius == rhs.cornerRadius, - lhs.itemHeight == rhs.itemHeight, - lhs.itemWidth == rhs.itemWidth + lhs.itemHeight == rhs.itemHeight + else { + return false + } + guard + lhs.itemWidth == rhs.itemWidth, + lhs.stroke == rhs.stroke else { return false } @@ -50,9 +72,11 @@ extension DivRoundedRectangleShape: Serializable { public func toDictionary() -> [String: ValidSerializationValue] { var result: [String: ValidSerializationValue] = [:] result["type"] = Self.type + result["background_color"] = backgroundColor?.toValidSerializationValue() result["corner_radius"] = cornerRadius.toDictionary() result["item_height"] = itemHeight.toDictionary() result["item_width"] = itemWidth.toDictionary() + result["stroke"] = stroke?.toDictionary() return result } } diff --git a/client/ios/DivKit/generated_sources/DivRoundedRectangleShapeTemplate.swift b/client/ios/DivKit/generated_sources/DivRoundedRectangleShapeTemplate.swift index 1fc7c7b7e..54a759cd9 100644 --- a/client/ios/DivKit/generated_sources/DivRoundedRectangleShapeTemplate.swift +++ b/client/ios/DivKit/generated_sources/DivRoundedRectangleShapeTemplate.swift @@ -8,9 +8,11 @@ import TemplatesSupport public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDeserializable { public static let type: String = "rounded_rectangle" public let parent: String? // at least 1 char + public let backgroundColor: Field>? public let cornerRadius: Field? // default value: DivFixedSize(value: .value(5)) public let itemHeight: Field? // default value: DivFixedSize(value: .value(10)) public let itemWidth: Field? // default value: DivFixedSize(value: .value(10)) + public let stroke: Field? static let parentValidator: AnyValueValidator = makeStringValidator(minLength: 1) @@ -18,37 +20,49 @@ public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDese public convenience init(dictionary: [String: Any], templateToType: TemplateToType) throws { self.init( parent: try dictionary.getOptionalField("type", validator: Self.parentValidator), + backgroundColor: try dictionary.getOptionalExpressionField("background_color", transform: Color.color(withHexString:)), cornerRadius: try dictionary.getOptionalField("corner_radius", templateToType: templateToType), itemHeight: try dictionary.getOptionalField("item_height", templateToType: templateToType), - itemWidth: try dictionary.getOptionalField("item_width", templateToType: templateToType) + itemWidth: try dictionary.getOptionalField("item_width", templateToType: templateToType), + stroke: try dictionary.getOptionalField("stroke", templateToType: templateToType) ) } init( parent: String?, + backgroundColor: Field>? = nil, cornerRadius: Field? = nil, itemHeight: Field? = nil, - itemWidth: Field? = nil + itemWidth: Field? = nil, + stroke: Field? = nil ) { self.parent = parent + self.backgroundColor = backgroundColor self.cornerRadius = cornerRadius self.itemHeight = itemHeight self.itemWidth = itemWidth + self.stroke = stroke } private static func resolveOnlyLinks(context: Context, parent: DivRoundedRectangleShapeTemplate?) -> DeserializationResult { + let backgroundColorValue = parent?.backgroundColor?.resolveOptionalValue(context: context, transform: Color.color(withHexString:), validator: ResolvedValue.backgroundColorValidator) ?? .noValue let cornerRadiusValue = parent?.cornerRadius?.resolveOptionalValue(context: context, validator: ResolvedValue.cornerRadiusValidator, useOnlyLinks: true) ?? .noValue let itemHeightValue = parent?.itemHeight?.resolveOptionalValue(context: context, validator: ResolvedValue.itemHeightValidator, useOnlyLinks: true) ?? .noValue let itemWidthValue = parent?.itemWidth?.resolveOptionalValue(context: context, validator: ResolvedValue.itemWidthValidator, useOnlyLinks: true) ?? .noValue + let strokeValue = parent?.stroke?.resolveOptionalValue(context: context, validator: ResolvedValue.strokeValidator, useOnlyLinks: true) ?? .noValue let errors = mergeErrors( + backgroundColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "background_color", error: $0) }, cornerRadiusValue.errorsOrWarnings?.map { .nestedObjectError(field: "corner_radius", error: $0) }, itemHeightValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_height", error: $0) }, - itemWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_width", error: $0) } + itemWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_width", error: $0) }, + strokeValue.errorsOrWarnings?.map { .nestedObjectError(field: "stroke", error: $0) } ) let result = DivRoundedRectangleShape( + backgroundColor: backgroundColorValue.value, cornerRadius: cornerRadiusValue.value, itemHeight: itemHeightValue.value, - itemWidth: itemWidthValue.value + itemWidth: itemWidthValue.value, + stroke: strokeValue.value ) return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) } @@ -57,23 +71,33 @@ public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDese if useOnlyLinks { return resolveOnlyLinks(context: context, parent: parent) } + var backgroundColorValue: DeserializationResult> = parent?.backgroundColor?.value() ?? .noValue var cornerRadiusValue: DeserializationResult = .noValue var itemHeightValue: DeserializationResult = .noValue var itemWidthValue: DeserializationResult = .noValue + var strokeValue: DeserializationResult = .noValue context.templateData.forEach { key, __dictValue in switch key { + case "background_color": + backgroundColorValue = deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.backgroundColorValidator).merged(with: backgroundColorValue) case "corner_radius": cornerRadiusValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.cornerRadiusValidator, type: DivFixedSizeTemplate.self).merged(with: cornerRadiusValue) case "item_height": itemHeightValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemHeightValidator, type: DivFixedSizeTemplate.self).merged(with: itemHeightValue) case "item_width": itemWidthValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemWidthValidator, type: DivFixedSizeTemplate.self).merged(with: itemWidthValue) + case "stroke": + strokeValue = deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.strokeValidator, type: DivStrokeTemplate.self).merged(with: strokeValue) + case parent?.backgroundColor?.link: + backgroundColorValue = backgroundColorValue.merged(with: deserialize(__dictValue, transform: Color.color(withHexString:), validator: ResolvedValue.backgroundColorValidator)) case parent?.cornerRadius?.link: cornerRadiusValue = cornerRadiusValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.cornerRadiusValidator, type: DivFixedSizeTemplate.self)) case parent?.itemHeight?.link: itemHeightValue = itemHeightValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemHeightValidator, type: DivFixedSizeTemplate.self)) case parent?.itemWidth?.link: itemWidthValue = itemWidthValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.itemWidthValidator, type: DivFixedSizeTemplate.self)) + case parent?.stroke?.link: + strokeValue = strokeValue.merged(with: deserialize(__dictValue, templates: context.templates, templateToType: context.templateToType, validator: ResolvedValue.strokeValidator, type: DivStrokeTemplate.self)) default: break } } @@ -81,16 +105,21 @@ public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDese cornerRadiusValue = cornerRadiusValue.merged(with: parent.cornerRadius?.resolveOptionalValue(context: context, validator: ResolvedValue.cornerRadiusValidator, useOnlyLinks: true)) itemHeightValue = itemHeightValue.merged(with: parent.itemHeight?.resolveOptionalValue(context: context, validator: ResolvedValue.itemHeightValidator, useOnlyLinks: true)) itemWidthValue = itemWidthValue.merged(with: parent.itemWidth?.resolveOptionalValue(context: context, validator: ResolvedValue.itemWidthValidator, useOnlyLinks: true)) + strokeValue = strokeValue.merged(with: parent.stroke?.resolveOptionalValue(context: context, validator: ResolvedValue.strokeValidator, useOnlyLinks: true)) } let errors = mergeErrors( + backgroundColorValue.errorsOrWarnings?.map { .nestedObjectError(field: "background_color", error: $0) }, cornerRadiusValue.errorsOrWarnings?.map { .nestedObjectError(field: "corner_radius", error: $0) }, itemHeightValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_height", error: $0) }, - itemWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_width", error: $0) } + itemWidthValue.errorsOrWarnings?.map { .nestedObjectError(field: "item_width", error: $0) }, + strokeValue.errorsOrWarnings?.map { .nestedObjectError(field: "stroke", error: $0) } ) let result = DivRoundedRectangleShape( + backgroundColor: backgroundColorValue.value, cornerRadius: cornerRadiusValue.value, itemHeight: itemHeightValue.value, - itemWidth: itemWidthValue.value + itemWidth: itemWidthValue.value, + stroke: strokeValue.value ) return errors.isEmpty ? .success(result) : .partialSuccess(result, warnings: NonEmptyArray(errors)!) } @@ -104,9 +133,11 @@ public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDese return DivRoundedRectangleShapeTemplate( parent: nil, + backgroundColor: backgroundColor ?? mergedParent.backgroundColor, cornerRadius: cornerRadius ?? mergedParent.cornerRadius, itemHeight: itemHeight ?? mergedParent.itemHeight, - itemWidth: itemWidth ?? mergedParent.itemWidth + itemWidth: itemWidth ?? mergedParent.itemWidth, + stroke: stroke ?? mergedParent.stroke ) } @@ -115,9 +146,11 @@ public final class DivRoundedRectangleShapeTemplate: TemplateValue, TemplateDese return DivRoundedRectangleShapeTemplate( parent: nil, + backgroundColor: merged.backgroundColor, cornerRadius: merged.cornerRadius?.tryResolveParent(templates: templates), itemHeight: merged.itemHeight?.tryResolveParent(templates: templates), - itemWidth: merged.itemWidth?.tryResolveParent(templates: templates) + itemWidth: merged.itemWidth?.tryResolveParent(templates: templates), + stroke: merged.stroke?.tryResolveParent(templates: templates) ) } } diff --git a/client/ios/LayoutKit/LayoutKit/Blocks/PageControlBlock.swift b/client/ios/LayoutKit/LayoutKit/Blocks/PageControlBlock.swift index 2230fdccb..c7898ab38 100644 --- a/client/ios/LayoutKit/LayoutKit/Blocks/PageControlBlock.swift +++ b/client/ios/LayoutKit/LayoutKit/Blocks/PageControlBlock.swift @@ -50,10 +50,10 @@ public final class PageControlBlock: BlockWithTraits { case let .fixed(value): return value case let .intrinsic(_, minSize, maxSize): - let height = configuration.pageSize.height * configuration.highlightingScale + let height = configuration.pageSize.height * configuration.highlightedHeightScale return clamp(height, min: minSize, max: maxSize) case .weighted: - return configuration.pageSize.height * configuration.highlightingScale + return configuration.pageSize.height * configuration.highlightedHeightScale } } diff --git a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/IndicatorState/IndicatorStateAnimator.swift b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/IndicatorState/IndicatorStateAnimator.swift index 84f2e94ce..5bf9ab3b8 100644 --- a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/IndicatorState/IndicatorStateAnimator.swift +++ b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/IndicatorState/IndicatorStateAnimator.swift @@ -20,12 +20,39 @@ struct IndicatorStateAnimator { } } - func highlightedIndicatorScale(for state: IndicatorState) -> CGFloat { + func indicatorBorder(for state: IndicatorState) -> BlockBorder { + let normalBorder = configuration.normalBorder + let highlightedBorder = configuration.highlightedBorder + + switch state.kind { + case .highlighted where configuration.animation == .scale: + let border = BlockBorder( + color: normalBorder.color.interpolate( + to: highlightedBorder.color, + progress: state.progress + ), + width: normalBorder.width.interpolated( + to: highlightedBorder.width, + progress: state.progress + ) + ) + return border + case .highlighted, .normal: + return normalBorder + } + } + + func highlightedIndicatorScale(for state: IndicatorState, borderScale: Scale) -> Scale { switch configuration.animation { case .scale: - return configuration.highlightingScale.interpolated(to: 1, progress: 1 - state.progress) + return ( + configuration.highlightedWidthScale + .interpolated(to: 1, progress: 1 - state.progress) * borderScale.x, + configuration.highlightedHeightScale + .interpolated(to: 1, progress: 1 - state.progress) * borderScale.y + ) case .worm, .slider: - return 1 + return (1, 1) } } @@ -56,7 +83,7 @@ struct IndicatorStateAnimator { return spaceBetweenCenters case let .stretch(_, maxVisibleItems): let visiblePageCount = min(maxVisibleItems, numberOfPages) - return boundsWidth / (CGFloat(visiblePageCount)) + return boundsWidth / CGFloat(visiblePageCount) } } } diff --git a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorConfiguration.swift b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorConfiguration.swift index c9002713f..57952837c 100644 --- a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorConfiguration.swift +++ b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorConfiguration.swift @@ -14,12 +14,16 @@ public struct PageIndicatorConfiguration: Equatable { public let highlightedColor: Color public let normalColor: Color + public let highlightedBorder: BlockBorder + public let normalBorder: BlockBorder // "Normal" scale is considered as 1 // for fully selected page - public let highlightingScale: CGFloat + public let highlightedHeightScale: CGFloat + public let highlightedWidthScale: CGFloat // for edge pages if not all pages are visible - public let disappearingScale: CGFloat + public let disappearingHeightScale: CGFloat + public let disappearingWidthScale: CGFloat public let pageSize: CGSize public let pageCornerRadius: CGFloat public let animation: Animation @@ -28,8 +32,12 @@ public struct PageIndicatorConfiguration: Equatable { public init( highlightedColor: Color, normalColor: Color, - highlightingScale: CGFloat, - disappearingScale: CGFloat, + highlightedBorder: BlockBorder, + normalBorder: BlockBorder, + highlightedHeightScale: CGFloat, + highlightedWidthScale: CGFloat, + disappearingHeightScale: CGFloat, + disappearingWidthScale: CGFloat, pageSize: CGSize, pageCornerRadius: CGFloat, animation: Animation, @@ -37,11 +45,39 @@ public struct PageIndicatorConfiguration: Equatable { ) { self.highlightedColor = highlightedColor self.normalColor = normalColor - self.highlightingScale = highlightingScale - self.disappearingScale = disappearingScale + self.highlightedBorder = highlightedBorder + self.normalBorder = normalBorder + self.highlightedHeightScale = highlightedHeightScale + self.highlightedWidthScale = highlightedWidthScale + self.disappearingHeightScale = disappearingHeightScale + self.disappearingWidthScale = disappearingWidthScale self.pageSize = pageSize self.pageCornerRadius = pageCornerRadius self.animation = animation self.itemPlacement = itemPlacement } } + +extension PageIndicatorConfiguration { + public struct RoundedRectangleIndicator { + public let size: CGSize + public let cornerRadius: CGFloat + public let backgroundColor: Color + public let borderWidth: CGFloat + public let borderColor: Color + + public init( + size: CGSize, + cornerRadius: CGFloat, + backgroundColor: Color, + borderWidth: CGFloat, + borderColor: Color + ) { + self.size = size + self.cornerRadius = cornerRadius + self.backgroundColor = backgroundColor + self.borderWidth = borderWidth + self.borderColor = borderColor + } + } +} diff --git a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorLayer.swift b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorLayer.swift index 49413cb6f..891e2e565 100644 --- a/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorLayer.swift +++ b/client/ios/LayoutKit/LayoutKit/UI/Base/PageIndicator/PageIndicatorLayer.swift @@ -2,14 +2,20 @@ import UIKit import CommonCore +typealias Scale = (x: CGFloat, y: CGFloat) + final class ScrollPageIndicatorLayer: CALayer { @NSManaged var currentIndexPosition: CGFloat var numberOfPages = 0 var configuration = PageIndicatorConfiguration( highlightedColor: .clear, normalColor: .clear, - highlightingScale: 0, - disappearingScale: 0, + highlightedBorder: BlockBorder(color: .clear, width: 0), + normalBorder: BlockBorder(color: .clear, width: 0), + highlightedHeightScale: 0, + highlightedWidthScale: 0, + disappearingHeightScale: 0, + disappearingWidthScale: 0, pageSize: .zero, pageCornerRadius: 0, animation: .scale, @@ -109,13 +115,24 @@ extension ScrollPageIndicatorLayer { animator: IndicatorStateAnimator ) -> Indicator { let rect = indicatorRect(at: state.index) - let scale = indicatorScale(for: state, animator: animator) - - return Indicator( - rect: rect.withScaledSize(scale), - cornerRadius: configuration.pageCornerRadius * scale, - color: animator.indicatorColor(for: state).cgColor + let border = animator.indicatorBorder(for: state) + let scale = indicatorScale( + for: state, + animator: animator, + borderScale: ( + (rect.width - border.width) / rect.width, + (rect.height - border.width) / rect.height + ) ) + let indicator = Indicator( + rect: rect.withScaledSize(x: scale.x, y: scale.y), + cornerRadius: configuration.pageCornerRadius * scale.y, + color: animator.indicatorColor(for: state).cgColor, + borderColor: animator.indicatorBorder(for: state).color.cgColor, + borderWidth: animator.indicatorBorder(for: state).width + ) + + return indicator } fileprivate func makeActiveIndicator(animator: IndicatorStateAnimator) -> Indicator? { @@ -129,12 +146,15 @@ extension ScrollPageIndicatorLayer { var rect = indicatorRect(at: Int(roundedIndex)) rect.size.width += offsets.widthOffset rect.center.x += offsets.xOffset - - return Indicator( + let indicator = Indicator( rect: rect, cornerRadius: configuration.pageCornerRadius, - color: configuration.highlightedColor.cgColor + color: configuration.highlightedColor.cgColor, + borderColor: configuration.highlightedBorder.color.cgColor, + borderWidth: configuration.highlightedBorder.width ) + + return indicator } fileprivate func indicatorRect(at index: Int) -> CGRect { @@ -153,13 +173,22 @@ extension ScrollPageIndicatorLayer { fileprivate func indicatorScale( for state: IndicatorState, - animator: IndicatorStateAnimator - ) -> CGFloat { + animator: IndicatorStateAnimator, + borderScale: Scale + ) -> Scale { switch state.kind { case .normal: - return configuration.disappearingScale.interpolated(to: 1, progress: state.progress) + return ( + configuration.disappearingWidthScale + .interpolated(to: 1, progress: state.progress) * borderScale.x, + configuration.disappearingHeightScale + .interpolated(to: 1, progress: state.progress) * borderScale.y + ) case .highlighted: - return animator.highlightedIndicatorScale(for: state) + return animator.highlightedIndicatorScale( + for: state, + borderScale: (borderScale.x, borderScale.y) + ) } } } @@ -168,6 +197,8 @@ private struct Indicator { let rect: CGRect let cornerRadius: CGFloat let color: CGColor + let borderColor: CGColor + let borderWidth: CGFloat func render(in ctx: CGContext) { let path = CGPath( @@ -177,7 +208,10 @@ private struct Indicator { transform: nil ) ctx.addPath(path) + ctx.setLineWidth(borderWidth) + ctx.setStrokeColor(borderColor) ctx.setFillColor(color) - ctx.fillPath() + ctx.closePath() + ctx.drawPath(using: .fillStroke) } } diff --git a/schema/div-indicator.json b/schema/div-indicator.json index f75bc565a..07545adfd 100644 --- a/schema/div-indicator.json +++ b/schema/div-indicator.json @@ -69,17 +69,23 @@ "active_shape": { "$ref": "div-rounded-rectangle-shape.json", "$description": "translations.json#/div_indicator_active_shape", - "platforms": [] + "platforms": [ + "ios" + ] }, "inactive_shape": { "$ref": "div-rounded-rectangle-shape.json", "$description": "translations.json#/div_indicator_inactive_shape", - "platforms": [] + "platforms": [ + "ios" + ] }, "inactive_minimum_shape": { "$ref": "div-rounded-rectangle-shape.json", "$description": "translations.json#/div_indicator_inactive_minimum_shape", - "platforms": [] + "platforms": [ + "ios" + ] }, "animation": { "type": "string",