         let timingFunction: CAMediaTimingFunction
     }
 }
+
+// MARK: - Helper
+
+extension Comparable {
+
+    /// Clamps the current value to the boundaries of the given `limits`.
+    ///
+    /// - Source: https://stackoverflow.com/a/40868784
+    func clamped(to limits: ClosedRange<Self>) -> Self {
+        min(max(self, limits.lowerBound), limits.upperBound)
+    }
+}

    var bounds: CGRect = .zero {
        didSet {
            // Update mask-layer frame accordingly.
            maskLayerFrameAnimationSubject.value = maskLayerFrameAnimationForCurrentProgress(animated: false)
        }
    }

    /// The current progress.
    var progress: Float = 0.5 {
        didSet {
            // Make sure progress value fits our bounds.
            progress = min(1, max(0, progress))

            if shouldUpdateMaskLayerFrameOnProgressChange {
                maskLayerFrameAnimationSubject.value = maskLayerFrameAnimationForCurrentProgress(animated: false)
            }
        }
    }



    // MARK: - Private properties

    /// The actual storage for our progress.
    ///
    /// We store the value on a separate properties for two reasons:
    /// - This makes sure the progress stays between zero and one.
    /// - The setter of `progress` can set a progress and always update the frame of the layer without an animation.
    ///   The method `setProgress(_:animated:)` can set a progress and afterwards update the frame with a possible animation.
    private var _progress: Float = 0.5

    private let maskLayerFrameAnimationSubject: CurrentValueSubject<FrameAnimation, Never> = CurrentValueSubject(.zero)

    // MARK: - Public methods

    /// Adjusts the current progress, optionally animating the change.
    func setProgress(_ progress: Float, animated: Bool = false) {
        _progress = progress.clamped(to: 0 ... 1)
        maskLayerFrameAnimationSubject.value = makeMaskLayerFrameAnimation(animated: animated)
    }

    // MARK: - Private methods

    private func makeMaskLayerFrameAnimation(animated: Bool) -> FrameAnimation {
        var maskLayerFrame = bounds
        maskLayerFrame.size.width *= CGFloat(progress)

