mirror of
https://github.com/qvacua/vimr.git
synced 2024-12-01 10:02:36 +03:00
331 lines
12 KiB
Swift
331 lines
12 KiB
Swift
/**
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
* See LICENSE
|
|
*/
|
|
|
|
import Cocoa
|
|
import PureLayout
|
|
import RxSwift
|
|
|
|
struct AppearancePrefData: Equatable, StandardPrefData {
|
|
|
|
fileprivate static let editorFontName = "editor-font-name"
|
|
fileprivate static let editorFontSize = "editor-font-size"
|
|
fileprivate static let editorLinespacing = "editor-linespacing"
|
|
fileprivate static let editorUsesLigatures = "editor-uses-ligatures"
|
|
|
|
static func ==(left: AppearancePrefData, right: AppearancePrefData) -> Bool {
|
|
return left.editorUsesLigatures == right.editorUsesLigatures
|
|
&& left.editorFont.isEqual(to: right.editorFont)
|
|
&& left.editorLinespacing == right.editorLinespacing
|
|
}
|
|
|
|
static let `default` = AppearancePrefData(editorFont: NeoVimView.defaultFont,
|
|
editorLinespacing: NeoVimView.defaultLinespacing,
|
|
editorUsesLigatures: false)
|
|
|
|
var editorFont: NSFont
|
|
var editorLinespacing: CGFloat
|
|
var editorUsesLigatures: Bool
|
|
|
|
init(editorFont: NSFont, editorLinespacing: CGFloat, editorUsesLigatures: Bool) {
|
|
self.editorFont = editorFont
|
|
self.editorLinespacing = editorLinespacing
|
|
self.editorUsesLigatures = editorUsesLigatures
|
|
}
|
|
|
|
init?(dict: [String: Any]) {
|
|
guard let editorFontName = dict[AppearancePrefData.editorFontName] as? String,
|
|
let fEditorFontSize = PrefUtils.float(from: dict, for: AppearancePrefData.editorFontSize),
|
|
let fEditorLinespacing = PrefUtils.float(from: dict, for: AppearancePrefData.editorLinespacing),
|
|
let editorUsesLigatures = PrefUtils.bool(from: dict, for: AppearancePrefData.editorUsesLigatures)
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
self.init(editorFont: PrefUtils.saneFont(editorFontName, fontSize: CGFloat(fEditorFontSize)),
|
|
editorLinespacing: CGFloat(fEditorLinespacing),
|
|
editorUsesLigatures: editorUsesLigatures)
|
|
}
|
|
|
|
func dict() -> [String: Any] {
|
|
return [
|
|
AppearancePrefData.editorFontName: self.editorFont.fontName,
|
|
AppearancePrefData.editorFontSize: Float(self.editorFont.pointSize),
|
|
AppearancePrefData.editorLinespacing: Float(self.editorLinespacing),
|
|
AppearancePrefData.editorUsesLigatures: self.editorUsesLigatures,
|
|
]
|
|
}
|
|
}
|
|
|
|
class AppearancePrefPane: PrefPane, NSComboBoxDelegate, NSControlTextEditingDelegate {
|
|
|
|
override var displayName: String {
|
|
return "Appearance"
|
|
}
|
|
|
|
override var pinToContainer: Bool {
|
|
return true
|
|
}
|
|
|
|
fileprivate var data: AppearancePrefData {
|
|
didSet {
|
|
self.updateViews(newData: self.data)
|
|
}
|
|
}
|
|
|
|
fileprivate let fontManager = NSFontManager.shared()
|
|
|
|
fileprivate let sizes = [9, 10, 11, 12, 13, 14, 16, 18, 24, 36, 48, 64]
|
|
fileprivate let sizeCombo = NSComboBox(forAutoLayout: ())
|
|
fileprivate let fontPopup = NSPopUpButton(frame: CGRect.zero, pullsDown: false)
|
|
fileprivate let linespacingField = NSTextField(forAutoLayout: ())
|
|
fileprivate let ligatureCheckbox = NSButton(forAutoLayout: ())
|
|
fileprivate let previewArea = NSTextView(frame: CGRect.zero)
|
|
|
|
fileprivate let exampleText =
|
|
"abcdefghijklmnopqrstuvwxyz\n" +
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" +
|
|
"0123456789\n" +
|
|
"(){}[] +-*/= .,;:!?#&$%@|^\n" +
|
|
"<- -> => >> << >>= =<< .. \n" +
|
|
":: -< >- -<< >>- ++ /= =="
|
|
|
|
init(source: Observable<Any>, initialData: AppearancePrefData) {
|
|
self.data = initialData
|
|
super.init(source: source)
|
|
|
|
self.updateViews(newData: initialData)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
fileprivate func set(data: AppearancePrefData) {
|
|
self.data = data
|
|
self.publish(event: data)
|
|
}
|
|
|
|
override func subscription(source: Observable<Any>) -> Disposable {
|
|
return source
|
|
.filter { $0 is PrefData }
|
|
.map { ($0 as! PrefData).appearance }
|
|
.filter { [unowned self] data in data != self.data }
|
|
.subscribe(onNext: { [unowned self] data in
|
|
self.data = data
|
|
})
|
|
}
|
|
|
|
override func addViews() {
|
|
let paneTitle = self.paneTitleTextField(title: "Appearance")
|
|
|
|
let fontTitle = self.titleTextField(title: "Default Font:")
|
|
|
|
let fontPopup = self.fontPopup
|
|
fontPopup.translatesAutoresizingMaskIntoConstraints = false
|
|
fontPopup.target = self
|
|
fontPopup.action = #selector(AppearancePrefPane.fontPopupAction)
|
|
fontPopup.addItems(withTitles: self.fontManager.availableFontNames(with: .fixedPitchFontMask)!)
|
|
|
|
let sizeCombo = self.sizeCombo
|
|
sizeCombo.delegate = self
|
|
sizeCombo.target = self
|
|
sizeCombo.action = #selector(AppearancePrefPane.sizeComboBoxDidEnter(_:))
|
|
self.sizes.forEach { string in
|
|
sizeCombo.addItem(withObjectValue: string)
|
|
}
|
|
|
|
let linespacingTitle = self.titleTextField(title: "Line Spacing:")
|
|
let linespacingField = self.linespacingField
|
|
|
|
let ligatureCheckbox = self.ligatureCheckbox
|
|
self.configureCheckbox(button: ligatureCheckbox,
|
|
title: "Use Ligatures",
|
|
action: #selector(AppearancePrefPane.usesLigaturesAction(_:)))
|
|
|
|
let previewArea = self.previewArea
|
|
previewArea.isEditable = true
|
|
previewArea.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
|
previewArea.isVerticallyResizable = true
|
|
previewArea.isHorizontallyResizable = true
|
|
previewArea.textContainer?.heightTracksTextView = false
|
|
previewArea.textContainer?.widthTracksTextView = false
|
|
previewArea.autoresizingMask = [ .viewWidthSizable, .viewHeightSizable]
|
|
previewArea.textContainer?.containerSize = CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
|
previewArea.layoutManager?.replaceTextStorage(NSTextStorage(string: self.exampleText))
|
|
previewArea.isRichText = false
|
|
previewArea.turnOffLigatures(self)
|
|
|
|
let previewScrollView = NSScrollView(forAutoLayout: ())
|
|
previewScrollView.hasVerticalScroller = true
|
|
previewScrollView.hasHorizontalScroller = true
|
|
previewScrollView.autohidesScrollers = true
|
|
previewScrollView.borderType = .bezelBorder
|
|
previewScrollView.documentView = previewArea
|
|
|
|
self.addSubview(paneTitle)
|
|
|
|
self.addSubview(fontTitle)
|
|
self.addSubview(fontPopup)
|
|
self.addSubview(sizeCombo)
|
|
self.addSubview(linespacingTitle)
|
|
self.addSubview(linespacingField)
|
|
self.addSubview(ligatureCheckbox)
|
|
self.addSubview(previewScrollView)
|
|
|
|
paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18)
|
|
paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
|
|
|
fontTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
|
fontTitle.autoAlignAxis(.baseline, toSameAxisOf: fontPopup)
|
|
|
|
fontPopup.autoPinEdge(.top, to: .bottom, of: paneTitle, withOffset: 18)
|
|
fontPopup.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
|
|
fontPopup.autoSetDimension(.width, toSize: 240)
|
|
|
|
sizeCombo.autoSetDimension(.width, toSize: 60)
|
|
// If we use .Baseline the combo box is placed one pixel off...
|
|
sizeCombo.autoAlignAxis(.horizontal, toSameAxisOf: fontPopup)
|
|
sizeCombo.autoPinEdge(.left, to: .right, of: fontPopup, withOffset: 5)
|
|
|
|
linespacingTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
|
|
linespacingTitle.autoPinEdge(.right, to: .right, of: fontTitle)
|
|
linespacingTitle.autoAlignAxis(.baseline, toSameAxisOf: linespacingField)
|
|
|
|
linespacingField.autoPinEdge(.top, to: .bottom, of: sizeCombo, withOffset: 18)
|
|
linespacingField.autoPinEdge(.left, to: .right, of: linespacingTitle, withOffset: 5)
|
|
linespacingField.autoSetDimension(.width, toSize: 60)
|
|
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSControlTextDidEndEditing,
|
|
object: linespacingField,
|
|
queue: nil)
|
|
{ [unowned self] _ in
|
|
self.linespacingAction()
|
|
}
|
|
|
|
ligatureCheckbox.autoPinEdge(.top, to: .bottom, of: linespacingField, withOffset: 18)
|
|
ligatureCheckbox.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
|
|
|
|
previewScrollView.autoSetDimension(.height, toSize: 200, relation: .greaterThanOrEqual)
|
|
previewScrollView.autoPinEdge(.top, to: .bottom, of: ligatureCheckbox, withOffset: 18)
|
|
previewScrollView.autoPinEdge(toSuperviewEdge: .right, withInset: 18)
|
|
previewScrollView.autoPinEdge(toSuperviewEdge: .bottom, withInset: 18)
|
|
previewScrollView.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
|
|
|
|
self.fontPopup.selectItem(withTitle: self.data.editorFont.fontName)
|
|
self.sizeCombo.stringValue = String(Int(self.data.editorFont.pointSize))
|
|
self.ligatureCheckbox.state = self.data.editorUsesLigatures ? NSOnState : NSOffState
|
|
self.previewArea.font = self.data.editorFont
|
|
if self.data.editorUsesLigatures {
|
|
self.previewArea.useAllLigatures(self)
|
|
} else {
|
|
self.previewArea.turnOffLigatures(self)
|
|
}
|
|
}
|
|
|
|
fileprivate func updateViews(newData: AppearancePrefData) {
|
|
let newFont = newData.editorFont
|
|
|
|
self.fontPopup.selectItem(withTitle: newFont.fontName)
|
|
self.sizeCombo.stringValue = String(Int(newFont.pointSize))
|
|
self.linespacingField.stringValue = String(format: "%.2f", newData.editorLinespacing)
|
|
self.ligatureCheckbox.boolState = newData.editorUsesLigatures
|
|
self.previewArea.font = newData.editorFont
|
|
|
|
if newData.editorUsesLigatures {
|
|
self.previewArea.useAllLigatures(self)
|
|
} else {
|
|
self.previewArea.turnOffLigatures(self)
|
|
}
|
|
}
|
|
|
|
override func windowWillClose() {
|
|
self.linespacingAction()
|
|
}
|
|
}
|
|
|
|
// MARK: - Actions
|
|
extension AppearancePrefPane {
|
|
|
|
func usesLigaturesAction(_ sender: NSButton) {
|
|
self.set(data: AppearancePrefData(editorFont: self.data.editorFont,
|
|
editorLinespacing: self.data.editorLinespacing,
|
|
editorUsesLigatures: sender.boolState))
|
|
}
|
|
|
|
func fontPopupAction(_ sender: NSPopUpButton) {
|
|
guard let selectedItem = self.fontPopup.selectedItem else {
|
|
return
|
|
}
|
|
|
|
guard selectedItem.title != self.data.editorFont.fontName else {
|
|
return
|
|
}
|
|
|
|
guard let newFont = NSFont(name: selectedItem.title, size: self.data.editorFont.pointSize) else {
|
|
return
|
|
}
|
|
|
|
self.set(data: AppearancePrefData(editorFont: newFont,
|
|
editorLinespacing: self.data.editorLinespacing,
|
|
editorUsesLigatures: self.data.editorUsesLigatures))
|
|
}
|
|
|
|
func comboBoxSelectionDidChange(_ notification: Notification) {
|
|
guard (notification.object as! NSComboBox) === self.sizeCombo else {
|
|
return
|
|
}
|
|
|
|
let newFontSize = self.cappedFontSize(Int(self.sizes[self.sizeCombo.indexOfSelectedItem]))
|
|
let newFont = self.fontManager.convert(self.data.editorFont, toSize: newFontSize)
|
|
|
|
self.set(data: AppearancePrefData(editorFont: newFont,
|
|
editorLinespacing: self.data.editorLinespacing,
|
|
editorUsesLigatures: self.data.editorUsesLigatures))
|
|
}
|
|
|
|
func sizeComboBoxDidEnter(_ sender: AnyObject!) {
|
|
let newFontSize = self.cappedFontSize(self.sizeCombo.integerValue)
|
|
let newFont = self.fontManager.convert(self.data.editorFont, toSize: newFontSize)
|
|
|
|
self.set(data: AppearancePrefData(editorFont: newFont,
|
|
editorLinespacing: self.data.editorLinespacing,
|
|
editorUsesLigatures: self.data.editorUsesLigatures))
|
|
}
|
|
|
|
func linespacingAction() {
|
|
let newLinespacing = self.cappedLinespacing(self.linespacingField.floatValue)
|
|
self.set(data: AppearancePrefData(editorFont: self.data.editorFont,
|
|
editorLinespacing: newLinespacing,
|
|
editorUsesLigatures: self.data.editorUsesLigatures))
|
|
}
|
|
|
|
fileprivate func cappedLinespacing(_ linespacing: Float) -> CGFloat {
|
|
let cgfLinespacing = CGFloat(linespacing)
|
|
|
|
guard cgfLinespacing >= NeoVimView.minLinespacing else {
|
|
return NeoVimView.defaultLinespacing
|
|
}
|
|
|
|
guard cgfLinespacing <= NeoVimView.maxLinespacing else {
|
|
return NeoVimView.maxLinespacing
|
|
}
|
|
|
|
return cgfLinespacing
|
|
}
|
|
|
|
fileprivate func cappedFontSize(_ size: Int) -> CGFloat {
|
|
let cgfSize = CGFloat(size)
|
|
|
|
guard cgfSize >= NeoVimView.minFontSize else {
|
|
return NeoVimView.defaultFont.pointSize
|
|
}
|
|
|
|
guard cgfSize <= NeoVimView.maxFontSize else {
|
|
return NeoVimView.maxFontSize
|
|
}
|
|
|
|
return cgfSize
|
|
}
|
|
}
|