Skip to content

Commit

Permalink
refactor: [ScrollPhase] remove afterPost
Browse files Browse the repository at this point in the history
  • Loading branch information
Caldis committed Sep 12, 2022
1 parent c90b495 commit 3467d86
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 123 deletions.
8 changes: 4 additions & 4 deletions Mos.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
0BB0F1C020F3A05D00909B7D /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BB0F1BF20F3A05D00909B7D /* WelcomeViewController.swift */; };
0BC2AD2F20F3BA090084416B /* PreferencesContributorsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BC2AD2E20F3BA090084416B /* PreferencesContributorsViewController.swift */; };
0BD39F68204EFC6C000CCC42 /* StatusItemManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BD39F67204EFC6C000CCC42 /* StatusItemManager.swift */; };
0BE4C93B20617C6E0001EA0E /* ScrollFiller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE4C93A20617C6E0001EA0E /* ScrollFiller.swift */; };
0BE4C93B20617C6E0001EA0E /* ScrollFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE4C93A20617C6E0001EA0E /* ScrollFilter.swift */; };
0BE5A292253A1168006D61C0 /* LoginServiceKit in Frameworks */ = {isa = PBXBuildFile; productRef = 0BE5A291253A1168006D61C0 /* LoginServiceKit */; };
0BE5A296253A119C006D61C0 /* Charts in Frameworks */ = {isa = PBXBuildFile; productRef = 0BE5A295253A119C006D61C0 /* Charts */; };
22003C6E1E8555F0001220BC /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22003C6D1E8555F0001220BC /* Utils.swift */; };
Expand Down Expand Up @@ -90,7 +90,7 @@
0BB0F1BF20F3A05D00909B7D /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
0BC2AD2E20F3BA090084416B /* PreferencesContributorsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesContributorsViewController.swift; sourceTree = "<group>"; };
0BD39F67204EFC6C000CCC42 /* StatusItemManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusItemManager.swift; sourceTree = "<group>"; };
0BE4C93A20617C6E0001EA0E /* ScrollFiller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollFiller.swift; sourceTree = "<group>"; };
0BE4C93A20617C6E0001EA0E /* ScrollFilter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollFilter.swift; sourceTree = "<group>"; };
22003C6D1E8555F0001220BC /* Utils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
22142DA01E25344E00E4BFBF /* Mos.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mos.app; sourceTree = BUILT_PRODUCTS_DIR; };
22142DA31E25344E00E4BFBF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -145,7 +145,7 @@
children = (
2291A8A21E2A5400004181B8 /* ScrollCore.swift */,
0B12EF1C203ADD230058B169 /* ScrollUtils.swift */,
0BE4C93A20617C6E0001EA0E /* ScrollFiller.swift */,
0BE4C93A20617C6E0001EA0E /* ScrollFilter.swift */,
0B6D3B692041C348004CC064 /* ScrollEvent.swift */,
0B474328258E12A200A5F8A3 /* ScrollPhase.swift */,
0B99C5FB2577FC9200021578 /* ScrollPoster.swift */,
Expand Down Expand Up @@ -444,7 +444,7 @@
23E9673F23613D1100E652F6 /* PreferencesExceptionInputViewController.swift in Sources */,
232E50A7236B372100265C58 /* PreferencesAdvanceWithApplicationViewController.swift in Sources */,
22A32B331E334B2E00A5CF38 /* PreferencesAboutViewController.swift in Sources */,
0BE4C93B20617C6E0001EA0E /* ScrollFiller.swift in Sources */,
0BE4C93B20617C6E0001EA0E /* ScrollFilter.swift in Sources */,
0B6D3B682041A790004CC064 /* WindowManager.swift in Sources */,
0B12EF1A203AD9C80058B169 /* Options.swift in Sources */,
228093501E2B5DA90061A00F /* PreferencesGeneralViewController.swift in Sources */,
Expand Down
6 changes: 3 additions & 3 deletions Mos/ScrollCore/ScrollCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ class ScrollCore {
x: scrollEvent.X.usableValue,
speed: speed,
amplification: ScrollCore.shared.dashAmplification
).enable()
).tryStart()
}
// 返回事件对象
if returnOriginalEvent {
Expand Down Expand Up @@ -238,7 +238,7 @@ class ScrollCore {
// MARK: - 鼠标事件处理
let mouseLeftEventCallBack: CGEventTapCallBack = { (proxy, type, event, refcon) in
// 如果点击左键则停止滚动
ScrollPoster.shared.disableManual()
ScrollPoster.shared.stop()
return nil
}

Expand Down Expand Up @@ -279,7 +279,7 @@ class ScrollCore {
if !isActive {return}
isActive = false
// 停止滚动事件发送器
ScrollPoster.shared.disableAuto()
ScrollPoster.shared.stop()
// 停止截取事件
scrollEventInterceptor?.stop()
hotkeyEventInterceptor?.stop()
Expand Down
43 changes: 0 additions & 43 deletions Mos/ScrollCore/ScrollFiller.swift

This file was deleted.

43 changes: 43 additions & 0 deletions Mos/ScrollCore/ScrollFilter.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// Filter.swift
// Mos
// 曲线峰值滤波, 用于去除滚动的起始抖动
// Created by Caldis on 2018/3/21.
// Copyright © 2018年 Caldis. All rights reserved.
//

import Cocoa

class ScrollFilter {

var curveWindowY = [0.0, 0.0]
var curveWindowX = [0.0, 0.0]

// 填充值
public func fill(with nextValue: ( y: Double, x: Double )) -> ( y: Double, x: Double ) {
curveWindowY = polish(curveWindowY, with: nextValue.y)
curveWindowX = polish(curveWindowX, with: nextValue.x)
return value()
}
// 获取值
public func value() -> ( y: Double, x: Double ) {
return ( y: curveWindowY[0], x: curveWindowX[0] )
}
// 清空
public func reset() {
curveWindowY = [0.0, 0.0]
curveWindowX = [0.0, 0.0]
}

}

extension ScrollFilter {
// 曲线平滑
// 计算曲线窗口数组首位与 nextValue 的距离, 用定长的非线性数列填充以平滑曲线
// 例如: 数组首位为 1, nextValue 为 2, 则生成数列 [1.00, 1.23, 1.50, 1.77, 2.00]
private func polish(_ array: [Double], with nextValue: Double) -> [Double] {
let first = array[1]
let diff = nextValue - first
return [first, first+0.23*diff, first+0.5*diff, first+0.77*diff, nextValue]
}
}
2 changes: 1 addition & 1 deletion Mos/ScrollCore/ScrollPhase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class ScrollPhase {
}

// MARK: - 滚动数据附加
func attach(to event: CGEvent) {
func attachExtraData(to event: CGEvent) {
let prevPhase = ScrollPhase.shared.consume()
// 仅作用于 Y 轴事件 (For MX Master)
if event.getDoubleValueField(.scrollWheelEventPointDeltaAxis2) == 0.0 {
Expand Down
125 changes: 53 additions & 72 deletions Mos/ScrollCore/ScrollPoster.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@ class ScrollPoster {
init() { NSLog("Module initialized: ScrollPoster") }

// 插值器
private let filler = ScrollFiller()
private let interpolator = Interpolator.lerp
private let filter = ScrollFilter()
// 发送器
private var poster: CVDisplayLink?
// 滚动数据
private var scrollCurr = ( y: 0.0, x: 0.0 ) // 当前滚动距离
private var scrollDelta = ( y: 0.0, x: 0.0 ) // 滚动方向记录
private var scrollBuffer = ( y: 0.0, x: 0.0 ) // 滚动缓冲距离
private var current = (y: 0.0, x: 0.0) // 当前滚动距离
private var delta = (y: 0.0, x: 0.0) // 滚动方向记录
private var buffer = (y: 0.0, x: 0.0) // 滚动缓冲距离
// 滚动配置
private var shifting = false
private var duration = Options.shared.scrollAdvanced.durationTransition
Expand All @@ -39,27 +38,27 @@ extension ScrollPoster {
// 更新滚动配置
self.duration = duration
// 更新滚动数据
if y*scrollDelta.y > 0 {
scrollBuffer.y += y * speed * amplification
if y*delta.y > 0 {
buffer.y += y * speed * amplification
} else {
scrollBuffer.y = y * speed * amplification
scrollCurr.y = 0.0
buffer.y = y * speed * amplification
current.y = 0.0
}
if x*scrollDelta.x > 0 {
scrollBuffer.x += x * speed * amplification
if x*delta.x > 0 {
buffer.x += x * speed * amplification
} else {
scrollBuffer.x = x * speed * amplification
scrollCurr.x = 0.0
buffer.x = x * speed * amplification
current.x = 0.0
}
scrollDelta = ( y: y, x: x )
delta = (y: y, x: x)
return self
}
func updateShifting(enable: Bool) {
shifting = enable
}
func swap(with nextValue: ( y: Double, x: Double ), enable: Bool) -> (y: Double, x: Double) {
func shift(with nextValue: ( y: Double, x: Double )) -> (y: Double, x: Double) {
// 如果按下 Shift, 则始终将滚动转为横向
if enable {
if shifting {
// 判断哪个轴有值, 有值则赋给 X
// 某些鼠标 (MXMaster/MXAnywhere), 按下 Shift 后会显式转换方向为横向, 此处针对这类转换进行归一化处理
if nextValue.y != 0.0 && nextValue.x == 0.0 {
Expand All @@ -72,15 +71,16 @@ extension ScrollPoster {
}
}
func brake() {
ScrollPoster.shared.scrollBuffer = ScrollPoster.shared.scrollCurr
ScrollPoster.shared.buffer = ScrollPoster.shared.current
}
func reset() {
// 重置数值
scrollCurr = ( y: 0.0, x: 0.0 )
scrollDelta = ( y: 0.0, x: 0.0 )
scrollBuffer = ( y: 0.0, x: 0.0 )
ref = (event: nil, proxy: nil)
current = ( y: 0.0, x: 0.0 )
delta = ( y: 0.0, x: 0.0 )
buffer = ( y: 0.0, x: 0.0 )
// 重置插值器
filler.reset()
filter.reset()
}
}

Expand All @@ -91,94 +91,75 @@ extension ScrollPoster {
// 新建一个 CVDisplayLinkSetOutputCallback 来执行循环
CVDisplayLinkCreateWithActiveCGDisplays(&poster)
CVDisplayLinkSetOutputCallback(poster!, { (displayLink, inNow, inOutputTime, flagsIn, flagsOut, displayLinkContext) -> CVReturn in
ScrollPoster.shared.beforePost()
ScrollPoster.shared.processing()
return kCVReturnSuccess
}, nil)
}
// 启动事件发送器
func enable() {
func tryStart() {
if !CVDisplayLinkIsRunning(poster!) {
CVDisplayLinkStart(poster!)
}
}
// 暂停事件发送器
func pauseAuto() {
ScrollPhase.shared.phase = Phase.PauseAuto
reset()
}
func pauseManual() {
ScrollPhase.shared.phase = Phase.PauseManual
reset()
}
// 停止事件发送器
func disableAuto() {
pauseAuto()
func stop(_ phase: Phase = Phase.PauseManual) {
// 停止循环
if let validPoster = poster {
CVDisplayLinkStop(validPoster)
afterPost()
}
}
func disableManual() {
pauseManual()
if let validPoster = poster {
CVDisplayLinkStop(validPoster)
// 更新阶段
ScrollPhase.shared.phase = phase
// 对于 Phase.PauseAuto, 我们在结束前额外发送一个事件来重置 Chrome 的滚动缓冲区
if phase == Phase.PauseAuto {
post(ref, (y: 0.0, x: 0.0))
}
// 最后重置参数
reset()
}
}

// MARK: - 数据处理及发送
private extension ScrollPoster {
// 预处理滚动事件
func beforePost() {
// 处理滚动事件
func processing() {
// 计算插值
let scrollPulse = (
y: interpolator(scrollCurr.y, scrollBuffer.y, duration),
x: interpolator(scrollCurr.x, scrollBuffer.x, duration)
let frame = (
y: Interpolator.lerp(src: current.y, dest: buffer.y, trans: duration),
x: Interpolator.lerp(src: current.x, dest: buffer.x, trans: duration)
)
// 更新滚动位置
scrollCurr = (
y: scrollCurr.y + scrollPulse.y,
x: scrollCurr.x + scrollPulse.x
current = (
y: current.y + frame.y,
x: current.x + frame.x
)
// 平滑滚动结果
let filledValue = filler.fill(with: scrollPulse)
// 交换滚动结果
let swapedValue = swap(with: filledValue, enable: shifting)
let filledValue = filter.fill(with: frame)
// 变换滚动结果
let shiftedValue = shift(with: filledValue)
// 发送滚动结果
if let proxy = ref.proxy, let event = ref.event {
post(proxy, event, swapedValue.y, swapedValue.x)
}
post(ref, shiftedValue)
// 如果临近目标距离小于精确度门限则暂停滚动
if (
scrollPulse.y.magnitude <= Options.shared.scrollAdvanced.precision &&
scrollPulse.x.magnitude <= Options.shared.scrollAdvanced.precision
frame.y.magnitude <= Options.shared.scrollAdvanced.precision &&
frame.x.magnitude <= Options.shared.scrollAdvanced.precision
) {
disableAuto()
stop(Phase.PauseAuto)
}
}
// 发送滚动事件
func post(_ proxy: CGEventTapProxy, _ event: CGEvent, _ y: Double, _ x: Double) {
if let eventClone = event.copy() {
// 复制指针防止在 Post 过程中被释放
let proxyClone = proxy
func post(_ r: (event: CGEvent?, proxy: CGEventTapProxy?), _ v: (y: Double, x: Double)) {
if let proxy = r.proxy, let eventClone = r.event?.copy() {
// 设置阶段数据
ScrollPhase.shared.attach(to: eventClone)
ScrollPhase.shared.attachExtraData(to: eventClone)
// 设置滚动数据
eventClone.setDoubleValueField(.scrollWheelEventPointDeltaAxis1, value: y)
eventClone.setDoubleValueField(.scrollWheelEventPointDeltaAxis2, value: x)
eventClone.setDoubleValueField(.scrollWheelEventPointDeltaAxis1, value: v.y)
eventClone.setDoubleValueField(.scrollWheelEventPointDeltaAxis2, value: v.x)
eventClone.setDoubleValueField(.scrollWheelEventFixedPtDeltaAxis1, value: 0.0)
eventClone.setDoubleValueField(.scrollWheelEventFixedPtDeltaAxis2, value: 0.0)
eventClone.setDoubleValueField(.scrollWheelEventIsContinuous, value: 1.0)
// EventTapProxy 标识了 EventTapCallback 在事件流中接收到事件的特定位置, 其粒度小于 tap 本身
// 使用 tapPostEvent 可以将自定义的事件发布到 proxy 标识的位置, 避免被 EventTapCallback 本身重复接收或处理
// 新发布的事件将早于 EventTapCallback 所处理的事件进入系统, 也如同 EventTapCallback 所处理的事件, 会被所有后续的 EventTap 接收
eventClone.tapPostEvent(proxyClone)
}
}
// 后处理滚动事件
func afterPost() {
if let proxy = ref.proxy, let event = ref.event {
post(proxy, event, 0.0, 0.0)
eventClone.tapPostEvent(proxy)
}
}
}

0 comments on commit 3467d86

Please sign in to comment.