Stop the macOS transcript jumping while scrolling
This commit is contained in:
@@ -495,6 +495,7 @@ private struct TranscriptScroll: View {
|
||||
@Environment(Store.self) private var store
|
||||
@Environment(\.paneAccent) private var accent
|
||||
@ViewState private var position = ScrollPosition(edge: .bottom)
|
||||
@ViewState private var scrolling = false
|
||||
|
||||
var body: some View {
|
||||
let running = model.running
|
||||
@@ -536,13 +537,16 @@ private struct TranscriptScroll: View {
|
||||
.onScrollGeometryChange(for: ScrollMetrics.self) { g in
|
||||
ScrollMetrics(content: g.contentSize.height, visible: g.visibleRect.height, distance: g.contentSize.height - g.visibleRect.maxY)
|
||||
} action: { old, new in
|
||||
if new.content != old.content || new.visible != old.visible {
|
||||
if model.stuck && new.distance > 1 {
|
||||
position.scrollTo(edge: .bottom)
|
||||
}
|
||||
if scrolling {
|
||||
model.stuck = new.distance < 80
|
||||
return
|
||||
}
|
||||
model.stuck = new.distance < 80
|
||||
if model.stuck && new.distance > 1 && (new.content != old.content || new.visible != old.visible) {
|
||||
position.scrollTo(edge: .bottom)
|
||||
}
|
||||
}
|
||||
.onScrollPhaseChange { _, phase in
|
||||
scrolling = phase.isScrolling
|
||||
}
|
||||
.onChange(of: model.builder.pending.count) {
|
||||
model.stuck = true
|
||||
|
||||
@@ -425,16 +425,7 @@ private struct FlowText: NSViewRepresentable {
|
||||
}
|
||||
|
||||
func sizeThatFits(_ proposal: ProposedViewSize, nsView v: FlowTextView, context: Context) -> CGSize? {
|
||||
let width = proposal.width.flatMap { $0.isFinite ? $0 : nil }
|
||||
let storage = NSTextStorage(attributedString: text)
|
||||
let layout = NSLayoutManager()
|
||||
let container = NSTextContainer(size: CGSize(width: width ?? .greatestFiniteMagnitude, height: .greatestFiniteMagnitude))
|
||||
container.lineFragmentPadding = 0
|
||||
layout.addTextContainer(container)
|
||||
storage.addLayoutManager(layout)
|
||||
layout.ensureLayout(for: container)
|
||||
let used = layout.usedRect(for: container)
|
||||
return CGSize(width: width ?? ceil(used.width), height: ceil(used.height))
|
||||
context.coordinator.size(text, proposal.width.flatMap { $0.isFinite ? $0 : nil })
|
||||
}
|
||||
|
||||
func makeCoordinator() -> Coordinator {
|
||||
@@ -443,6 +434,33 @@ private struct FlowText: NSViewRepresentable {
|
||||
|
||||
final class Coordinator: NSObject, NSTextViewDelegate {
|
||||
var openURL: OpenURLAction?
|
||||
private let storage = NSTextStorage()
|
||||
private let layout = NSLayoutManager()
|
||||
private let container = NSTextContainer()
|
||||
private var measured: (text: NSAttributedString, width: CGFloat?, size: CGSize)?
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
container.lineFragmentPadding = 0
|
||||
layout.addTextContainer(container)
|
||||
storage.addLayoutManager(layout)
|
||||
}
|
||||
|
||||
func size(_ text: NSAttributedString, _ width: CGFloat?) -> CGSize {
|
||||
let same = measured.map { $0.text === text || $0.text.isEqual(to: text) } ?? false
|
||||
if same, let m = measured, m.width == width {
|
||||
return m.size
|
||||
}
|
||||
if !same {
|
||||
storage.setAttributedString(text)
|
||||
}
|
||||
container.size = CGSize(width: width ?? .greatestFiniteMagnitude, height: .greatestFiniteMagnitude)
|
||||
layout.ensureLayout(for: container)
|
||||
let used = layout.usedRect(for: container)
|
||||
let size = CGSize(width: width ?? ceil(used.width), height: ceil(used.height))
|
||||
measured = (text, width, size)
|
||||
return size
|
||||
}
|
||||
|
||||
func textView(_ textView: NSTextView, clickedOnLink link: Any, at charIndex: Int) -> Bool {
|
||||
guard let url = link as? URL, let openURL else {
|
||||
|
||||
Reference in New Issue
Block a user