import AppKit 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. /// Includes a toolbar for justification toggles and alternating row striping. struct TwoColumnEditorView: View { @State private var text: String = "# CalcPad\n\n// Basic arithmetic\n2 + 3\n10 * 4.5\n100 / 7\n\n// Variables\nprice = 49.99\nquantity = 3\nsubtotal = price * quantity\n\n// Percentages\ntax = subtotal * 8%\ntotal = subtotal + tax\n\n// Functions\nsqrt(144)\n2 ^ 10\n" @State private var scrollOffset: CGFloat = 0 @State private var results: [LineResult] = [] @State private var evaluationTask: Task? @State private var editorAlignment: NSTextAlignment = .left @State private var resultsAlignment: NSTextAlignment = .right /// 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 { let baseSize = NSFont.systemFontSize let preferredSize = NSFont.preferredFont(forTextStyle: .body, options: [:]).pointSize let size = max(baseSize, preferredSize) return NSFont.monospacedSystemFont(ofSize: size, weight: .regular) } var body: some View { VStack(spacing: 0) { // Justification toolbar HStack(spacing: 0) { // Editor alignment buttons HStack(spacing: 4) { Button(action: { editorAlignment = .left }) { Image(systemName: "text.alignleft") } .buttonStyle(.borderless) .foregroundColor(editorAlignment == .left ? .accentColor : .secondary) .help("Align editor text left") Button(action: { editorAlignment = .center }) { Image(systemName: "text.aligncenter") } .buttonStyle(.borderless) .foregroundColor(editorAlignment == .center ? .accentColor : .secondary) .help("Align editor text center") Button(action: { editorAlignment = .right }) { Image(systemName: "text.alignright") } .buttonStyle(.borderless) .foregroundColor(editorAlignment == .right ? .accentColor : .secondary) .help("Align editor text right") } .padding(.horizontal, 8) Spacer() // Results alignment buttons HStack(spacing: 4) { Button(action: { resultsAlignment = .left }) { Image(systemName: "text.alignleft") } .buttonStyle(.borderless) .foregroundColor(resultsAlignment == .left ? .accentColor : .secondary) .help("Align results left") Button(action: { resultsAlignment = .center }) { Image(systemName: "text.aligncenter") } .buttonStyle(.borderless) .foregroundColor(resultsAlignment == .center ? .accentColor : .secondary) .help("Align results center") Button(action: { resultsAlignment = .right }) { Image(systemName: "text.alignright") } .buttonStyle(.borderless) .foregroundColor(resultsAlignment == .right ? .accentColor : .secondary) .help("Align results right") } .padding(.horizontal, 8) } .padding(.vertical, 4) Divider() HSplitView { // Left pane: Editor EditorTextView( text: $text, scrollOffset: $scrollOffset, font: editorFont, alignment: editorAlignment ) .frame(minWidth: 200) // Right pane: Answer column AnswerColumnView( results: results, scrollOffset: $scrollOffset, font: editorFont, alignment: resultsAlignment ) .frame(minWidth: 120, idealWidth: 200) } } .onChange(of: text) { _, newValue in scheduleEvaluation(newValue) } .onAppear { evaluateText(text) } } 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) } }