165 lines
5.3 KiB
Swift
165 lines
5.3 KiB
Swift
import Cocoa
|
|
|
|
class TitleBarAccessoryController: NSTitlebarAccessoryViewController {
|
|
let titleView = TitleBarView()
|
|
|
|
override func loadView() {
|
|
let container = NSView(frame: NSRect(x: 0, y: 0, width: 400, height: 28))
|
|
titleView.translatesAutoresizingMaskIntoConstraints = false
|
|
container.addSubview(titleView)
|
|
NSLayoutConstraint.activate([
|
|
titleView.leadingAnchor.constraint(equalTo: container.leadingAnchor),
|
|
titleView.trailingAnchor.constraint(equalTo: container.trailingAnchor),
|
|
titleView.topAnchor.constraint(equalTo: container.topAnchor),
|
|
titleView.bottomAnchor.constraint(equalTo: container.bottomAnchor),
|
|
])
|
|
self.view = container
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
layoutAttribute = .bottom
|
|
fullScreenMinHeight = 28
|
|
}
|
|
}
|
|
|
|
class TitleBarView: NSView {
|
|
private let label = NSTextField(labelWithString: "")
|
|
private let editor = NSTextField()
|
|
private(set) var isEditing = false
|
|
|
|
var title: String = "" {
|
|
didSet {
|
|
if !isEditing {
|
|
let dt = displayTitle
|
|
label.stringValue = dt.isEmpty ? "Untitled" : dt
|
|
label.textColor = dt.isEmpty ? Theme.current.overlay0 : Theme.current.text
|
|
}
|
|
}
|
|
}
|
|
|
|
var onCommit: ((String) -> Void)?
|
|
|
|
private var displayTitle: String {
|
|
let trimmed = title.trimmingCharacters(in: .whitespaces)
|
|
if trimmed.hasPrefix("# ") {
|
|
return String(trimmed.dropFirst(2))
|
|
}
|
|
return trimmed
|
|
}
|
|
|
|
override init(frame: NSRect) {
|
|
super.init(frame: frame)
|
|
setup()
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
super.init(coder: coder)
|
|
setup()
|
|
}
|
|
|
|
private func setup() {
|
|
label.font = .systemFont(ofSize: 13, weight: .semibold)
|
|
label.textColor = Theme.current.overlay0
|
|
label.backgroundColor = .clear
|
|
label.isBezeled = false
|
|
label.isEditable = false
|
|
label.isSelectable = false
|
|
label.alignment = .center
|
|
label.lineBreakMode = .byTruncatingTail
|
|
label.cell?.truncatesLastVisibleLine = true
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
label.stringValue = "Untitled"
|
|
|
|
editor.font = .systemFont(ofSize: 13, weight: .semibold)
|
|
editor.textColor = Theme.current.text
|
|
editor.backgroundColor = Theme.current.surface0
|
|
editor.isBezeled = false
|
|
editor.isEditable = true
|
|
editor.isSelectable = true
|
|
editor.alignment = .center
|
|
editor.focusRingType = .none
|
|
editor.cell?.lineBreakMode = .byTruncatingTail
|
|
editor.translatesAutoresizingMaskIntoConstraints = false
|
|
editor.isHidden = true
|
|
editor.delegate = self
|
|
|
|
addSubview(label)
|
|
addSubview(editor)
|
|
|
|
NSLayoutConstraint.activate([
|
|
label.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
label.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
label.widthAnchor.constraint(lessThanOrEqualTo: widthAnchor, multiplier: 0.5),
|
|
|
|
editor.centerXAnchor.constraint(equalTo: centerXAnchor),
|
|
editor.centerYAnchor.constraint(equalTo: centerYAnchor),
|
|
editor.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.5),
|
|
])
|
|
|
|
let dblClick = NSClickGestureRecognizer(target: self, action: #selector(handleDoubleClick(_:)))
|
|
dblClick.numberOfClicksRequired = 2
|
|
dblClick.delaysPrimaryMouseButtonEvents = false
|
|
addGestureRecognizer(dblClick)
|
|
}
|
|
|
|
@objc private func handleDoubleClick(_ sender: NSClickGestureRecognizer) {
|
|
if !isEditing { beginEditing() }
|
|
}
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
if event.clickCount == 2 && !isEditing {
|
|
beginEditing()
|
|
return
|
|
}
|
|
super.mouseDown(with: event)
|
|
}
|
|
|
|
func beginEditing() {
|
|
isEditing = true
|
|
editor.stringValue = title
|
|
label.isHidden = true
|
|
editor.isHidden = false
|
|
window?.makeFirstResponder(editor)
|
|
editor.currentEditor()?.selectAll(nil)
|
|
}
|
|
|
|
func endEditing() {
|
|
guard isEditing else { return }
|
|
isEditing = false
|
|
let raw = editor.stringValue
|
|
onCommit?(raw)
|
|
let dt = displayTitle
|
|
label.stringValue = dt.isEmpty ? "Untitled" : dt
|
|
label.textColor = dt.isEmpty ? Theme.current.overlay0 : Theme.current.text
|
|
editor.isHidden = true
|
|
label.isHidden = false
|
|
}
|
|
|
|
func updateColors() {
|
|
let dt = displayTitle
|
|
label.textColor = dt.isEmpty ? Theme.current.overlay0 : Theme.current.text
|
|
editor.textColor = Theme.current.text
|
|
editor.backgroundColor = Theme.current.surface0
|
|
}
|
|
}
|
|
|
|
extension TitleBarView: NSTextFieldDelegate {
|
|
func controlTextDidEndEditing(_ obj: Notification) {
|
|
endEditing()
|
|
}
|
|
|
|
func control(_ control: NSControl, textView: NSTextView, doCommandBy sel: Selector) -> Bool {
|
|
if sel == #selector(NSResponder.insertNewline(_:)) {
|
|
endEditing()
|
|
NotificationCenter.default.post(name: .focusEditor, object: nil)
|
|
return true
|
|
}
|
|
if sel == #selector(NSResponder.cancelOperation(_:)) {
|
|
endEditing()
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
}
|