Objective c 如何欺骗OSX应用程序,使其认为鼠标是手指?
我正在编写一个包含集合视图的Mac应用程序。此应用程序将在大型触摸屏上运行(从平面)。由于硬件限制,触摸屏不会发送滚动事件(甚至任何多点触摸事件)我如何才能欺骗应用程序,使其认为“鼠标向下+拖动”与“鼠标滚动”相同? 通过子类化NSCollectionView并在其中实现我自己的nsPangestureRecogener处理程序,我让它工作了一半。不幸的是,结果是笨重的,没有正常OSX滚动的感觉(即滚动结束时的速度效果,或内容结束时的滚动反弹) 关于这个实现的一个注意事项:我把Objective c 如何欺骗OSX应用程序,使其认为鼠标是手指?,objective-c,macos,cocoa,Objective C,Macos,Cocoa,我正在编写一个包含集合视图的Mac应用程序。此应用程序将在大型触摸屏上运行(从平面)。由于硬件限制,触摸屏不会发送滚动事件(甚至任何多点触摸事件)我如何才能欺骗应用程序,使其认为“鼠标向下+拖动”与“鼠标滚动”相同? 通过子类化NSCollectionView并在其中实现我自己的nsPangestureRecogener处理程序,我让它工作了一半。不幸的是,结果是笨重的,没有正常OSX滚动的感觉(即滚动结束时的速度效果,或内容结束时的滚动反弹) 关于这个实现的一个注意事项:我把scrollSca
scrollScaling
和pointSmoother
放在一起,尝试改进滚动用户体验。我使用的触摸屏是基于红外的,而且非常不稳定(特别是当太阳出来的时候)
如果相关的话:我在Yosemite beta(14A329r)上使用Xcode 6 beta 6(6A280e),我的构建目标是10.10
谢谢 我有一个可以通过NSTableView
实现这一点的工具,所以希望它能在NSCollectionView
中很好地工作
免责声明:我在学习GCD时写了这篇文章,所以请注意保留周期。。。我没有检查我刚刚发布的内容是否有bug。请随意指出:)我刚刚在MacOS10.9上测试了它,它仍然可以工作(最初是为10.7IIRC编写的),而不是在10.10上测试
可以肯定,这整个过程是一个黑客行为,它看起来需要(似乎无论如何)异步UI操作(我认为是为了防止无限递归)。可能有一个更干净/更好的方法,请在发现时与我们分享
我已经好几个月没碰过这个了,所以我想不起所有的细节,但它的实质肯定是在NBBTableView
代码中,它将粘贴的代码片段
首先,有一个NSAnimation
子类nbbscrollmanimation
处理“橡皮筋”效果:
您应该能够在具有NSClipView
的任何控件上使用动画,方法是将其设置为\u scrollAnimation=[[NBBScrollAnimation scrollAnimationWithClipView:(NSClipView*)[self superview]]retain]代码>
这里的技巧是NSTableView
的超级视图是NSClipView
;我不知道NSCollectionView
,但我怀疑任何可滚动控件都使用NSClipView
下面是NBBTableView
子类如何通过鼠标事件使用该动画:
- (void)mouseDown:(NSEvent *)theEvent
{
_scrollDelta = 0.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (_scrollAnimation && _scrollAnimation.isAnimating) {
[_scrollAnimation stopAnimation];
}
});
}
- (void)mouseUp:(NSEvent *)theEvent
{
if (_scrollDelta) {
[super mouseUp:theEvent];
// reset the scroll animation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSClipView* cv = (NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([cv documentVisibleRect].origin.y - _scrollDelta));
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
[anim setCurrentProgress:0.0];
anim.targetPoint = newPoint;
[anim startAnimation];
});
} else {
[super mouseDown:theEvent];
}
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSClipView* clipView=(NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([clipView documentVisibleRect].origin.y - [theEvent deltaY]));
CGFloat limit = self.frame.size.height;
if (newPoint.y >= limit) {
newPoint.y = limit - 1.0;
} else if (newPoint.y <= limit * -1) {
newPoint.y = (limit * -1) + 1;
}
// do NOT constrain the point here. we want to "rubber band"
[clipView scrollToPoint:newPoint];
[[self enclosingScrollView] reflectScrolledClipView:clipView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
anim.originPoint = newPoint;
});
// because we have to animate asyncronously, we must save the target value to use later
// instead of setting it in the animation here
_scrollDelta = [theEvent deltaY] * 3.5;
}
- (BOOL)autoscroll:(NSEvent *)theEvent
{
return NO;
}
-(void)鼠标向下移动:(n事件*)事件
{
_滚动增量=0.0;
调度异步(调度获取全局队列(调度队列优先级高,0)^{
if(_scrollAnimation&&u scrollAnimation.isAnimating){
[滚动动画停止动画];
}
});
}
-(无效)鼠标点击:(n事件*)事件
{
如果(_){
[超级鼠标:theEvent];
//重置滚动动画
调度异步(调度获取全局队列(调度队列优先级高,0)^{
NSClipView*cv=(NSClipView*)[self superview];
NSPoint newPoint=NSMakePoint(0.0,([cv documentVisibleRect].origin.y-_scrollDelta));
NBBScrollAnimation*动画=(NBBScrollAnimation*)\u滚动动画;
[anim setCurrentProgress:0.0];
anim.targetPoint=新点;
[动画开始动画];
});
}否则{
[超级鼠标镇:theEvent];
}
}
-(无效)鼠标标记:(n事件*)事件
{
NSClipView*clipView=(NSClipView*)[自超视图];
NSPoint newPoint=NSMakePoint(0.0,([clipView documentVisibleRect].origin.y-[theEvent deltaY]);
CGFloat limit=自身框架尺寸高度;
如果(newPoint.y>=限制){
newPoint.y=限值-1.0;
}否则,如果(newPoint.y我成功地使用了NSPangestureRecognitor并模拟了轨迹板滚轮事件。如果你模拟得好,你将从NSScrollView“免费”获得反弹
我没有公共代码,但我找到的解释NSScrollView所期望的最好的资源是在下面模拟动量滚动的单元测试中(请参见mousescrollbywithwheels和momentumphases
)
mousescrollbywithwheel和momentumphases的实现为如何在低级别合成滚动事件提供了一些提示。我发现我需要的一个补充是在事件中实际设置一个递增的时间戳,以便让滚动视图玩球
最后,为了实际创建衰减速度,我使用了一个POPDecayAnimation
,并调整了NSPanGestureRecognizer
中的速度,使其感觉类似。这并不完美,但它确实与NSScrollView
的反弹保持一致。OSX多点触控API不支持事件注入,除非您做一些修改极其肮脏的东西(手工构建内部事件结构并将其放入HID事件流中…即使这样也不总是有效).我已经被这个问题困扰了好几次-我很想看到这个问题的答案。几年前,我用可可豆制作了多点触控事件,将它们转换为CGEvent,然后转换为碳事件(这需要反向工程如何用碳来表示触摸事件,因为通常它们根本不会出现在碳流中…)不幸的是,即使在较旧版本的OS X上尝试以64位编译该代码也会给我带来大量错误,因此我怀疑它不再有效。谢谢!我刚刚开始讨论这个问题。我注意到你有两个类NBBVirtualKeyboard和NBBKeyboardKeyCell。是吗ose放弃了,转而采用另一种键盘解决方案?它们似乎没有得到实现。@SpencerWi
@implementation NBBScrollAnimation
@synthesize clipView;
@synthesize originPoint;
@synthesize targetPoint;
+ (NBBScrollAnimation*)scrollAnimationWithClipView:(NSClipView *)clipView
{
NBBScrollAnimation *animation = [[NBBScrollAnimation alloc] initWithDuration:0.6 animationCurve:NSAnimationEaseOut];
animation.clipView = clipView;
animation.originPoint = clipView.documentVisibleRect.origin;
animation.targetPoint = animation.originPoint;
return [animation autorelease];
}
- (void)setCurrentProgress:(NSAnimationProgress)progress
{
typedef float (^MyAnimationCurveBlock)(float, float, float);
MyAnimationCurveBlock cubicEaseOut = ^ float (float t, float start, float end) {
t--;
return end*(t * t * t + 1) + start;
};
dispatch_sync(dispatch_get_main_queue(), ^{
NSPoint progressPoint = self.originPoint;
progressPoint.x += cubicEaseOut(progress, 0, self.targetPoint.x - self.originPoint.x);
progressPoint.y += cubicEaseOut(progress, 0, self.targetPoint.y - self.originPoint.y);
NSPoint constraint = [self.clipView constrainScrollPoint:progressPoint];
if (!NSEqualPoints(constraint, progressPoint)) {
// constraining the point and reassigning to target gives us the "rubber band" effect
self.targetPoint = constraint;
}
[self.clipView scrollToPoint:progressPoint];
[self.clipView.enclosingScrollView reflectScrolledClipView:self.clipView];
[self.clipView.enclosingScrollView displayIfNeeded];
});
}
@end
- (void)mouseDown:(NSEvent *)theEvent
{
_scrollDelta = 0.0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
if (_scrollAnimation && _scrollAnimation.isAnimating) {
[_scrollAnimation stopAnimation];
}
});
}
- (void)mouseUp:(NSEvent *)theEvent
{
if (_scrollDelta) {
[super mouseUp:theEvent];
// reset the scroll animation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSClipView* cv = (NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([cv documentVisibleRect].origin.y - _scrollDelta));
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
[anim setCurrentProgress:0.0];
anim.targetPoint = newPoint;
[anim startAnimation];
});
} else {
[super mouseDown:theEvent];
}
}
- (void)mouseDragged:(NSEvent *)theEvent
{
NSClipView* clipView=(NSClipView*)[self superview];
NSPoint newPoint = NSMakePoint(0.0, ([clipView documentVisibleRect].origin.y - [theEvent deltaY]));
CGFloat limit = self.frame.size.height;
if (newPoint.y >= limit) {
newPoint.y = limit - 1.0;
} else if (newPoint.y <= limit * -1) {
newPoint.y = (limit * -1) + 1;
}
// do NOT constrain the point here. we want to "rubber band"
[clipView scrollToPoint:newPoint];
[[self enclosingScrollView] reflectScrolledClipView:clipView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NBBScrollAnimation* anim = (NBBScrollAnimation*)_scrollAnimation;
anim.originPoint = newPoint;
});
// because we have to animate asyncronously, we must save the target value to use later
// instead of setting it in the animation here
_scrollDelta = [theEvent deltaY] * 3.5;
}
- (BOOL)autoscroll:(NSEvent *)theEvent
{
return NO;
}