Files
divkit/client/ios/DivKit/Extensions/DivSliderExtensions.swift
denlvovich d0e6273230 Slider press_start and press_end actions implemented
commit_hash:d8b48885a44f2a30f190b2f8af065fc1d2a8c898
2026-05-07 01:32:08 +03:00

278 lines
9.1 KiB
Swift

import CoreGraphics
import Foundation
import LayoutKit
import VGSL
extension DivSlider: DivBlockModeling {
public func makeBlock(context: DivBlockModelingContext) throws -> Block {
let context = modifiedContextParentPath(context)
return try applyBaseProperties(
to: { try makeBaseBlock(context: context) },
context: context,
actionsHolder: nil
)
}
private func makeBaseBlock(context: DivBlockModelingContext) throws -> Block {
let expressionResolver = context.expressionResolver
let firstThumbValue: Binding<Int> = thumbValueVariable.flatMap {
context.makeBinding(variableName: $0, defaultValue: 0)
} ?? .zero
let firstThumb = SliderModel.ThumbModel(
block: makeThumbBlock(
thumb: thumbStyle
.makeBlock(context: context, corners: .all),
textBlock: thumbTextStyle?.makeThumbTextBlock(
context: context,
value: firstThumbValue.value
),
textOffset: thumbTextStyle.flatMap {
CGPoint(
x: $0.offset?.x.resolveValue(expressionResolver) ?? 0,
y: $0.offset?.y.resolveValue(expressionResolver) ?? 0
)
} ?? .zero
),
value: firstThumbValue,
size: CGSize(
width: thumbStyle.resolveWidth(context),
height: thumbStyle.resolveHeight(context)
),
offsetX: thumbTextStyle?.offset?.x.resolveValue(expressionResolver) ?? 0,
offsetY: thumbTextStyle?.offset?.y.resolveValue(expressionResolver) ?? 0
)
let secondThumb: SliderModel.ThumbModel?
if let thumbSecondaryValueVariable {
let secondThumbValue = context.makeBinding(
variableName: thumbSecondaryValueVariable,
defaultValue: 0
)
secondThumb = SliderModel.ThumbModel(
block: makeThumbBlock(
thumb: thumbSecondaryStyle?.makeBlock(context: context, corners: .all) ?? thumbStyle
.makeBlock(context: context, corners: .all),
textBlock: (thumbSecondaryTextStyle ?? thumbTextStyle)?.makeThumbTextBlock(
context: context,
value: secondThumbValue.value
),
textOffset: thumbSecondaryTextStyle.flatMap {
CGPoint(
x: $0.offset?.x.resolveValue(expressionResolver) ?? 0,
y: $0.offset?.y.resolveValue(expressionResolver) ?? 0
)
} ?? .zero
),
value: secondThumbValue,
size: CGSize(
width: thumbSecondaryStyle?.resolveWidth(context)
?? thumbStyle.resolveWidth(context),
height: thumbSecondaryStyle?.resolveHeight(context)
?? thumbStyle.resolveHeight(context)
),
offsetX: thumbSecondaryTextStyle?.offset?.x.resolveValue(expressionResolver) ?? 0,
offsetY: thumbSecondaryTextStyle?.offset?.y.resolveValue(expressionResolver) ?? 0
)
} else {
secondThumb = nil
}
let activeMark = makeRoundedRectangle(with: tickMarkActiveStyle, resolver: expressionResolver)
let inactiveMark = makeRoundedRectangle(
with: tickMarkInactiveStyle,
resolver: expressionResolver
)
let minValue = resolveMinValue(expressionResolver)
let maxValue = resolveMaxValue(expressionResolver)
let marksConfiguration = MarksConfigurationModel(
minValue: CGFloat(minValue),
maxValue: CGFloat(maxValue),
activeMark: activeMark ?? .empty,
inactiveMark: inactiveMark ?? .empty,
layoutDirection: context.layoutDirection
)
let pressStartActions = pressStartActions?.uiActions(context: context) ?? []
let pressEndActions = pressEndActions?.uiActions(context: context) ?? []
let sliderModel = SliderModel(
firstThumb: firstThumb,
secondThumb: secondThumb,
minValue: minValue,
maxValue: maxValue,
marksConfiguration: marksConfiguration,
ranges: makeRanges(ranges, with: context),
layoutDirection: context.layoutDirection,
path: context.path,
isEnabled: resolveIsEnabled(expressionResolver),
pressStartActions: pressStartActions,
pressEndActions: pressEndActions
)
return SliderBlock(
sliderModel: sliderModel,
widthTrait: resolveWidthTrait(context),
heightTrait: resolveHeightTrait(context)
)
}
private func makeRanges(
_ ranges: [DivSlider.Range]?,
with context: DivBlockModelingContext
) -> [SliderModel.RangeModel] {
let expressionResolver = context.expressionResolver
let minValue = resolveMinValue(expressionResolver)
let maxValue = resolveMaxValue(expressionResolver)
var sliderRanges: [SliderModel.RangeModel] = (ranges ?? []).map { range in
SliderModel.RangeModel(
start: range.resolveStart(expressionResolver) ?? minValue,
end: range.resolveEnd(expressionResolver) ?? maxValue,
margins: range.margins.resolve(context),
activeTrack: range.trackActiveStyle?.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
) ?? trackActiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
),
inactiveTrack: range.trackInactiveStyle?.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
) ?? trackInactiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: .all
)
)
}
let makeBasicRange: (Int, Int, CGRect.Corners) -> SliderModel.RangeModel = { [self] in
SliderModel.RangeModel(
start: $0,
end: $1,
margins: EdgeInsets.zero,
activeTrack: trackActiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: $2
),
inactiveTrack: trackInactiveStyle.makeBlock(
context: context,
widthTrait: .resizable,
corners: $2
)
)
}
sliderRanges.sort { $0.start < $1.start }
var lastRangeEnd = minValue
for (index, range) in sliderRanges.enumerated() {
if range.start != lastRangeEnd {
let corner: CGRect.Corners = index == 0 ? .left : .all
sliderRanges.append(makeBasicRange(lastRangeEnd, range.start, corner))
}
lastRangeEnd = range.end
}
if lastRangeEnd != maxValue {
let corner: CGRect.Corners = sliderRanges.count == 0 ? .all : .right
sliderRanges.append(makeBasicRange(lastRangeEnd, maxValue, corner))
}
sliderRanges.sort { $0.start < $1.start }
return sliderRanges
}
}
private func makeThumbBlock(
thumb: Block,
textBlock: Block?,
textOffset: CGPoint
) -> Block {
let insets = EdgeInsets(
top: max(textOffset.y, 0),
left: max(textOffset.x, 0),
bottom: max(-textOffset.y, 0),
right: max(-textOffset.x, 0)
) * 2
return LayeredBlock(
widthTrait: .intrinsic,
horizontalChildrenAlignment: .center,
verticalChildrenAlignment: .center,
children: [
thumb,
textBlock?.addingEdgeInsets(insets),
].compactMap { $0 }
)
}
private func makeRoundedRectangle(
with mark: DivDrawable?,
resolver: ExpressionResolver
) -> MarksConfigurationModel
.RoundedRectangle? {
guard let mark,
let divShapeDrawable = mark.value as? DivShapeDrawable else {
return nil
}
let color = divShapeDrawable.resolveColor(resolver) ?? .clear
let borderWidth = CGFloat(divShapeDrawable.stroke?.resolveWidth(resolver) ?? 0)
let borderColor = divShapeDrawable.stroke?.resolveColor(resolver) ?? .clear
switch divShapeDrawable.shape {
case let .divRoundedRectangleShape(shape):
return MarksConfigurationModel.RoundedRectangle(
size: CGSize(
width: shape.itemWidth.resolveValue(resolver) ?? 0,
height: shape.itemHeight.resolveValue(resolver) ?? 0
),
cornerRadius: CGFloat(shape.cornerRadius.resolveValue(resolver) ?? 0),
color: color,
borderWidth: borderWidth,
borderColor: borderColor
)
case let .divCircleShape(shape):
let cornerRadius = CGFloat(shape.radius.resolveValue(resolver) ?? 0)
let sideSize = cornerRadius * 2
return MarksConfigurationModel.RoundedRectangle(
size: CGSize(squareDimension: sideSize),
cornerRadius: cornerRadius,
color: color,
borderWidth: borderWidth,
borderColor: borderColor
)
}
}
extension DivSlider.TextStyle {
fileprivate func makeThumbTextBlock(
context: DivBlockModelingContext,
value: Int
) -> Block {
let expressionResolver = context.expressionResolver
let fontParams = FontParams(
family: "",
weight: resolveFontWeightValue(expressionResolver)
?? resolveFontWeight(expressionResolver)?.toInt(),
size: resolveFontSize(expressionResolver),
unit: resolveFontSizeUnit(expressionResolver),
featureSettings: nil,
variationSettings: resolveFontVariationSettings(expressionResolver)?
.mapValues { $0 as? NSNumber }.filteringNilValues()
)
let typo = Typo(font: context.font(fontParams))
.with(color: resolveTextColor(expressionResolver))
.with(alignment: .center)
return TextBlock(
widthTrait: .intrinsic,
text: Int(value).description.with(typo: typo)
)
}
}