import Combine import SwiftUI /// The main two-column editor layout: text editor on the left, results on the right. /// Scrolling is synchronized between both columns. struct TwoColumnEditorView: View { @State private var text: String = "" @State private var scrollOffset: CGFloat = 0 @State private var results: [LineResult] = [] @State private var evaluationTask: Task? /// Uses the Rust FFI engine. Falls back to StubCalculationEngine if the Rust /// library is not linked (e.g., during UI-only development). private let engine: CalculationEngine = RustCalculationEngine() /// Debounce interval for re-evaluation after typing (seconds). private let evaluationDebounce: TimeInterval = 0.05 /// Font that respects the user's accessibility / Dynamic Type settings. private var editorFont: NSFont { // Use the system's preferred monospaced font size, which scales with // Accessibility > Display > Text Size in System Settings (macOS 14+). let baseSize = NSFont.systemFontSize // Scale with accessibility settings via the body text style size let preferredSize = NSFont.preferredFont(forTextStyle: .body, options: [:]).pointSize // Use the larger of system default or accessibility-preferred size let size = max(baseSize, preferredSize) return NSFont.monospacedSystemFont(ofSize: size, weight: .regular) } var body: some View { HSplitView { // Left pane: Editor EditorTextView( text: $text, scrollOffset: $scrollOffset, font: editorFont ) .frame(minWidth: 200) // Divider is automatic with HSplitView // Right pane: Answer column AnswerColumnView( results: results, scrollOffset: $scrollOffset, font: editorFont ) .frame(minWidth: 120, idealWidth: 200) } .onChange(of: text) { _, newValue in scheduleEvaluation(newValue) } .onAppear { evaluateText(text) } } /// Debounce evaluation so rapid typing doesn't cause excessive recalculation. private func scheduleEvaluation(_ newText: String) { evaluationTask?.cancel() evaluationTask = Task { @MainActor in try? await Task.sleep(for: .milliseconds(Int(evaluationDebounce * 1000))) guard !Task.isCancelled else { return } evaluateText(newText) } } private func evaluateText(_ newText: String) { results = engine.evaluateSheet(newText) } }