mirror of
https://github.com/divkit/divkit.git
synced 2026-06-06 20:07:59 +00:00
Add border in page indicator
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+31
-4
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
-6
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user