1
1
mirror of https://github.com/qvacua/vimr.git synced 2024-11-28 02:54:31 +03:00
vimr/VimR/AppearancePref.swift

279 lines
9.4 KiB
Swift
Raw Normal View History

/**
* Tae Won Ha - http://taewon.de - @hataewon
* See LICENSE
*/
import Cocoa
import PureLayout
2016-07-21 20:28:58 +03:00
import RxSwift
2017-02-28 11:53:27 +03:00
class AppearancePref: PrefPane, NSComboBoxDelegate, NSControlTextEditingDelegate {
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
typealias StateType = AppState
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
enum Action {
case setUsesLigatures(Bool)
case setFont(NSFont)
case setLinespacing(CGFloat)
2016-11-19 15:29:18 +03:00
}
2016-07-21 20:28:58 +03:00
2017-02-28 11:53:27 +03:00
override var displayName: String {
return "Appearance"
}
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
override var pinToContainer: Bool {
return true
}
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
override func windowWillClose() {
self.linespacingAction()
2016-11-19 15:29:18 +03:00
}
2017-02-28 11:53:27 +03:00
required init(source: Observable<StateType>, emitter: ActionEmitter, state: StateType) {
2017-04-26 20:40:42 +03:00
self.emit = emitter.typedEmit()
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
self.font = state.mainWindowTemplate.appearance.font
self.linespacing = state.mainWindowTemplate.appearance.linespacing
self.usesLigatures = state.mainWindowTemplate.appearance.usesLigatures
2016-11-19 15:29:18 +03:00
2017-02-28 11:53:27 +03:00
super.init(frame: .zero)
2016-08-14 22:46:03 +03:00
2017-02-28 11:53:27 +03:00
self.addViews()
self.updateViews()
2016-09-24 17:35:27 +03:00
2017-02-28 11:53:27 +03:00
source
.observeOn(MainScheduler.instance)
.subscribe(onNext: { state in
let appearance = state.mainWindowTemplate.appearance
2016-07-21 20:28:58 +03:00
2017-02-28 11:53:27 +03:00
if self.font != appearance.font
|| self.linespacing != appearance.linespacing
|| self.usesLigatures != appearance.usesLigatures {
self.font = appearance.font
self.linespacing = appearance.linespacing
self.usesLigatures = appearance.usesLigatures
self.updateViews()
}
})
.disposed(by: self.disposeBag)
}
2016-08-14 22:46:03 +03:00
2017-04-22 16:56:13 +03:00
fileprivate let emit: (Action) -> Void
2017-02-28 11:53:27 +03:00
fileprivate let disposeBag = DisposeBag()
2016-09-25 18:50:33 +03:00
fileprivate let fontManager = NSFontManager.shared()
2016-08-14 22:46:03 +03:00
2017-02-28 11:53:27 +03:00
fileprivate var font: NSFont
fileprivate var linespacing: CGFloat
fileprivate var usesLigatures: Bool
2016-09-25 18:50:33 +03:00
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)
2016-10-27 00:37:51 +03:00
fileprivate let linespacingField = NSTextField(forAutoLayout: ())
2016-09-25 18:50:33 +03:00
fileprivate let ligatureCheckbox = NSButton(forAutoLayout: ())
fileprivate let previewArea = NSTextView(frame: CGRect.zero)
2016-07-21 20:28:58 +03:00
2016-09-25 18:50:33 +03:00
fileprivate let exampleText =
"abcdefghijklmnopqrstuvwxyz\n" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" +
"0123456789\n" +
"(){}[] +-*/= .,;:!?#&$%@|^\n" +
"<- -> => >> << >>= =<< .. \n" +
":: -< >- -<< >>- ++ /= =="
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
2017-02-28 11:53:27 +03:00
fileprivate func addViews() {
let paneTitle = self.paneTitleTextField(title: "Appearance")
let fontTitle = self.titleTextField(title: "Default Font:")
2016-07-21 20:28:58 +03:00
let fontPopup = self.fontPopup
fontPopup.translatesAutoresizingMaskIntoConstraints = false
fontPopup.target = self
2017-02-28 11:53:27 +03:00
fontPopup.action = #selector(AppearancePref.fontPopupAction)
// This takes approx. 0.8s - 1s on my machine... -_-
DispatchQueue.global(qos: .background).async {
fontPopup.addItems(withTitles: self.fontManager.availableFontNames(with: .fixedPitchFontMask)!)
}
let sizeCombo = self.sizeCombo
2016-09-25 18:50:33 +03:00
sizeCombo.delegate = self
sizeCombo.target = self
2017-02-28 11:53:27 +03:00
sizeCombo.action = #selector(AppearancePref.sizeComboBoxDidEnter(_:))
2016-07-21 20:28:58 +03:00
self.sizes.forEach { string in
2016-09-25 18:50:33 +03:00
sizeCombo.addItem(withObjectValue: string)
2016-07-21 20:28:58 +03:00
}
2016-10-27 00:37:51 +03:00
let linespacingTitle = self.titleTextField(title: "Line Spacing:")
let linespacingField = self.linespacingField
2016-07-30 23:06:42 +03:00
let ligatureCheckbox = self.ligatureCheckbox
self.configureCheckbox(button: ligatureCheckbox,
title: "Use Ligatures",
2017-02-28 11:53:27 +03:00
action: #selector(AppearancePref.usesLigaturesAction(_:)))
2016-07-21 20:28:58 +03:00
let previewArea = self.previewArea
2016-09-25 18:50:33 +03:00
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
2017-02-28 11:53:27 +03:00
previewArea.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
2016-09-25 18:50:33 +03:00
previewArea.textContainer?.containerSize = CGSize.init(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
previewArea.layoutManager?.replaceTextStorage(NSTextStorage(string: self.exampleText))
2016-09-25 18:50:33 +03:00
previewArea.isRichText = false
previewArea.turnOffLigatures(self)
let previewScrollView = NSScrollView(forAutoLayout: ())
previewScrollView.hasVerticalScroller = true
previewScrollView.hasHorizontalScroller = true
previewScrollView.autohidesScrollers = true
2016-09-25 18:50:33 +03:00
previewScrollView.borderType = .bezelBorder
previewScrollView.documentView = previewArea
self.addSubview(paneTitle)
self.addSubview(fontTitle)
self.addSubview(fontPopup)
self.addSubview(sizeCombo)
2016-10-27 00:37:51 +03:00
self.addSubview(linespacingTitle)
self.addSubview(linespacingField)
self.addSubview(ligatureCheckbox)
self.addSubview(previewScrollView)
2016-09-25 18:50:33 +03:00
paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18)
paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18)
2016-10-27 00:37:51 +03:00
fontTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18, relation: .greaterThanOrEqual)
2016-09-25 18:50:33 +03:00
fontTitle.autoAlignAxis(.baseline, toSameAxisOf: fontPopup)
2016-09-25 18:50:33 +03:00
fontPopup.autoPinEdge(.top, to: .bottom, of: paneTitle, withOffset: 18)
fontPopup.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
fontPopup.autoSetDimension(.width, toSize: 240)
2016-09-25 18:50:33 +03:00
sizeCombo.autoSetDimension(.width, toSize: 60)
2016-07-21 20:28:58 +03:00
// If we use .Baseline the combo box is placed one pixel off...
2016-09-25 18:50:33 +03:00
sizeCombo.autoAlignAxis(.horizontal, toSameAxisOf: fontPopup)
sizeCombo.autoPinEdge(.left, to: .right, of: fontPopup, withOffset: 5)
2016-08-14 15:29:50 +03:00
2016-10-27 00:37:51 +03:00
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)
2016-10-27 01:26:04 +03:00
NotificationCenter.default.addObserver(forName: NSNotification.Name.NSControlTextDidEndEditing,
object: linespacingField,
2017-02-28 11:53:27 +03:00
queue: nil) { [unowned self] _ in
2016-10-27 01:26:04 +03:00
self.linespacingAction()
}
2016-10-27 00:37:51 +03:00
ligatureCheckbox.autoPinEdge(.top, to: .bottom, of: linespacingField, withOffset: 18)
2016-09-25 18:50:33 +03:00
ligatureCheckbox.autoPinEdge(.left, to: .right, of: fontTitle, withOffset: 5)
2016-07-21 20:28:58 +03:00
2016-09-25 18:50:33 +03:00
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)
}
2016-08-14 22:46:03 +03:00
2017-02-28 11:53:27 +03:00
fileprivate func updateViews() {
self.fontPopup.selectItem(withTitle: self.font.fontName)
self.sizeCombo.stringValue = String(Int(self.font.pointSize))
self.linespacingField.stringValue = String(format: "%.2f", self.linespacing)
self.ligatureCheckbox.boolState = self.usesLigatures
self.previewArea.font = self.font
2016-09-11 15:28:56 +03:00
2017-02-28 11:53:27 +03:00
if self.usesLigatures {
2016-09-11 15:28:56 +03:00
self.previewArea.useAllLigatures(self)
} else {
self.previewArea.turnOffLigatures(self)
2016-08-14 22:46:03 +03:00
}
}
}
2016-07-21 20:28:58 +03:00
// MARK: - Actions
2017-02-28 11:53:27 +03:00
extension AppearancePref {
2016-09-25 18:50:33 +03:00
func usesLigaturesAction(_ sender: NSButton) {
2017-04-22 16:56:13 +03:00
self.emit(.setUsesLigatures(sender.boolState))
2016-07-30 23:06:42 +03:00
}
2016-07-21 20:28:58 +03:00
2016-09-25 18:50:33 +03:00
func fontPopupAction(_ sender: NSPopUpButton) {
2016-08-14 22:46:03 +03:00
guard let selectedItem = self.fontPopup.selectedItem else {
return
2016-07-21 20:28:58 +03:00
}
2017-02-28 11:53:27 +03:00
guard selectedItem.title != self.font.fontName else {
2016-08-14 22:46:03 +03:00
return
}
2017-02-28 11:53:27 +03:00
guard let newFont = NSFont(name: selectedItem.title, size: self.font.pointSize) else {
2016-08-14 22:46:03 +03:00
return
}
2017-04-22 16:56:13 +03:00
self.emit(.setFont(newFont))
2016-07-21 20:28:58 +03:00
}
2016-09-25 18:50:33 +03:00
func comboBoxSelectionDidChange(_ notification: Notification) {
guard (notification.object as! NSComboBox) === self.sizeCombo else {
return
}
2016-08-14 22:46:03 +03:00
let newFontSize = self.cappedFontSize(Int(self.sizes[self.sizeCombo.indexOfSelectedItem]))
2017-02-28 11:53:27 +03:00
let newFont = self.fontManager.convert(self.font, toSize: newFontSize)
2017-04-22 16:56:13 +03:00
self.emit(.setFont(newFont))
2016-07-21 20:28:58 +03:00
}
2016-09-25 18:50:33 +03:00
func sizeComboBoxDidEnter(_ sender: AnyObject!) {
2016-08-14 22:46:03 +03:00
let newFontSize = self.cappedFontSize(self.sizeCombo.integerValue)
2017-02-28 11:53:27 +03:00
let newFont = self.fontManager.convert(self.font, toSize: newFontSize)
2017-04-22 16:56:13 +03:00
self.emit(.setFont(newFont))
2016-07-21 20:28:58 +03:00
}
2016-10-27 01:26:04 +03:00
func linespacingAction() {
2016-10-27 09:21:29 +03:00
let newLinespacing = self.cappedLinespacing(self.linespacingField.floatValue)
2017-04-22 16:56:13 +03:00
self.emit(.setLinespacing(newLinespacing))
2016-10-27 09:21:29 +03:00
}
fileprivate func cappedLinespacing(_ linespacing: Float) -> CGFloat {
let cgfLinespacing = CGFloat(linespacing)
2016-11-19 15:29:18 +03:00
guard cgfLinespacing >= NeoVimView.minLinespacing else {
return NeoVimView.defaultLinespacing
2016-10-27 09:21:29 +03:00
}
2016-11-19 15:29:18 +03:00
guard cgfLinespacing <= NeoVimView.maxLinespacing else {
return NeoVimView.maxLinespacing
2016-10-27 09:21:29 +03:00
}
return cgfLinespacing
2016-10-27 01:26:04 +03:00
}
2016-09-25 18:50:33 +03:00
fileprivate func cappedFontSize(_ size: Int) -> CGFloat {
2016-10-27 09:21:29 +03:00
let cgfSize = CGFloat(size)
2016-11-19 15:29:18 +03:00
guard cgfSize >= NeoVimView.minFontSize else {
return NeoVimView.defaultFont.pointSize
2016-07-21 20:28:58 +03:00
}
2016-11-19 15:29:18 +03:00
guard cgfSize <= NeoVimView.maxFontSize else {
return NeoVimView.maxFontSize
2016-07-21 20:28:58 +03:00
}
2016-10-27 09:21:29 +03:00
return cgfSize
}
}