2016-07-19 20:34:05 +03:00
|
|
|
/**
|
|
|
|
* Tae Won Ha - http://taewon.de - @hataewon
|
|
|
|
* See LICENSE
|
|
|
|
*/
|
|
|
|
|
|
|
|
import Cocoa
|
|
|
|
import PureLayout
|
2016-07-21 20:28:58 +03:00
|
|
|
import RxSwift
|
2016-07-19 20:34:05 +03:00
|
|
|
|
2016-07-21 20:28:58 +03:00
|
|
|
struct AppearancePrefData {
|
|
|
|
let editorFont: NSFont
|
2016-07-30 23:06:42 +03:00
|
|
|
let editorUsesLigatures: Bool
|
2016-07-21 20:28:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
class AppearancePrefPane: NSView, NSComboBoxDelegate, NSControlTextEditingDelegate, ViewComponent {
|
|
|
|
|
|
|
|
private let source: Observable<Any>
|
|
|
|
private let disposeBag = DisposeBag()
|
|
|
|
|
|
|
|
private let subject = PublishSubject<Any>()
|
|
|
|
var sink: Observable<Any> {
|
|
|
|
return self.subject.asObservable()
|
|
|
|
}
|
2016-07-19 20:34:05 +03:00
|
|
|
|
2016-07-21 20:28:58 +03:00
|
|
|
var view: NSView {
|
|
|
|
return self
|
|
|
|
}
|
|
|
|
|
|
|
|
private let sizes = [9, 10, 11, 12, 13, 14, 16, 18, 24, 36, 48, 64]
|
2016-07-19 20:34:05 +03:00
|
|
|
private let sizeCombo = NSComboBox(forAutoLayout: ())
|
2016-07-21 20:28:58 +03:00
|
|
|
private let fontPopup = NSPopUpButton(frame: CGRect.zero, pullsDown: false)
|
2016-07-30 23:06:42 +03:00
|
|
|
private let ligatureCheckbox = NSButton(forAutoLayout: ())
|
2016-07-24 23:31:19 +03:00
|
|
|
private let previewArea = NSTextView(frame: CGRect.zero)
|
2016-07-21 20:28:58 +03:00
|
|
|
|
2016-08-02 08:34:00 +03:00
|
|
|
private let exampleText =
|
|
|
|
"abcdefghijklmnopqrstuvwxyz\n" +
|
|
|
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ\n" +
|
|
|
|
"0123456789\n" +
|
|
|
|
"(){}[] +-*/= .,;:!?#&$%@|^\n" +
|
|
|
|
"<- -> => >> << >>= =<< .. \n" +
|
|
|
|
":: -< >- -<< >>- ++ /= =="
|
|
|
|
|
2016-07-21 20:28:58 +03:00
|
|
|
private var font: NSFont
|
|
|
|
private var fontSize = CGFloat(13)
|
|
|
|
private var fontName = "Menlo"
|
2016-07-31 00:04:20 +03:00
|
|
|
private var usesLigatures = false
|
2016-07-19 20:34:05 +03:00
|
|
|
|
|
|
|
// Return true to place this to the upper left corner when the scroll view is bigger than this view.
|
|
|
|
override var flipped: Bool {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-07-27 00:40:20 +03:00
|
|
|
init(source: Observable<Any>, initialData: AppearancePrefData) {
|
2016-07-21 20:28:58 +03:00
|
|
|
self.source = source
|
|
|
|
|
2016-07-27 00:40:20 +03:00
|
|
|
self.font = initialData.editorFont
|
|
|
|
self.fontSize = initialData.editorFont.pointSize
|
|
|
|
self.fontName = initialData.editorFont.fontName
|
2016-07-31 00:04:20 +03:00
|
|
|
self.usesLigatures = initialData.editorUsesLigatures
|
2016-07-21 20:28:58 +03:00
|
|
|
|
|
|
|
super.init(frame: CGRect.zero)
|
|
|
|
self.translatesAutoresizingMaskIntoConstraints = false
|
2016-07-19 20:34:05 +03:00
|
|
|
|
|
|
|
self.addViews()
|
2016-07-27 00:40:20 +03:00
|
|
|
self.addReactions()
|
2016-07-19 20:34:05 +03:00
|
|
|
}
|
2016-07-21 20:28:58 +03:00
|
|
|
|
|
|
|
deinit {
|
|
|
|
self.subject.onCompleted()
|
|
|
|
}
|
2016-07-19 20:34:05 +03:00
|
|
|
|
|
|
|
required init?(coder: NSCoder) {
|
|
|
|
fatalError("init(coder:) has not been implemented")
|
|
|
|
}
|
|
|
|
|
2016-07-27 00:40:20 +03:00
|
|
|
private func addReactions() {
|
|
|
|
self.source
|
|
|
|
.filter { $0 is PrefData }
|
2016-08-02 08:34:00 +03:00
|
|
|
.map { ($0 as! PrefData).appearance }
|
|
|
|
.filter { $0.editorFont != self.font || $0.editorUsesLigatures != self.usesLigatures }
|
|
|
|
.subscribeNext { [unowned self] appearance in
|
|
|
|
NSLog("react")
|
|
|
|
let editorFont = appearance.editorFont
|
2016-07-27 00:40:20 +03:00
|
|
|
self.font = editorFont
|
|
|
|
self.fontName = editorFont.fontName
|
|
|
|
self.fontSize = editorFont.pointSize
|
2016-08-02 08:34:00 +03:00
|
|
|
self.usesLigatures = appearance.editorUsesLigatures
|
2016-07-27 00:40:20 +03:00
|
|
|
self.updateViews()
|
|
|
|
}
|
|
|
|
.addDisposableTo(self.disposeBag)
|
|
|
|
}
|
|
|
|
|
2016-07-19 20:34:05 +03:00
|
|
|
private func addViews() {
|
|
|
|
let fontTitle = NSTextField(forAutoLayout: ())
|
|
|
|
fontTitle.backgroundColor = NSColor.clearColor();
|
|
|
|
fontTitle.stringValue = "Default Font:";
|
|
|
|
fontTitle.editable = false;
|
|
|
|
fontTitle.bordered = false;
|
|
|
|
fontTitle.alignment = .Right;
|
|
|
|
|
|
|
|
let fontManager = NSFontManager.sharedFontManager()
|
2016-07-21 20:28:58 +03:00
|
|
|
let fontPopup = self.fontPopup
|
2016-07-19 20:34:05 +03:00
|
|
|
fontPopup.translatesAutoresizingMaskIntoConstraints = false
|
|
|
|
fontPopup.target = self
|
2016-07-21 20:28:58 +03:00
|
|
|
fontPopup.action = #selector(AppearancePrefPane.fontPopupAction)
|
2016-07-19 20:34:05 +03:00
|
|
|
fontPopup.addItemsWithTitles(fontManager.availableFontNamesWithTraits(.FixedPitchFontMask)!)
|
|
|
|
|
|
|
|
let sizeCombo = self.sizeCombo
|
|
|
|
sizeCombo.setDelegate(self)
|
2016-08-03 20:01:37 +03:00
|
|
|
sizeCombo.target = self
|
|
|
|
sizeCombo.action = #selector(AppearancePrefPane.sizeComboBoxDidEnter(_:))
|
2016-07-21 20:28:58 +03:00
|
|
|
self.sizes.forEach { string in
|
|
|
|
sizeCombo.addItemWithObjectValue(string)
|
|
|
|
}
|
2016-07-30 22:07:23 +03:00
|
|
|
|
2016-07-30 23:06:42 +03:00
|
|
|
let ligatureCheckbox = self.ligatureCheckbox
|
2016-07-30 22:07:23 +03:00
|
|
|
ligatureCheckbox.title = "Use Ligatures"
|
|
|
|
ligatureCheckbox.setButtonType(.SwitchButton)
|
|
|
|
ligatureCheckbox.bezelStyle = .ThickSquareBezelStyle
|
2016-07-30 23:06:42 +03:00
|
|
|
ligatureCheckbox.target = self
|
|
|
|
ligatureCheckbox.action = #selector(AppearancePrefPane.usesLigaturesAction(_:))
|
2016-07-21 20:28:58 +03:00
|
|
|
|
2016-07-24 23:31:19 +03:00
|
|
|
let previewArea = self.previewArea
|
|
|
|
previewArea.editable = true
|
|
|
|
previewArea.maxSize = CGSize(width: CGFloat.max, height: CGFloat.max)
|
|
|
|
previewArea.verticallyResizable = true
|
|
|
|
previewArea.horizontallyResizable = true
|
|
|
|
previewArea.textContainer?.heightTracksTextView = false
|
|
|
|
previewArea.textContainer?.widthTracksTextView = false
|
|
|
|
previewArea.autoresizingMask = [ .ViewWidthSizable, .ViewHeightSizable]
|
|
|
|
previewArea.textContainer?.containerSize = CGSize.init(width: CGFloat.max, height: CGFloat.max)
|
2016-08-02 08:34:00 +03:00
|
|
|
previewArea.layoutManager?.replaceTextStorage(NSTextStorage(string: self.exampleText))
|
|
|
|
previewArea.richText = false
|
|
|
|
previewArea.turnOffLigatures(self)
|
2016-07-24 23:31:19 +03:00
|
|
|
|
|
|
|
let previewScrollView = NSScrollView(forAutoLayout: ())
|
|
|
|
previewScrollView.hasVerticalScroller = true
|
|
|
|
previewScrollView.hasHorizontalScroller = true
|
|
|
|
previewScrollView.autohidesScrollers = true
|
|
|
|
previewScrollView.borderType = .BezelBorder
|
|
|
|
previewScrollView.documentView = previewArea
|
|
|
|
|
2016-07-19 20:34:05 +03:00
|
|
|
self.addSubview(fontTitle)
|
|
|
|
self.addSubview(fontPopup)
|
|
|
|
self.addSubview(sizeCombo)
|
2016-07-30 22:07:23 +03:00
|
|
|
self.addSubview(ligatureCheckbox)
|
2016-07-24 23:31:19 +03:00
|
|
|
self.addSubview(previewScrollView)
|
2016-07-19 20:34:05 +03:00
|
|
|
|
|
|
|
fontTitle.autoPinEdgeToSuperviewEdge(.Left, withInset: 18)
|
|
|
|
fontTitle.autoAlignAxis(.Baseline, toSameAxisOfView: fontPopup)
|
|
|
|
|
|
|
|
fontPopup.autoPinEdgeToSuperviewEdge(.Top, withInset: 18)
|
|
|
|
fontPopup.autoPinEdge(.Left, toEdge: .Right, ofView: fontTitle, withOffset: 5)
|
|
|
|
fontPopup.autoSetDimension(.Width, toSize: 180)
|
|
|
|
|
|
|
|
sizeCombo.autoSetDimension(.Width, toSize: 75)
|
2016-07-21 20:28:58 +03:00
|
|
|
// If we use .Baseline the combo box is placed one pixel off...
|
|
|
|
sizeCombo.autoAlignAxis(.Horizontal, toSameAxisOfView: fontPopup)
|
2016-07-19 20:34:05 +03:00
|
|
|
sizeCombo.autoPinEdge(.Left, toEdge: .Right, ofView: fontPopup, withOffset: 5)
|
2016-07-30 22:07:23 +03:00
|
|
|
|
|
|
|
ligatureCheckbox.autoPinEdge(.Top, toEdge: .Bottom, ofView: sizeCombo, withOffset: 18)
|
|
|
|
ligatureCheckbox.autoPinEdge(.Left, toEdge: .Right, ofView: fontTitle, withOffset: 5)
|
2016-07-21 20:28:58 +03:00
|
|
|
|
2016-07-24 23:31:19 +03:00
|
|
|
previewScrollView.autoSetDimension(.Height, toSize: 200)
|
2016-07-30 22:07:23 +03:00
|
|
|
previewScrollView.autoPinEdge(.Top, toEdge: .Bottom, ofView: ligatureCheckbox, withOffset: 18)
|
2016-07-24 23:31:19 +03:00
|
|
|
previewScrollView.autoPinEdgeToSuperviewEdge(.Right, withInset: 18)
|
|
|
|
previewScrollView.autoPinEdgeToSuperviewEdge(.Bottom, withInset: 18)
|
|
|
|
previewScrollView.autoPinEdgeToSuperviewEdge(.Left, withInset: 18)
|
2016-07-21 20:28:58 +03:00
|
|
|
|
2016-07-27 00:40:20 +03:00
|
|
|
self.updateViews()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func updateViews() {
|
|
|
|
self.fontPopup.selectItemWithTitle(self.fontName)
|
|
|
|
self.sizeCombo.stringValue = String(Int(self.fontSize))
|
2016-07-31 00:04:20 +03:00
|
|
|
self.ligatureCheckbox.state = self.usesLigatures ? NSOnState : NSOffState
|
2016-07-27 00:40:20 +03:00
|
|
|
self.previewArea.font = self.font
|
2016-08-02 08:34:00 +03:00
|
|
|
if self.usesLigatures {
|
|
|
|
self.previewArea.useAllLigatures(self)
|
|
|
|
} else {
|
|
|
|
self.previewArea.turnOffLigatures(self)
|
|
|
|
}
|
2016-07-19 20:34:05 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-21 20:28:58 +03:00
|
|
|
// MARK: - Actions
|
2016-07-19 20:34:05 +03:00
|
|
|
extension AppearancePrefPane {
|
2016-07-30 23:06:42 +03:00
|
|
|
|
|
|
|
func usesLigaturesAction(sender: NSButton) {
|
|
|
|
self.publishData()
|
|
|
|
}
|
2016-07-21 20:28:58 +03:00
|
|
|
|
|
|
|
func fontPopupAction(sender: NSPopUpButton) {
|
|
|
|
if let selectedItem = self.fontPopup.selectedItem {
|
|
|
|
self.fontName = selectedItem.title
|
|
|
|
} else {
|
|
|
|
self.fontName = "Menlo"
|
|
|
|
}
|
|
|
|
|
|
|
|
self.publishData()
|
|
|
|
}
|
|
|
|
|
2016-07-19 20:34:05 +03:00
|
|
|
func comboBoxSelectionDidChange(notification: NSNotification) {
|
|
|
|
guard notification.object! === self.sizeCombo else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-07-21 20:28:58 +03:00
|
|
|
self.fontSize = self.cappedFontSize(Int(self.sizes[self.sizeCombo.indexOfSelectedItem]))
|
|
|
|
self.publishData()
|
2016-07-19 20:34:05 +03:00
|
|
|
}
|
|
|
|
|
2016-08-03 20:01:37 +03:00
|
|
|
func sizeComboBoxDidEnter(sender: AnyObject!) {
|
2016-07-21 20:28:58 +03:00
|
|
|
self.fontSize = self.cappedFontSize(self.sizeCombo.integerValue)
|
|
|
|
self.publishData()
|
|
|
|
}
|
|
|
|
|
|
|
|
private func publishData() {
|
|
|
|
guard let font = NSFont(name: self.fontName, size: CGFloat(self.fontSize)) else {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
self.font = font
|
2016-08-02 08:34:00 +03:00
|
|
|
self.usesLigatures = self.ligatureCheckbox.state == NSOnState
|
|
|
|
self.updateViews()
|
|
|
|
|
2016-07-30 23:06:42 +03:00
|
|
|
self.subject.onNext(AppearancePrefData(editorFont: font, editorUsesLigatures: usesLigatures))
|
2016-07-21 20:28:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private func cappedFontSize(size: Int) -> CGFloat {
|
|
|
|
guard size >= 4 else {
|
|
|
|
return 13
|
|
|
|
}
|
|
|
|
|
|
|
|
guard size <= 128 else {
|
|
|
|
return 128
|
|
|
|
}
|
|
|
|
|
|
|
|
return CGFloat(size)
|
2016-07-19 20:34:05 +03:00
|
|
|
}
|
|
|
|
}
|