diff --git a/NvimView/NvimView/NvimView+Mouse.swift b/NvimView/NvimView/NvimView+Mouse.swift index 0fdf8a16..11e233fd 100644 --- a/NvimView/NvimView/NvimView+Mouse.swift +++ b/NvimView/NvimView/NvimView+Mouse.swift @@ -37,8 +37,8 @@ extension NvimView { return } - let (absDeltaX, absDeltaY) = (min(Int(ceil(abs(deltaX) / 5.0)), maxScrollDeltaX), - min(Int(ceil(abs(deltaY) / 5.0)), maxScrollDeltaY)) + let (absDeltaX, absDeltaY) = (min(Int(ceil(abs(deltaX) / self.trackpadScrollResistance)), maxScrollDeltaX), + min(Int(ceil(abs(deltaY) / self.trackpadScrollResistance)), maxScrollDeltaY)) let (horizSign, vertSign) = (deltaX > 0 ? 1 : -1, deltaY > 0 ? 1 : -1) self.uiBridge.scroll(horizontal: horizSign * absDeltaX, vertical: vertSign * absDeltaY, at: cellPosition) } diff --git a/NvimView/NvimView/NvimView.swift b/NvimView/NvimView/NvimView.swift index ff1be942..21d06118 100644 --- a/NvimView/NvimView/NvimView.swift +++ b/NvimView/NvimView/NvimView.swift @@ -107,6 +107,8 @@ public class NvimView: NSView, public internal(set) var theme = Theme.default + public var trackpadScrollResistance = CGFloat(5) + public var usesLigatures = false { didSet { self.drawer.usesLigatures = self.usesLigatures diff --git a/VimR/VimR/AdvancedPrefReducer.swift b/VimR/VimR/AdvancedPrefReducer.swift index b7384126..0759af81 100644 --- a/VimR/VimR/AdvancedPrefReducer.swift +++ b/VimR/VimR/AdvancedPrefReducer.swift @@ -14,6 +14,10 @@ class AdvancedPrefReducer { switch pair.action { + case let .setTrackpadScrollResistance(value): + state.mainWindowTemplate.trackpadScrollResistance = value + state.mainWindows.keys.forEach { state.mainWindows[$0]?.trackpadScrollResistance = value } + case let .setUseInteractiveZsh(value): state.mainWindowTemplate.useInteractiveZsh = value diff --git a/VimR/VimR/AdvencedPref.swift b/VimR/VimR/AdvencedPref.swift index d0b68c6c..2dc35cf5 100644 --- a/VimR/VimR/AdvencedPref.swift +++ b/VimR/VimR/AdvencedPref.swift @@ -15,6 +15,7 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { case setUseInteractiveZsh(Bool) case setUseSnapshotUpdate(Bool) + case setTrackpadScrollResistance(Double) } override var displayName: String { @@ -30,6 +31,7 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { self.useInteractiveZsh = state.mainWindowTemplate.useInteractiveZsh self.useSnapshotUpdate = state.useSnapshotUpdate + self.sensitivity = 1 / state.mainWindowTemplate.trackpadScrollResistance super.init(frame: .zero) @@ -40,7 +42,9 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { .observeOn(MainScheduler.instance) .subscribe(onNext: { state in if self.useInteractiveZsh != state.mainWindowTemplate.useInteractiveZsh - || self.useSnapshotUpdate != state.useSnapshotUpdate { + || self.useSnapshotUpdate != state.useSnapshotUpdate + || self.sensitivity != state.mainWindowTemplate.trackpadScrollResistance + { self.useInteractiveZsh = state.mainWindowTemplate.useInteractiveZsh self.useSnapshotUpdate = state.useSnapshotUpdate @@ -55,9 +59,11 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { private var useInteractiveZsh: Bool private var useSnapshotUpdate: Bool + private var sensitivity: Double private let useInteractiveZshCheckbox = NSButton(forAutoLayout: ()) private let useSnapshotUpdateCheckbox = NSButton(forAutoLayout: ()) + private let sensitivitySlider = NSSlider(forAutoLayout: ()) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") @@ -66,6 +72,8 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { private func updateViews() { self.useSnapshotUpdateCheckbox.boolState = self.useSnapshotUpdate self.useInteractiveZshCheckbox.boolState = self.useInteractiveZsh + + // We don't update the value of the NSSlider since we don't know when events are fired. } private func addViews() { @@ -76,11 +84,12 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { title: "Use interactive mode for zsh", action: #selector(AdvancedPref.useInteractiveZshAction(_:))) - let useInteractiveZshInfo = self.infoTextField( - markdown: "If your login shell is `zsh`, when checked, the `-i` option will be used to launch `zsh`. \n" - + "Checking this option may break VimR if your `.zshrc` contains complex stuff. \n" - + "It may be a good idea to put the `PATH`-settings in `.zshenv` and let this unchecked. \n" - + "Use with caution." + let useInteractiveZshInfo = self.infoTextField(markdown: """ + If your login shell is `zsh`, when checked, the `-i` option will be used to launch `zsh`. + Checking this option may break VimR if your `.zshrc` contains complex stuff. + It may be a good idea to put the `PATH`-settings in `.zshenv` and let this unchecked. + *Use with caution.* + """ ) let useSnapshotUpdate = self.useSnapshotUpdateCheckbox @@ -88,39 +97,64 @@ class AdvancedPref: PrefPane, UiComponent, NSTextFieldDelegate { title: "Use Snapshot Update Channel", action: #selector(AdvancedPref.useSnapshotUpdateChannelAction(_:))) - let useSnapshotUpdateInfo = self.infoTextField( - markdown: "If you are adventurous, check this. \n" - + "You'll be test driving the newest snapshot builds of VimR in no time!" + let useSnapshotUpdateInfo = self.infoTextField(markdown: """ + If you are adventurous, check this. + You'll be test driving the newest snapshot builds of VimR in no time! + """ ) + let sensitivityTitle = self.titleTextField(title: "Scroll Sensitivity:") + sensitivitySlider.maxValue = 1 / 5.0 + sensitivitySlider.minValue = 1 / 500 + sensitivitySlider.target = self + sensitivitySlider.action = #selector(sensitivitySliderAction) + + // We set the value of the NSSlider only at the beginning. + self.sensitivitySlider.doubleValue = self.sensitivity + self.addSubview(paneTitle) self.addSubview(useSnapshotUpdate) self.addSubview(useSnapshotUpdateInfo) self.addSubview(useInteractiveZsh) self.addSubview(useInteractiveZshInfo) + self.addSubview(sensitivityTitle) + self.addSubview(sensitivitySlider) paneTitle.autoPinEdge(toSuperviewEdge: .top, withInset: 18) paneTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18) paneTitle.autoPinEdge(toSuperviewEdge: .right, withInset: 18, relation: .greaterThanOrEqual) useSnapshotUpdate.autoPinEdge(.top, to: .bottom, of: paneTitle, withOffset: 18) - useSnapshotUpdate.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + useSnapshotUpdate.autoPinEdge(.left, to: .right, of: sensitivityTitle, withOffset: 5) useSnapshotUpdateInfo.autoPinEdge(.top, to: .bottom, of: useSnapshotUpdate, withOffset: 5) - useSnapshotUpdateInfo.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + useSnapshotUpdateInfo.autoPinEdge(.left, to: .left, of: useSnapshotUpdate) + useSnapshotUpdateInfo.autoSetDimension(.width, toSize: 300) useInteractiveZsh.autoPinEdge(.top, to: .bottom, of: useSnapshotUpdateInfo, withOffset: 18) - useInteractiveZsh.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + useInteractiveZsh.autoPinEdge(.left, to: .right, of: sensitivityTitle, withOffset: 5) useInteractiveZshInfo.autoPinEdge(.top, to: .bottom, of: useInteractiveZsh, withOffset: 5) - useInteractiveZshInfo.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + useInteractiveZshInfo.autoPinEdge(.left, to: .left, of: useInteractiveZsh) + useInteractiveZshInfo.autoSetDimension(.width, toSize: 300) + + sensitivityTitle.autoPinEdge(toSuperviewEdge: .left, withInset: 18) + sensitivityTitle.autoPinEdge(.top, to: .bottom, of: useInteractiveZshInfo, withOffset: 18) + + sensitivitySlider.autoSetDimension(.width, toSize: 100) + sensitivitySlider.autoAlignAxis(.baseline, toSameAxisOf: sensitivityTitle) + sensitivitySlider.autoPinEdge(.left, to: .right, of: sensitivityTitle, withOffset: 5) } } // MARK: - Actions extension AdvancedPref { + @objc func sensitivitySliderAction(_ sender: NSSlider) { + self.emit(.setTrackpadScrollResistance(1 / sender.doubleValue)) + } + @objc func useInteractiveZshAction(_ sender: NSButton) { self.emit(.setUseInteractiveZsh(sender.boolState)) } diff --git a/VimR/VimR/MainWindow.swift b/VimR/VimR/MainWindow.swift index 261a20bb..3cea12b1 100644 --- a/VimR/VimR/MainWindow.swift +++ b/VimR/VimR/MainWindow.swift @@ -205,6 +205,7 @@ class MainWindow: NSObject, self.addViews() + self.neoVimView.trackpadScrollResistance = CGFloat(state.trackpadScrollResistance) self.updateNeoVimAppearance() self.open(urls: state.urlsToOpen) @@ -305,6 +306,10 @@ class MainWindow: NSObject, self.set(repUrl: nil, themed: self.titlebarThemed) } + if self.neoVimView.trackpadScrollResistance != CGFloat(state.trackpadScrollResistance) { + self.neoVimView.trackpadScrollResistance = CGFloat(state.trackpadScrollResistance) + } + if self.defaultFont != state.appearance.font || self.linespacing != state.appearance.linespacing || self.usesLigatures != state.appearance.usesLigatures { diff --git a/VimR/VimR/SerializableStates.swift b/VimR/VimR/SerializableStates.swift index 93a9c36c..35469b18 100644 --- a/VimR/VimR/SerializableStates.swift +++ b/VimR/VimR/SerializableStates.swift @@ -48,6 +48,7 @@ class Keys { static let activeTools = "active-tools" static let frame = "frame" + static let trackpadScrollResistance = "trackpad-scroll-resistance" static let useInteractiveZsh = "use-interactive-zsh" static let isShowHidden = "is-show-hidden" } diff --git a/VimR/VimR/States.swift b/VimR/VimR/States.swift index ac8d12ac..8dd530e7 100644 --- a/VimR/VimR/States.swift +++ b/VimR/VimR/States.swift @@ -243,6 +243,8 @@ extension MainWindow { var fileBrowserShowHidden = false + var trackpadScrollResistance = 5.0 + // neovim var uuid = UUID().uuidString var currentBuffer: NvimView.Buffer? @@ -278,6 +280,9 @@ extension MainWindow { // Stay compatible with 168 self.useInteractiveZsh = PrefUtils.bool(from: dict, for: Keys.MainWindow.useInteractiveZsh, default: false) + self.trackpadScrollResistance = PrefUtils.value(from: dict, + for: Keys.MainWindow.trackpadScrollResistance, + default: 5.0) let frameString = PrefUtils.string(from: dict, for: Keys.MainWindow.frame, default: NSStringFromRect(self.frame)) @@ -331,6 +336,8 @@ extension MainWindow { Keys.MainWindow.frame: NSStringFromRect(self.frame), + Keys.MainWindow.trackpadScrollResistance: self.trackpadScrollResistance, + Keys.Appearance.key: self.appearance.dict(), Keys.WorkspaceTool.key: self.tools.mapToDict { ($0.rawValue, $1.dict()) },