Files
Outpost/Vendor/swift-markdown-engine/Sources/MarkdownEngine/TextView/BottomOverscrollPolicy.swift
T
Puranjay Savar Mattas 6e1ffb96fd chore: vendor swift-markdown-engine as plain tracked source
Convert from git submodule to plain vendored copy. No push access to
upstream nodes-app/swift-markdown-engine meant our local fix commit
(05c1720) was stranded and unreachable from any remote on fresh
clones/CI. Vendoring as plain files folds it into normal repo history
instead.
2026-08-20 13:26:13 +01:00

72 lines
2.8 KiB
Swift

//
// BottomOverscrollPolicy.swift
// MarkdownEngine
//
// Created by Luca Chen on 16.03.26.
//
// Computes the comfortable bottom-of-document slack so the caret never
// hugs the bottom edge of the visible viewport while typing.
//
import AppKit
import Foundation
struct BottomOverscrollPolicy {
let overscrollPercent: CGFloat
let minOverscrollPoints: CGFloat
let maxOverscrollPoints: CGFloat
let activationStartFraction: CGFloat
let activationRangeFraction: CGFloat
init(configuration: OverscrollPolicy) {
self.overscrollPercent = configuration.percent
self.minOverscrollPoints = configuration.minPoints
self.maxOverscrollPoints = configuration.maxPoints
self.activationStartFraction = configuration.activationStartFraction
self.activationRangeFraction = configuration.activationRangeFraction
}
init(
overscrollPercent: CGFloat,
minOverscrollPoints: CGFloat,
maxOverscrollPoints: CGFloat,
activationStartFraction: CGFloat = OverscrollPolicy.default.activationStartFraction,
activationRangeFraction: CGFloat = OverscrollPolicy.default.activationRangeFraction
) {
self.overscrollPercent = overscrollPercent
self.minOverscrollPoints = minOverscrollPoints
self.maxOverscrollPoints = maxOverscrollPoints
self.activationStartFraction = activationStartFraction
self.activationRangeFraction = activationRangeFraction
}
func activeOverscroll(
baseContentHeight: CGFloat,
headerHeight: CGFloat = 0,
visibleHeight: CGFloat,
lineHeight: CGFloat
) -> CGFloat {
// The header band scrolls with the content, so it counts toward activation
// and unlock; the comfortable slack stays viewport-based (the band is gone
// once the user reaches the document bottom).
let stackedContentHeight = baseContentHeight + headerHeight
let activationStartHeight = visibleHeight * activationStartFraction
let activationRange = max(visibleHeight * activationRangeFraction, 1)
let activationProgress = min(
max((stackedContentHeight - activationStartHeight) / activationRange, 0),
1
)
guard activationProgress > 0 else { return 0 }
var desiredSlack = visibleHeight * overscrollPercent
desiredSlack = min(desiredSlack, maxOverscrollPoints)
desiredSlack = max(desiredSlack, minOverscrollPoints)
desiredSlack = max(0, floor(desiredSlack - lineHeight))
// Start unlocking downward scroll before the stacked content fully fills
// the viewport, then blend into the final comfortable bottom slack.
let scrollUnlockDistance = max(visibleHeight - stackedContentHeight, 0)
return floor((scrollUnlockDistance + desiredSlack) * activationProgress)
}
}