import Cocoa import Combine import SwiftUI class AppDelegate: NSObject, NSApplicationDelegate { var window: NSWindow! var appState: AppState! private var titleCancellable: AnyCancellable? private var titleBarView: TitleBarView? private var focusTitleObserver: NSObjectProtocol? func applicationDidFinishLaunching(_ notification: Notification) { appState = AppState() let contentView = ContentView(state: appState) let hostingView = NSHostingView(rootView: contentView) window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 1200, height: 800), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false ) window.titlebarAppearsTransparent = true window.titleVisibility = .hidden window.backgroundColor = Theme.current.base window.title = "Swiftly" window.contentView = hostingView window.center() window.setFrameAutosaveName("SwiftlyMainWindow") window.makeKeyAndOrderFront(nil) setupTitleBar() setupMenuBar() observeDocumentTitle() } func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true } func applicationWillTerminate(_ notification: Notification) { appState.saveNote() } // MARK: - Menu bar private func setupMenuBar() { let mainMenu = NSMenu() mainMenu.addItem(buildAppMenu()) mainMenu.addItem(buildFileMenu()) mainMenu.addItem(buildEditMenu()) mainMenu.addItem(buildViewMenu()) mainMenu.addItem(buildWindowMenu()) NSApp.mainMenu = mainMenu } private func buildAppMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu() menu.addItem(withTitle: "About Swiftly", action: #selector(NSApplication.orderFrontStandardAboutPanel(_:)), keyEquivalent: "") menu.addItem(.separator()) menu.addItem(withTitle: "Quit Swiftly", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") item.submenu = menu return item } private func buildFileMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu(title: "File") let newItem = NSMenuItem(title: "New Note", action: #selector(newNote), keyEquivalent: "n") newItem.target = self menu.addItem(newItem) let openItem = NSMenuItem(title: "Open...", action: #selector(openNote), keyEquivalent: "o") openItem.target = self menu.addItem(openItem) menu.addItem(.separator()) let saveItem = NSMenuItem(title: "Save", action: #selector(saveNote), keyEquivalent: "s") saveItem.target = self menu.addItem(saveItem) let saveAsItem = NSMenuItem(title: "Save As...", action: #selector(saveNoteAs), keyEquivalent: "S") saveAsItem.target = self menu.addItem(saveAsItem) item.submenu = menu return item } private func buildEditMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu(title: "Edit") menu.addItem(withTitle: "Undo", action: Selector(("undo:")), keyEquivalent: "z") menu.addItem(withTitle: "Redo", action: Selector(("redo:")), keyEquivalent: "Z") menu.addItem(.separator()) menu.addItem(withTitle: "Cut", action: #selector(NSText.cut(_:)), keyEquivalent: "x") menu.addItem(withTitle: "Copy", action: #selector(NSText.copy(_:)), keyEquivalent: "c") menu.addItem(withTitle: "Paste", action: #selector(NSText.paste(_:)), keyEquivalent: "v") menu.addItem(withTitle: "Select All", action: #selector(NSText.selectAll(_:)), keyEquivalent: "a") menu.addItem(.separator()) let findItem = NSMenuItem(title: "Find...", action: #selector(NSTextView.performFindPanelAction(_:)), keyEquivalent: "f") findItem.tag = Int(NSTextFinder.Action.showFindInterface.rawValue) menu.addItem(findItem) item.submenu = menu return item } private func buildViewMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu(title: "View") let toggleItem = NSMenuItem(title: "Toggle Sidebar", action: #selector(toggleSidebar), keyEquivalent: "b") toggleItem.keyEquivalentModifierMask = .control toggleItem.target = self menu.addItem(toggleItem) item.submenu = menu return item } private func buildWindowMenu() -> NSMenuItem { let item = NSMenuItem() let menu = NSMenu(title: "Window") menu.addItem(withTitle: "Minimize", action: #selector(NSWindow.miniaturize(_:)), keyEquivalent: "m") menu.addItem(withTitle: "Zoom", action: #selector(NSWindow.zoom(_:)), keyEquivalent: "") item.submenu = menu NSApp.windowsMenu = menu return item } // MARK: - Actions @objc private func newNote() { appState.newNote() } @objc private func openNote() { let panel = NSOpenPanel() panel.allowedContentTypes = [.plainText] panel.canChooseFiles = true panel.canChooseDirectories = false panel.allowsMultipleSelection = false panel.beginSheetModal(for: window) { [weak self] response in guard response == .OK, let url = panel.url else { return } self?.appState.loadNoteFromFile(url) } } @objc private func saveNote() { appState.saveNote() } @objc private func saveNoteAs() { let panel = NSSavePanel() panel.allowedContentTypes = [.plainText] panel.nameFieldStringValue = "note.txt" panel.beginSheetModal(for: window) { [weak self] response in guard response == .OK, let url = panel.url else { return } self?.appState.saveNoteToFile(url) } } @objc private func toggleSidebar() { NotificationCenter.default.post(name: .toggleSidebar, object: nil) } private func setupTitleBar() { let accessory = TitleBarAccessoryController() window.addTitlebarAccessoryViewController(accessory) let tbv = accessory.titleView tbv.onCommit = { [weak self] rawTitle in guard let self = self else { return } let lines = self.appState.documentText.components(separatedBy: "\n") var rest = Array(lines.dropFirst()) if rest.isEmpty && self.appState.documentText.isEmpty { rest = [] } self.appState.documentText = ([rawTitle] + rest).joined(separator: "\n") } titleBarView = tbv focusTitleObserver = NotificationCenter.default.addObserver( forName: .focusTitle, object: nil, queue: .main ) { [weak self] _ in self?.titleBarView?.beginEditing() } } private func observeDocumentTitle() { titleCancellable = appState.$documentText .receive(on: RunLoop.main) .sink { [weak self] text in guard let self = self else { return } let firstLine = text.components(separatedBy: "\n").first? .trimmingCharacters(in: .whitespaces) ?? "" let clean = firstLine.replacingOccurrences( of: "^#+\\s*", with: "", options: .regularExpression ) let displayTitle = clean.isEmpty ? "Swiftly" : String(clean.prefix(60)) self.window.title = displayTitle self.titleBarView?.title = firstLine } } }