Add border in page indicator

This commit is contained in:
dashapalshau
2023-01-18 16:17:37 +03:00
parent d32d218a8b
commit ea33e8a65c
10 changed files with 372 additions and 72 deletions
@@ -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(
@@ -17,6 +17,7 @@ public final class DivIndicator: DivBase {
public let accessibility: DivAccessibility
public let activeItemColor: Expression<Color> // default value: #ffdc60
public let activeItemSize: Expression<Double> // constraint: number > 0; default value: 1.3
public let activeShape: DivRoundedRectangleShape?
public let alignmentHorizontal: Expression<DivAlignmentHorizontal>?
public let alignmentVertical: Expression<DivAlignmentVertical>?
public let alpha: Expression<Double> // 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<Color> // default value: #33919cb5
public let inactiveMinimumShape: DivRoundedRectangleShape?
public let inactiveShape: DivRoundedRectangleShape?
public let itemsPlacement: DivIndicatorItemPlacement?
public let margins: DivEdgeInsets
public let minimumItemSize: Expression<Double> // constraint: number > 0; default value: 0.5
@@ -102,6 +105,9 @@ public final class DivIndicator: DivBase {
static let activeItemSizeValidator: AnyValueValidator<Double> =
makeValueValidator(valueValidator: { $0 > 0 })
static let activeShapeValidator: AnyValueValidator<DivRoundedRectangleShape> =
makeNoOpValueValidator()
static let alignmentHorizontalValidator: AnyValueValidator<DivAlignmentHorizontal> =
makeNoOpValueValidator()
@@ -138,6 +144,12 @@ public final class DivIndicator: DivBase {
static let inactiveItemColorValidator: AnyValueValidator<Color> =
makeNoOpValueValidator()
static let inactiveMinimumShapeValidator: AnyValueValidator<DivRoundedRectangleShape> =
makeNoOpValueValidator()
static let inactiveShapeValidator: AnyValueValidator<DivRoundedRectangleShape> =
makeNoOpValueValidator()
static let itemsPlacementValidator: AnyValueValidator<DivIndicatorItemPlacement> =
makeNoOpValueValidator()
@@ -199,6 +211,7 @@ public final class DivIndicator: DivBase {
accessibility: DivAccessibility? = nil,
activeItemColor: Expression<Color>? = nil,
activeItemSize: Expression<Double>? = nil,
activeShape: DivRoundedRectangleShape? = nil,
alignmentHorizontal: Expression<DivAlignmentHorizontal>? = nil,
alignmentVertical: Expression<DivAlignmentVertical>? = nil,
alpha: Expression<Double>? = nil,
@@ -211,6 +224,8 @@ public final class DivIndicator: DivBase {
height: DivSize? = nil,
id: String? = nil,
inactiveItemColor: Expression<Color>? = nil,
inactiveMinimumShape: DivRoundedRectangleShape? = nil,
inactiveShape: DivRoundedRectangleShape? = nil,
itemsPlacement: DivIndicatorItemPlacement? = nil,
margins: DivEdgeInsets? = nil,
minimumItemSize: Expression<Double>? = 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()
@@ -13,6 +13,7 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable {
public let accessibility: Field<DivAccessibilityTemplate>?
public let activeItemColor: Field<Expression<Color>>? // default value: #ffdc60
public let activeItemSize: Field<Expression<Double>>? // constraint: number > 0; default value: 1.3
public let activeShape: Field<DivRoundedRectangleShapeTemplate>?
public let alignmentHorizontal: Field<Expression<DivAlignmentHorizontal>>?
public let alignmentVertical: Field<Expression<DivAlignmentVertical>>?
public let alpha: Field<Expression<Double>>? // 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<DivSizeTemplate>? // default value: .divWrapContentSize(DivWrapContentSize())
public let id: Field<String>? // at least 1 char
public let inactiveItemColor: Field<Expression<Color>>? // default value: #33919cb5
public let inactiveMinimumShape: Field<DivRoundedRectangleShapeTemplate>?
public let inactiveShape: Field<DivRoundedRectangleShapeTemplate>?
public let itemsPlacement: Field<DivIndicatorItemPlacementTemplate>?
public let margins: Field<DivEdgeInsetsTemplate>?
public let minimumItemSize: Field<Expression<Double>>? // 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<DivAccessibilityTemplate>? = nil,
activeItemColor: Field<Expression<Color>>? = nil,
activeItemSize: Field<Expression<Double>>? = nil,
activeShape: Field<DivRoundedRectangleShapeTemplate>? = nil,
alignmentHorizontal: Field<Expression<DivAlignmentHorizontal>>? = nil,
alignmentVertical: Field<Expression<DivAlignmentVertical>>? = nil,
alpha: Field<Expression<Double>>? = nil,
@@ -105,6 +112,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable {
height: Field<DivSizeTemplate>? = nil,
id: Field<String>? = nil,
inactiveItemColor: Field<Expression<Color>>? = nil,
inactiveMinimumShape: Field<DivRoundedRectangleShapeTemplate>? = nil,
inactiveShape: Field<DivRoundedRectangleShapeTemplate>? = nil,
itemsPlacement: Field<DivIndicatorItemPlacementTemplate>? = nil,
margins: Field<DivEdgeInsetsTemplate>? = nil,
minimumItemSize: Field<Expression<Double>>? = 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<DivAccessibility> = .noValue
var activeItemColorValue: DeserializationResult<Expression<Color>> = parent?.activeItemColor?.value() ?? .noValue
var activeItemSizeValue: DeserializationResult<Expression<Double>> = parent?.activeItemSize?.value() ?? .noValue
var activeShapeValue: DeserializationResult<DivRoundedRectangleShape> = .noValue
var alignmentHorizontalValue: DeserializationResult<Expression<DivAlignmentHorizontal>> = parent?.alignmentHorizontal?.value() ?? .noValue
var alignmentVerticalValue: DeserializationResult<Expression<DivAlignmentVertical>> = parent?.alignmentVertical?.value() ?? .noValue
var alphaValue: DeserializationResult<Expression<Double>> = parent?.alpha?.value() ?? .noValue
@@ -291,6 +313,8 @@ public final class DivIndicatorTemplate: TemplateValue, TemplateDeserializable {
var heightValue: DeserializationResult<DivSize> = .noValue
var idValue: DeserializationResult<String> = parent?.id?.value(validatedBy: ResolvedValue.idValidator) ?? .noValue
var inactiveItemColorValue: DeserializationResult<Expression<Color>> = parent?.inactiveItemColor?.value() ?? .noValue
var inactiveMinimumShapeValue: DeserializationResult<DivRoundedRectangleShape> = .noValue
var inactiveShapeValue: DeserializationResult<DivRoundedRectangleShape> = .noValue
var itemsPlacementValue: DeserializationResult<DivIndicatorItemPlacement> = .noValue
var marginsValue: DeserializationResult<DivEdgeInsets> = .noValue
var minimumItemSizeValue: DeserializationResult<Expression<Double>> = 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,
@@ -7,9 +7,18 @@ import TemplatesSupport
public final class DivRoundedRectangleShape {
public static let type: String = "rounded_rectangle"
public let backgroundColor: Expression<Color>?
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<Color> =
makeNoOpValueValidator()
static let cornerRadiusValidator: AnyValueValidator<DivFixedSize> =
makeNoOpValueValidator()
@@ -20,14 +29,21 @@ public final class DivRoundedRectangleShape {
static let itemWidthValidator: AnyValueValidator<DivFixedSize> =
makeNoOpValueValidator()
static let strokeValidator: AnyValueValidator<DivStroke> =
makeNoOpValueValidator()
init(
backgroundColor: Expression<Color>? = 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
}
}
@@ -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<Expression<Color>>?
public let cornerRadius: Field<DivFixedSizeTemplate>? // default value: DivFixedSize(value: .value(5))
public let itemHeight: Field<DivFixedSizeTemplate>? // default value: DivFixedSize(value: .value(10))
public let itemWidth: Field<DivFixedSizeTemplate>? // default value: DivFixedSize(value: .value(10))
public let stroke: Field<DivStrokeTemplate>?
static let parentValidator: AnyValueValidator<String> =
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<Expression<Color>>? = nil,
cornerRadius: Field<DivFixedSizeTemplate>? = nil,
itemHeight: Field<DivFixedSizeTemplate>? = nil,
itemWidth: Field<DivFixedSizeTemplate>? = nil
itemWidth: Field<DivFixedSizeTemplate>? = nil,
stroke: Field<DivStrokeTemplate>? = 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<DivRoundedRectangleShape> {
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<Expression<Color>> = parent?.backgroundColor?.value() ?? .noValue
var cornerRadiusValue: DeserializationResult<DivFixedSize> = .noValue
var itemHeightValue: DeserializationResult<DivFixedSize> = .noValue
var itemWidthValue: DeserializationResult<DivFixedSize> = .noValue
var strokeValue: DeserializationResult<DivStroke> = .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)
)
}
}
@@ -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
}
}
@@ -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)
}
}
}
@@ -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
}
}
}
@@ -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)
}
}
+9 -3
View File
@@ -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",