From 4def07992625c4e2f057d3e189ca5b316462d8c7 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 11:33:40 -0400 Subject: [PATCH 01/12] Add skeletal implementation of NSGestureRecognizer --- Headers/AppKit/NSGestureRecognizer.h | 564 ++++++++++++++++++++++++++- Source/NSGestureRecognizer.m | 440 ++++++++++++++++++++- 2 files changed, 986 insertions(+), 18 deletions(-) diff --git a/Headers/AppKit/NSGestureRecognizer.h b/Headers/AppKit/NSGestureRecognizer.h index 6fbeff195d..d08ee588ee 100644 --- a/Headers/AppKit/NSGestureRecognizer.h +++ b/Headers/AppKit/NSGestureRecognizer.h @@ -1,6 +1,6 @@ /* NSGestureRecognizer.h - + Abstract base class for monitoring user events Copyright (C) 2017 Free Software Foundation, Inc. @@ -11,21 +11,21 @@ Date: Thu Dec 5 12:54:49 EST 2019 This file is part of the GNUstep GUI Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -43,12 +43,492 @@ extern "C" { #endif @class NSView, NSEvent; +@protocol NSGestureRecognizerDelegate; + +typedef NS_ENUM(NSInteger, NSGestureRecognizerState) { + NSGestureRecognizerStatePossible, + NSGestureRecognizerStateBegan, + NSGestureRecognizerStateChanged, + NSGestureRecognizerStateEnded, + NSGestureRecognizerStateCancelled, + NSGestureRecognizerStateFailed, + NSGestureRecognizerStateRecognized = NSGestureRecognizerStateEnded +}; APPKIT_EXPORT_CLASS -@interface NSGestureRecognizer : NSObject +/** + * NSGestureRecognizer is an abstract base class that provides the framework + * for recognizing user gestures from input events. This class defines the + * fundamental architecture for gesture recognition systems, enabling + * applications to respond to complex user interactions like taps, swipes, + * pinches, and rotations. Gesture recognizers maintain state machines that + * track gesture progress through various phases from possible to recognized, + * failed, or cancelled states. The class implements the target-action pattern + * for delivering gesture recognition results to application objects. Each + * gesture recognizer can be attached to a view and will monitor events + * delivered to that view, processing them according to the specific gesture + * recognition logic implemented by subclasses. The framework supports gesture + * dependencies, simultaneous recognition, and delegate-based customization + * of recognition behavior. Subclasses must implement specific event handling + * methods to define their gesture recognition algorithms while leveraging + * the base class infrastructure for state management and action delivery. + */ +@interface NSGestureRecognizer : NSObject +{ +@private + id _target; + SEL _action; + NSGestureRecognizerState _state; + NSView *_view; + id _delegate; + BOOL _enabled; + BOOL _delaysPrimaryMouseButtonEvents; + BOOL _delaysSecondaryMouseButtonEvents; + BOOL _delaysOtherMouseButtonEvents; + BOOL _delaysKeyEvents; + NSMutableArray *_failureRequirements; + NSEvent *_lastEvent; +} + +// Initializing a Gesture Recognizer +/** + * Initializes a new gesture recognizer with the specified target and action. + * The target parameter specifies the object that will receive action messages + * when the gesture is successfully recognized. The action parameter defines + * the selector method that will be called on the target object. This designated + * initializer establishes the target-action relationship that enables gesture + * recognition results to be delivered to application objects. The initialized + * gesture recognizer starts in the possible state and is enabled by default. + * Multiple target-action pairs can be added after initialization using the + * addTarget:action: method. The gesture recognizer maintains a weak reference + * to the target object to prevent retain cycles. If target is nil or action + * is NULL, no action will be performed when the gesture is recognized, but + * the recognizer will still track gesture state and can be monitored through + * its delegate or state property. + */ +- (instancetype)initWithTarget:(id)target action:(SEL)action; + +// Adding and Removing Targets and Actions +/** + * Adds a target-action pair to this gesture recognizer. The target parameter + * specifies the object that should receive action messages when the gesture + * is successfully recognized. The action parameter defines the selector method + * that will be invoked on the target object. Multiple target-action pairs + * can be associated with a single gesture recognizer, allowing the same + * gesture to trigger multiple responses simultaneously. The gesture recognizer + * maintains weak references to target objects to prevent retain cycles. When + * the gesture is recognized, all registered target-action pairs will be + * invoked in the order they were added. If either target or action is nil, + * the method has no effect. The action method should accept either no arguments + * or a single argument that will receive the gesture recognizer instance. + */ +- (void)addTarget:(id)target action:(SEL)action; +/** + * Removes a target-action pair from this gesture recognizer. The target + * parameter specifies the target object to remove, and the action parameter + * specifies the action selector to remove. If target matches a registered + * target and action either matches the registered action or is NULL, the + * target-action pair is removed from the recognizer. When action is NULL, + * all actions associated with the specified target are removed. This method + * enables dynamic modification of gesture response behavior during the + * application lifecycle. Removing a target-action pair prevents future + * action messages from being sent to that target when the gesture is + * recognized. If the specified target-action pair is not found, the method + * has no effect. + */ +- (void)removeTarget:(id)target action:(SEL)action; + +// Getting the Touches and Location of a Gesture +/** + * Returns the location of the gesture in the coordinate system of the + * specified view. The view parameter determines the coordinate system for + * the returned point. If view is nil, the method uses the gesture recognizer's + * associated view. If no view is associated, the method returns the location + * in window coordinates. The location represents the centroid of all active + * touch points involved in the gesture. For single-touch gestures, this is + * the location of the single touch. For multi-touch gestures, this is the + * average location of all touches. The returned point is calculated based + * on the most recent event processed by the gesture recognizer. This method + * provides a convenient way to determine where the gesture is occurring + * without needing to access individual touch locations. + */ - (NSPoint)locationInView:(NSView *)view; +/** + * Returns the location of a specific touch in the coordinate system of the + * specified view. The touchIndex parameter specifies which touch to query, + * with 0 representing the first touch, 1 the second touch, and so on. The + * view parameter determines the coordinate system for the returned point. + * If view is nil, the method uses the gesture recognizer's associated view. + * This method enables access to individual touch locations in multi-touch + * gestures where the overall gesture location may not provide sufficient + * detail. For single-touch gestures, only index 0 is valid. If touchIndex + * exceeds the number of active touches, the method returns NSZeroPoint. + * The returned location is based on the most recent event processed by + * the gesture recognizer. + */ +- (NSPoint)locationOfTouch:(NSUInteger)touchIndex inView:(NSView *)view; +/** + * Returns the number of touches currently involved in the gesture. For + * single-touch gestures like taps or swipes, this returns 1. For multi-touch + * gestures like pinches or rotations, this returns the number of simultaneous + * touches being tracked. The count reflects the number of active touches + * at the time of the most recent event processed by the gesture recognizer. + * During gesture recognition, the touch count may change as touches are + * added or removed. Subclasses can use this information to validate gesture + * requirements and adjust recognition behavior based on the number of + * concurrent touches. A return value of 0 indicates no active touches, + * which typically occurs before gesture recognition begins or after all + * touches have ended. + */ +- (NSUInteger)numberOfTouches; + +// Getting and Setting the Gesture Recognizer's State +@property(readonly) NSGestureRecognizerState state; + +// Enabling and Disabling a Gesture Recognizer +@property(getter=isEnabled) BOOL enabled; + +// Specifying Dependencies Between Gesture Recognizers +/** + * Establishes a failure dependency on another gesture recognizer. The + * otherGestureRecognizer parameter specifies the gesture recognizer that + * must fail before this gesture recognizer can succeed. This creates a + * recognition precedence where the other gesture recognizer is given the + * first opportunity to recognize its gesture. Only if the other recognizer + * fails to recognize its gesture will this recognizer be allowed to succeed. + * This mechanism enables complex gesture interactions where specific gestures + * take priority over more general ones. For example, a double-tap recognizer + * might require a single-tap recognizer to fail, ensuring that single taps + * are not recognized when the user intends a double tap. Multiple failure + * dependencies can be established, creating chains of recognition precedence. + * If otherGestureRecognizer is nil or already a dependency, the method has + * no effect. + */ +- (void)requireGestureRecognizerToFail:(NSGestureRecognizer *)otherGestureRecognizer; + +// Setting and Getting the Delegate +@property(weak) id delegate; + +// Getting the Gesture Recognizer's View +@property(readonly, weak) NSView *view; + +// Delaying Touches +@property BOOL delaysPrimaryMouseButtonEvents; +@property BOOL delaysSecondaryMouseButtonEvents; +@property BOOL delaysOtherMouseButtonEvents; +@property BOOL delaysKeyEvents; + +// Methods for Subclasses +/** + * Resets the gesture recognizer to its initial state. This method is called + * automatically by the framework when the gesture recognizer transitions + * to terminal states like ended, cancelled, or failed. Subclasses should + * override this method to reset any gesture-specific state variables, + * accumulated values, or tracking data to their initial conditions. The + * base implementation clears internal event tracking and prepares the + * recognizer for processing new gesture sequences. Custom implementations + * should call the super implementation to ensure proper base class cleanup. + * This method enables gesture recognizers to be reused for multiple gesture + * recognition cycles without creating new instances. The reset occurs after + * action messages have been sent, allowing action handlers to access final + * gesture state before cleanup. + */ +- (void)reset; +/** + * Instructs the gesture recognizer to ignore a specific event. The event + * parameter specifies the event that should be excluded from gesture + * recognition processing. This method provides a way for subclasses to + * filter out events that are not relevant to their specific gesture type + * or that occur under conditions where recognition should not proceed. + * Ignored events are not processed by the normal event handling methods + * and do not contribute to gesture state transitions. This selective + * event filtering can improve recognition accuracy and prevent unwanted + * gesture triggers. The base implementation provides a placeholder that + * subclasses can override to implement custom event filtering logic. + * Events that are ignored do not affect the gesture recognizer's state + * or trigger delegate method calls. + */ +- (void)ignoreEvent:(NSEvent *)event; +/** + * Determines whether this gesture recognizer can prevent another gesture + * recognizer from succeeding. The preventedGestureRecognizer parameter + * specifies the gesture recognizer that might be prevented from recognizing + * its gesture. This method enables custom gesture interaction policies + * where certain gestures take precedence over others. The default + * implementation returns YES, allowing this recognizer to prevent any other + * recognizer. Subclasses can override this method to implement more + * sophisticated interaction rules based on gesture types, view hierarchies, + * or application-specific requirements. When this method returns NO, both + * gesture recognizers may succeed simultaneously if their delegate methods + * also permit simultaneous recognition. This method is called during gesture + * recognition to resolve conflicts between competing recognizers. + */ +- (BOOL)canPreventGestureRecognizer:(NSGestureRecognizer *)preventedGestureRecognizer; +/** + * Determines whether this gesture recognizer can be prevented from succeeding + * by another gesture recognizer. The preventingGestureRecognizer parameter + * specifies the gesture recognizer that might prevent this one from + * recognizing its gesture. This method enables custom gesture interaction + * policies where certain recognizers can be overridden by others. The + * default implementation returns YES, allowing any other recognizer to + * prevent this one. Subclasses can override this method to implement + * resistance to prevention based on gesture priority, specificity, or + * application requirements. When this method returns NO, this recognizer + * cannot be prevented by the specified preventing recognizer. This method + * works in conjunction with canPreventGestureRecognizer: to establish + * complex gesture hierarchies and interaction patterns. + */ +- (BOOL)canBePreventedByGestureRecognizer:(NSGestureRecognizer *)preventingGestureRecognizer; + +// Event-Handling Methods +/** + * Handles mouse down events for gesture recognition. The event parameter + * contains information about the mouse press including location, timestamp, + * and button state. This method is called when a mouse button is pressed + * within the gesture recognizer's associated view. Subclasses override this + * method to implement specific gesture recognition logic for mouse press + * events. The base implementation stores the event for location tracking + * and consults the delegate to determine whether recognition should proceed. + * Mouse down events typically mark the beginning of gesture recognition + * sequences. Subclasses should analyze event properties like click count, + * location, and timing to determine appropriate state transitions. The + * method should update the gesture recognizer's state based on recognition + * progress and gesture completion criteria. + */ +- (void)mouseDown:(NSEvent *)event; +/** + * Handles mouse drag events for gesture recognition. The event parameter + * contains information about the mouse movement including location, delta + * values, and timestamp. This method is called when the mouse is moved + * while a button is pressed within the gesture recognizer's associated + * view. Subclasses override this method to track gesture progression + * through continuous mouse movements. The base implementation stores the + * event for location tracking. Mouse drag events are crucial for gestures + * that involve movement like swipes, pans, or drags. Subclasses should + * analyze movement distance, direction, velocity, and patterns to determine + * whether the movement matches their specific gesture criteria and update + * state accordingly. + */ +- (void)mouseDragged:(NSEvent *)event; +/** + * Handles mouse up events for gesture recognition. The event parameter + * contains information about the mouse release including final location + * and timing. This method is called when a mouse button is released + * within the gesture recognizer's associated view. Subclasses override + * this method to complete gesture recognition sequences that began with + * mouse down events. The base implementation stores the event for location + * tracking. Mouse up events typically mark the end of gesture recognition + * sequences and often trigger final recognition decisions. Subclasses + * should analyze the complete gesture sequence including duration, movement + * patterns, and end conditions to determine whether recognition criteria + * have been met and transition to appropriate terminal states. + */ +- (void)mouseUp:(NSEvent *)event; +/** + * Handles right mouse down events for gesture recognition. The event + * parameter contains information about the right mouse button press. + * This method is called when the right mouse button is pressed within + * the gesture recognizer's associated view. Subclasses override this + * method to implement gesture recognition logic specific to right mouse + * button interactions. The base implementation stores the event for + * location tracking. Right mouse events can trigger different gesture + * types than left mouse events, such as context menu gestures or + * alternative interaction modes. Subclasses should distinguish between + * different mouse buttons when implementing multi-button gesture support + * and apply appropriate recognition logic based on button-specific + * gesture requirements. + */ +- (void)rightMouseDown:(NSEvent *)event; +/** + * Handles right mouse drag events for gesture recognition. The event + * parameter contains information about right mouse button drag movements. + * This method is called when the mouse is moved while the right button + * is pressed within the gesture recognizer's associated view. Subclasses + * override this method to track gesture progression through right mouse + * movements. The base implementation stores the event for location tracking. + * Right mouse drag events enable gesture types that are distinct from + * left mouse drags, providing additional gesture vocabulary for applications. + * Subclasses should implement button-specific movement analysis and apply + * recognition criteria appropriate to right mouse button gesture semantics. + */ +- (void)rightMouseDragged:(NSEvent *)event; +/** + * Handles right mouse up events for gesture recognition. The event parameter + * contains information about the right mouse button release. This method + * is called when the right mouse button is released within the gesture + * recognizer's associated view. Subclasses override this method to complete + * right mouse button gesture sequences. The base implementation stores + * the event for location tracking. Right mouse up events typically conclude + * right mouse button gesture recognition and may trigger different actions + * than left mouse button gestures. Subclasses should implement button- + * specific completion logic and transition to appropriate terminal states + * based on right mouse button gesture criteria. + */ +- (void)rightMouseUp:(NSEvent *)event; +/** + * Handles other mouse button down events for gesture recognition. The + * event parameter contains information about additional mouse button + * presses beyond left and right buttons. This method is called when + * mouse buttons other than left or right are pressed within the gesture + * recognizer's associated view. Subclasses override this method to support + * gesture recognition using additional mouse buttons like middle buttons + * or extended button sets on multi-button mice. The base implementation + * stores the event for location tracking. Other mouse button events + * expand the gesture vocabulary available to applications and enable + * specialized interaction modes based on extended mouse hardware capabilities. + */ +- (void)otherMouseDown:(NSEvent *)event; +/** + * Handles other mouse button drag events for gesture recognition. The + * event parameter contains information about drag movements with additional + * mouse buttons pressed. This method is called when the mouse is moved + * while buttons other than left or right are pressed within the gesture + * recognizer's associated view. Subclasses override this method to track + * gesture progression through extended mouse button movements. The base + * implementation stores the event for location tracking. Other mouse + * button drag events enable specialized gesture types that utilize the + * full capabilities of multi-button input devices and provide additional + * interaction possibilities for sophisticated applications. + */ +- (void)otherMouseDragged:(NSEvent *)event; +/** + * Handles other mouse button up events for gesture recognition. The event + * parameter contains information about additional mouse button releases. + * This method is called when mouse buttons other than left or right are + * released within the gesture recognizer's associated view. Subclasses + * override this method to complete extended mouse button gesture sequences. + * The base implementation stores the event for location tracking. Other + * mouse button up events conclude gesture recognition sequences that + * began with other mouse button down events and enable completion of + * specialized gesture types that leverage extended mouse button capabilities + * for enhanced user interaction patterns. + */ +- (void)otherMouseUp:(NSEvent *)event; +/** + * Handles key down events for gesture recognition. The event parameter + * contains information about key presses including key codes, character + * values, and modifier flags. This method is called when keys are pressed + * while the gesture recognizer's associated view has keyboard focus. + * Subclasses override this method to implement gesture recognition that + * incorporates keyboard input. The base implementation stores the event + * for tracking. Key events can be part of composite gestures that combine + * keyboard and mouse input, or can trigger keyboard-only gesture sequences. + * Subclasses should analyze key codes, modifiers, and timing to implement + * keyboard-based gesture recognition appropriate to their specific + * gesture requirements. + */ +- (void)keyDown:(NSEvent *)event; +/** + * Handles key up events for gesture recognition. The event parameter + * contains information about key releases including key codes and timing. + * This method is called when keys are released while the gesture + * recognizer's associated view has keyboard focus. Subclasses override + * this method to complete keyboard-based gesture recognition sequences. + * The base implementation stores the event for tracking. Key up events + * mark the completion of key press sequences and enable gesture recognizers + * to analyze complete keyboard input patterns including key press duration + * and release timing. Subclasses should implement key release handling + * appropriate to their keyboard gesture recognition requirements. + */ +- (void)keyUp:(NSEvent *)event; +/** + * Handles modifier flag change events for gesture recognition. The event + * parameter contains information about changes to modifier key states + * including shift, control, command, and option keys. This method is + * called when modifier key states change while the gesture recognizer's + * associated view is active. Subclasses override this method to implement + * gesture recognition that responds to modifier key combinations. The + * base implementation stores the event for tracking. Flag change events + * enable gesture recognition based on modifier key sequences and + * combinations, supporting complex keyboard-based gesture vocabularies. + * Subclasses should analyze modifier flag transitions to implement + * appropriate gesture state changes. + */ +- (void)flagsChanged:(NSEvent *)event; +/** + * Handles tablet input events for gesture recognition. The event parameter + * contains information about tablet pen or stylus input including pressure, + * tilt, and position data. This method is called when tablet input occurs + * within the gesture recognizer's associated view. Subclasses override + * this method to implement gesture recognition that utilizes advanced + * tablet input capabilities. The base implementation stores the event + * for tracking. Tablet events provide rich input data that enables + * sophisticated gesture recognition based on pressure sensitivity, tilt + * angles, and precise positioning. Subclasses should analyze tablet- + * specific properties to implement gesture recognition appropriate to + * stylus-based interaction patterns. + */ +- (void)tabletPoint:(NSEvent *)event; +/** + * Handles magnification gesture events for gesture recognition. The event + * parameter contains information about pinch-to-zoom gestures including + * magnification factors and center points. This method is called when + * magnification gestures are detected by the system's built-in gesture + * recognition. Subclasses override this method to implement custom + * responses to magnification gestures or to combine magnification with + * other gesture types. The base implementation stores the event for + * tracking. Magnification events provide high-level gesture information + * that can be used directly or combined with other event types to create + * composite gesture recognition behavior. + */ +- (void)magnifyWithEvent:(NSEvent *)event; +/** + * Handles rotation gesture events for gesture recognition. The event + * parameter contains information about rotation gestures including rotation + * angles and center points. This method is called when rotation gestures + * are detected by the system's built-in gesture recognition. Subclasses + * override this method to implement custom responses to rotation gestures + * or to combine rotation with other gesture types. The base implementation + * stores the event for tracking. Rotation events provide processed gesture + * data that can be used to implement rotation-based interactions or + * combined with other events to create complex multi-touch gesture + * recognition patterns. + */ +- (void)rotateWithEvent:(NSEvent *)event; +/** + * Handles swipe gesture events for gesture recognition. The event parameter + * contains information about swipe gestures including direction and velocity. + * This method is called when swipe gestures are detected by the system's + * built-in gesture recognition. Subclasses override this method to implement + * custom responses to swipe gestures or to combine swipes with other + * gesture types. The base implementation stores the event for tracking. + * Swipe events provide directional gesture information that enables + * navigation and transitional interfaces. Subclasses should analyze + * swipe direction and characteristics to implement appropriate gesture + * responses and state transitions. + */ +- (void)swipeWithEvent:(NSEvent *)event; +/** + * Handles scroll wheel events for gesture recognition. The event parameter + * contains information about scroll wheel input including scroll deltas + * and direction. This method is called when scroll wheel events occur + * within the gesture recognizer's associated view. Subclasses override + * this method to implement gesture recognition that incorporates scroll + * wheel input. The base implementation stores the event for tracking. + * Scroll wheel events can be part of composite gestures or can trigger + * scroll-based gesture sequences. Subclasses should analyze scroll + * direction, magnitude, and timing to implement scroll wheel gesture + * recognition appropriate to their specific requirements. + */ +- (void)scrollWheel:(NSEvent *)event; + @end +/** + * The NSGestureRecognizerDelegate protocol defines methods that customize + * gesture recognition behavior and enable fine-grained control over gesture + * recognizer interactions. Delegates can influence when gesture recognition + * begins, whether gestures should be recognized simultaneously, and how + * gesture recognizers interact with each other and with the event system. + * This protocol enables application-specific gesture recognition policies + * that go beyond the default behavior provided by gesture recognizers. + * Delegate methods are called at key points during the gesture recognition + * process, allowing applications to implement custom logic for gesture + * coordination, conflict resolution, and recognition customization. The + * delegate pattern provides a powerful mechanism for adapting gesture + * recognition to specific application requirements without subclassing + * gesture recognizer classes. + */ @protocol NSGestureRecognizerDelegate #if GS_PROTOCOLS_HAVE_OPTIONAL @optional @@ -56,13 +536,83 @@ APPKIT_EXPORT_CLASS @end @interface NSObject (NSGestureRecognizerDelegate) #endif +/** + * Asks the delegate whether the gesture recognizer should attempt to + * recognize its gesture when the specified event is received. The + * gestureRecognizer parameter is the recognizer requesting permission + * to process the event, and the event parameter is the event that + * triggered the recognition attempt. This method is called before the + * gesture recognizer processes events and enables the delegate to + * selectively filter events based on application state, event properties, + * or other contextual factors. Returning NO prevents the gesture recognizer + * from processing the event and may cause it to fail. Returning YES + * allows normal event processing to proceed. This method provides + * event-level control over gesture recognition and enables dynamic + * gesture recognition policies based on runtime conditions. + */ - (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldAttemptToRecognizeWithEvent:(NSEvent *)event; +/** + * Asks the delegate whether the gesture recognizer should begin recognizing + * its gesture. The gestureRecognizer parameter is the recognizer that is + * about to begin recognition. This method is called when the recognizer + * has detected initial conditions that might lead to gesture recognition + * but before committing to the recognition process. Returning NO prevents + * the gesture recognizer from transitioning out of the possible state + * and effectively cancels recognition. Returning YES allows recognition + * to proceed normally. This method provides high-level control over + * when gesture recognition can begin and enables delegates to implement + * application-specific recognition policies based on current state or + * context. The method is not called for every event but only at the + * beginning of potential recognition sequences. + */ - (BOOL)gestureRecognizerShouldBegin:(NSGestureRecognizer *)gestureRecognizer; +/** + * Asks the delegate whether two gesture recognizers should be allowed to + * recognize their gestures simultaneously. The gestureRecognizer parameter + * is one of the recognizers, and otherGestureRecognizer is the other + * recognizer that might recognize simultaneously. This method enables + * applications to define custom simultaneous recognition policies that + * go beyond the default mutual exclusion behavior. Returning YES allows + * both recognizers to succeed at the same time, while returning NO + * enforces mutual exclusion. This method is called for both recognizers + * in the pair, and both must return YES for simultaneous recognition + * to occur. Simultaneous recognition enables complex multi-gesture + * interactions where multiple gesture types can be active at the same + * time, such as pinch-to-zoom combined with rotation. + */ - (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(NSGestureRecognizer *)otherGestureRecognizer; +/** + * Asks the delegate whether one gesture recognizer should require another + * to fail before it can succeed. The gestureRecognizer parameter is the + * recognizer that would wait for failure, and otherGestureRecognizer is + * the recognizer that must fail first. This method enables dynamic + * establishment of recognition dependencies based on runtime conditions. + * Returning YES creates a failure dependency similar to calling + * requireGestureRecognizerToFail: but allows the dependency to be + * determined dynamically. Returning NO prevents the dependency from + * being established. This method provides delegate-controlled recognition + * precedence that can adapt to changing application state or user + * interaction patterns. Dynamic dependencies enable flexible gesture + * hierarchies that respond to contextual factors. + */ - (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldRequireFailureOfGestureRecognizer:(NSGestureRecognizer *)otherGestureRecognizer; +/** + * Asks the delegate whether one gesture recognizer should be required + * to fail by another gesture recognizer. The gestureRecognizer parameter + * is the recognizer that might be required to fail, and + * otherGestureRecognizer is the recognizer that would wait for this + * failure. This method enables dynamic establishment of recognition + * dependencies from the perspective of the recognizer that would fail + * first. Returning YES allows the dependency to be created, while + * returning NO prevents it. This method complements + * shouldRequireFailureOfGestureRecognizer: by providing control from + * both sides of the dependency relationship. Dynamic failure requirements + * enable sophisticated gesture interaction patterns that adapt to + * application context and user behavior patterns. + */ - (BOOL)gestureRecognizer:(NSGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(NSGestureRecognizer *)otherGestureRecognizer; @end diff --git a/Source/NSGestureRecognizer.m b/Source/NSGestureRecognizer.m index d699ffa393..de418855b8 100644 --- a/Source/NSGestureRecognizer.m +++ b/Source/NSGestureRecognizer.m @@ -1,6 +1,6 @@ /* NSGestureRecognizer.m - + Abstract base class for monitoring user events Copyright (C) 2017 Free Software Foundation, Inc. @@ -9,40 +9,458 @@ Date: 2017 This file is part of the GNUstep GUI Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import +#import +#import +#import +#import +#import + +@interface NSGestureRecognizer () +- (void)_setState:(NSGestureRecognizerState)state; +- (void)_callDelegateWithSelector:(SEL)selector withObject:(id)object; +@end @implementation NSGestureRecognizer -- (id)initWithCoder: (NSCoder *)coder + +#pragma mark - Class Methods + ++ (void)initialize +{ + if (self == [NSGestureRecognizer class]) + { + [self setVersion: 1]; + } +} + +#pragma mark - Initialization + +- (instancetype)init +{ + return [self initWithTarget:nil action:NULL]; +} + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super init]; + if (self) + { + _target = target; + _action = action; + _state = NSGestureRecognizerStatePossible; + _enabled = YES; + _delaysPrimaryMouseButtonEvents = YES; + _delaysSecondaryMouseButtonEvents = YES; + _delaysOtherMouseButtonEvents = YES; + _delaysKeyEvents = NO; + _failureRequirements = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)dealloc +{ + RELEASE(_failureRequirements); + RELEASE(_lastEvent); + [super dealloc]; +} + +#pragma mark - Target-Action Management + +- (void)addTarget:(id)target action:(SEL)action +{ + if (!target || !action) + return; + + // For simplicity, this basic implementation only supports one target-action pair + // A full implementation would maintain multiple target-action pairs + _target = target; + _action = action; +} + +- (void)removeTarget:(id)target action:(SEL)action +{ + if (target == _target && (action == NULL || action == _action)) + { + _target = nil; + _action = NULL; + } +} + +#pragma mark - State Management + +- (NSGestureRecognizerState)state +{ + return _state; +} + +- (void)_setState:(NSGestureRecognizerState)state +{ + if (_state == state) + return; + + NSGestureRecognizerState oldState = _state; + _state = state; + + // Trigger action if appropriate + if (state == NSGestureRecognizerStateRecognized && _target && _action) + { + [_target performSelector:_action withObject:self]; + } + + // Reset state after terminal states + if (state == NSGestureRecognizerStateEnded || + state == NSGestureRecognizerStateCancelled || + state == NSGestureRecognizerStateFailed) + { + [self reset]; + _state = NSGestureRecognizerStatePossible; + } +} + +#pragma mark - Properties + +- (BOOL)isEnabled +{ + return _enabled; +} + +- (void)setEnabled:(BOOL)enabled +{ + if (_enabled == enabled) + return; + + _enabled = enabled; + + if (!enabled) + { + [self _setState:NSGestureRecognizerStateFailed]; + } +} + +- (id)delegate +{ + return _delegate; +} + +- (void)setDelegate:(id)delegate +{ + _delegate = delegate; +} + +- (NSView *)view +{ + return _view; +} + +- (BOOL)delaysPrimaryMouseButtonEvents +{ + return _delaysPrimaryMouseButtonEvents; +} + +- (void)setDelaysPrimaryMouseButtonEvents:(BOOL)delays +{ + _delaysPrimaryMouseButtonEvents = delays; +} + +- (BOOL)delaysSecondaryMouseButtonEvents +{ + return _delaysSecondaryMouseButtonEvents; +} + +- (void)setDelaysSecondaryMouseButtonEvents:(BOOL)delays { - return nil; + _delaysSecondaryMouseButtonEvents = delays; } -- (void)encodeWithCoder: (NSCoder *)coder +- (BOOL)delaysOtherMouseButtonEvents { - return; + return _delaysOtherMouseButtonEvents; } +- (void)setDelaysOtherMouseButtonEvents:(BOOL)delays +{ + _delaysOtherMouseButtonEvents = delays; +} + +- (BOOL)delaysKeyEvents +{ + return _delaysKeyEvents; +} + +- (void)setDelaysKeyEvents:(BOOL)delays +{ + _delaysKeyEvents = delays; +} + +#pragma mark - Location and Touch Information + - (NSPoint)locationInView:(NSView *)view { - return NSZeroPoint; + if (!_lastEvent) + return NSZeroPoint; + + if (!view) + view = _view; + + if (!view) + return [_lastEvent locationInWindow]; + + return [view convertPoint:[_lastEvent locationInWindow] fromView:nil]; +} + +- (NSPoint)locationOfTouch:(NSUInteger)touchIndex inView:(NSView *)view +{ + // Base implementation assumes single touch + if (touchIndex != 0) + return NSZeroPoint; + + return [self locationInView:view]; } + +- (NSUInteger)numberOfTouches +{ + // Base implementation assumes single touch + return _lastEvent ? 1 : 0; +} + +#pragma mark - Gesture Dependencies + +- (void)requireGestureRecognizerToFail:(NSGestureRecognizer *)otherGestureRecognizer +{ + if (!otherGestureRecognizer || [_failureRequirements containsObject:otherGestureRecognizer]) + return; + + [_failureRequirements addObject:otherGestureRecognizer]; +} + +#pragma mark - Subclass Methods + +- (void)reset +{ + // Subclasses should override to reset their specific state + ASSIGN(_lastEvent, nil); +} + +- (void)ignoreEvent:(NSEvent *)event +{ + // Subclasses can override to ignore specific events +} + +- (BOOL)canPreventGestureRecognizer:(NSGestureRecognizer *)preventedGestureRecognizer +{ + // Subclasses can override for custom prevention logic + return YES; +} + +- (BOOL)canBePreventedByGestureRecognizer:(NSGestureRecognizer *)preventingGestureRecognizer +{ + // Subclasses can override for custom prevention logic + return YES; +} + +#pragma mark - Event Handling + +- (void)mouseDown:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); + + // Check delegate before beginning recognition + if (_delegate && [_delegate respondsToSelector:@selector(gestureRecognizerShouldBegin:)]) + { + if (![_delegate gestureRecognizerShouldBegin:self]) + { + [self _setState:NSGestureRecognizerStateFailed]; + return; + } + } + + if (_delegate && [_delegate respondsToSelector:@selector(gestureRecognizer:shouldAttemptToRecognizeWithEvent:)]) + { + if (![_delegate gestureRecognizer:self shouldAttemptToRecognizeWithEvent:event]) + { + [self _setState:NSGestureRecognizerStateFailed]; + return; + } + } +} + +- (void)mouseDragged:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)mouseUp:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)rightMouseDown:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)rightMouseUp:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)otherMouseDown:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)otherMouseUp:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)keyDown:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)keyUp:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)flagsChanged:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)tabletPoint:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)magnifyWithEvent:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)rotateWithEvent:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +- (void)scrollWheel:(NSEvent *)event +{ + ASSIGN(_lastEvent, event); +} + +#pragma mark - Private Helper Methods + +- (void)_callDelegateWithSelector:(SEL)selector withObject:(id)object +{ + if (_delegate && [_delegate respondsToSelector:selector]) + { + [_delegate performSelector:selector withObject:self withObject:object]; + } +} + +#pragma mark - NSCoding + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super init]; + if (self) + { + if ([coder allowsKeyedCoding]) + { + _enabled = [coder decodeBoolForKey:@"NSEnabled"]; + _delaysPrimaryMouseButtonEvents = [coder decodeBoolForKey:@"NSDelaysPrimaryMouseButtonEvents"]; + _delaysSecondaryMouseButtonEvents = [coder decodeBoolForKey:@"NSDelaysSecondaryMouseButtonEvents"]; + _delaysOtherMouseButtonEvents = [coder decodeBoolForKey:@"NSDelaysOtherMouseButtonEvents"]; + _delaysKeyEvents = [coder decodeBoolForKey:@"NSDelaysKeyEvents"]; + + if ([coder containsValueForKey:@"NSTarget"]) + _target = [coder decodeObjectForKey:@"NSTarget"]; + + if ([coder containsValueForKey:@"NSAction"]) + { + NSString *actionString = [coder decodeObjectForKey:@"NSAction"]; + _action = NSSelectorFromString(actionString); + } + } + else + { + [coder decodeValueOfObjCType:@encode(BOOL) at:&_enabled]; + [coder decodeValueOfObjCType:@encode(BOOL) at:&_delaysPrimaryMouseButtonEvents]; + [coder decodeValueOfObjCType:@encode(BOOL) at:&_delaysSecondaryMouseButtonEvents]; + [coder decodeValueOfObjCType:@encode(BOOL) at:&_delaysOtherMouseButtonEvents]; + [coder decodeValueOfObjCType:@encode(BOOL) at:&_delaysKeyEvents]; + _target = RETAIN([coder decodeObject]); + [coder decodeValueOfObjCType:@encode(SEL) at:&_action]; + } + + _state = NSGestureRecognizerStatePossible; + _failureRequirements = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + if ([coder allowsKeyedCoding]) + { + [coder encodeBool:_enabled forKey:@"NSEnabled"]; + [coder encodeBool:_delaysPrimaryMouseButtonEvents forKey:@"NSDelaysPrimaryMouseButtonEvents"]; + [coder encodeBool:_delaysSecondaryMouseButtonEvents forKey:@"NSDelaysSecondaryMouseButtonEvents"]; + [coder encodeBool:_delaysOtherMouseButtonEvents forKey:@"NSDelaysOtherMouseButtonEvents"]; + [coder encodeBool:_delaysKeyEvents forKey:@"NSDelaysKeyEvents"]; + + if (_target) + [coder encodeObject:_target forKey:@"NSTarget"]; + + if (_action) + [coder encodeObject:NSStringFromSelector(_action) forKey:@"NSAction"]; + } + else + { + [coder encodeValueOfObjCType:@encode(BOOL) at:&_enabled]; + [coder encodeValueOfObjCType:@encode(BOOL) at:&_delaysPrimaryMouseButtonEvents]; + [coder encodeValueOfObjCType:@encode(BOOL) at:&_delaysSecondaryMouseButtonEvents]; + [coder encodeValueOfObjCType:@encode(BOOL) at:&_delaysOtherMouseButtonEvents]; + [coder encodeValueOfObjCType:@encode(BOOL) at:&_delaysKeyEvents]; + [coder encodeObject:_target]; + [coder encodeValueOfObjCType:@encode(SEL) at:&_action]; + } +} + @end From a44f19d50b2fb75a6f319649e77de059cf3c947d Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 11:47:58 -0400 Subject: [PATCH 02/12] Update to fix error --- Headers/AppKit/NSGestureRecognizer.h | 26 +- Headers/AppKit/NSPressGestureRecognizer.h | 102 ++++- Source/NSPressGestureRecognizer.m | 471 +++++++++++++++++++++- 3 files changed, 581 insertions(+), 18 deletions(-) diff --git a/Headers/AppKit/NSGestureRecognizer.h b/Headers/AppKit/NSGestureRecognizer.h index d08ee588ee..286a1f1e5a 100644 --- a/Headers/AppKit/NSGestureRecognizer.h +++ b/Headers/AppKit/NSGestureRecognizer.h @@ -35,6 +35,7 @@ #import #import +#import #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) @@ -183,10 +184,11 @@ APPKIT_EXPORT_CLASS - (NSUInteger)numberOfTouches; // Getting and Setting the Gesture Recognizer's State -@property(readonly) NSGestureRecognizerState state; +- (NSGestureRecognizerState) state; // Enabling and Disabling a Gesture Recognizer -@property(getter=isEnabled) BOOL enabled; +- (BOOL) isEnabled; +- (void) setEnabled: (BOOL)enabled; // Specifying Dependencies Between Gesture Recognizers /** @@ -207,16 +209,24 @@ APPKIT_EXPORT_CLASS - (void)requireGestureRecognizerToFail:(NSGestureRecognizer *)otherGestureRecognizer; // Setting and Getting the Delegate -@property(weak) id delegate; +- (id) delegate; +- (void) setDelegate: (id)delegate; // Getting the Gesture Recognizer's View -@property(readonly, weak) NSView *view; +- (NSView *) view; // Delaying Touches -@property BOOL delaysPrimaryMouseButtonEvents; -@property BOOL delaysSecondaryMouseButtonEvents; -@property BOOL delaysOtherMouseButtonEvents; -@property BOOL delaysKeyEvents; +- (BOOL) delaysPrimaryMouseButtonEvents; +- (void) setDelaysPrimaryMouseButtonEvents: (BOOL)delays; + +- (BOOL) delaysSecondaryMouseButtonEvents; +- (void) setDelaysSecondaryMouseButtonEvents: (BOOL)delays; + +- (BOOL) delaysOtherMouseButtonEvents; +- (void) setDelaysOtherMouseButtonEvents: (BOOL)delays; + +- (BOOL) delaysKeyEvents; +- (void) setDelaysKeyEvents: (BOOL)delays; // Methods for Subclasses /** diff --git a/Headers/AppKit/NSPressGestureRecognizer.h b/Headers/AppKit/NSPressGestureRecognizer.h index b4c61c4fbf..a4698cad36 100644 --- a/Headers/AppKit/NSPressGestureRecognizer.h +++ b/Headers/AppKit/NSPressGestureRecognizer.h @@ -1,25 +1,25 @@ /* Definition of class NSPressGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -35,7 +35,97 @@ extern "C" { #endif APPKIT_EXPORT_CLASS +/** + * NSPressGestureRecognizer is a concrete subclass of NSGestureRecognizer + * that recognizes press and hold gestures. A press gesture occurs when the + * user presses down on the view and holds for a specified minimum duration + * without moving beyond an allowable movement threshold. This recognizer is + * commonly used to trigger contextual menus, begin drag operations, or + * activate special editing modes in user interface elements. + * + * The recognizer monitors mouse down events and starts an internal timer + * when a press begins. If the user maintains the press for the minimum + * duration without moving beyond the allowable movement threshold, the + * gesture is recognized and transitions to the began state. The gesture + * continues in the changed state as long as the press is maintained within + * the movement bounds, and transitions to ended when the user releases. + * + * Key configuration properties include minimumPressDuration which defaults + * to 0.5 seconds, allowableMovement which defaults to 10 points, and + * numberOfTouchesRequired which defaults to 1. The recognizer fails if + * the user moves beyond the allowable distance or releases before the + * minimum duration is reached. + */ @interface NSPressGestureRecognizer : NSGestureRecognizer +{ +@private + NSTimeInterval _minimumPressDuration; + CGFloat _allowableMovement; + NSUInteger _numberOfTouchesRequired; + NSTimer *_pressTimer; + NSPoint _initialLocation; + BOOL _pressDetected; +} + +// Configuring the Press Gesture +/** + * Returns the minimum duration in seconds that a press must be held + * before the gesture is recognized. This value determines how long + * the user must maintain contact with the view before the press + * gesture transitions from the possible state to the began state. + * The default value is 0.5 seconds, providing a balance between + * responsiveness and preventing accidental activations. + */ +- (NSTimeInterval) minimumPressDuration; + +/** + * Sets the minimum duration in seconds that a press must be held + * before the gesture is recognized. Values less than 0 are clamped + * to 0. Setting a shorter duration makes the gesture more responsive + * but may increase accidental activations. Setting a longer duration + * makes the gesture more deliberate but may feel less responsive to + * users. The duration is measured from the initial mouse down event. + */ +- (void) setMinimumPressDuration: (NSTimeInterval)duration; + +/** + * Returns the maximum distance in points that the user can move + * during the press without causing the gesture to fail. This + * tolerance accounts for natural hand tremor and slight movements + * during a press gesture. The distance is calculated as the + * Euclidean distance from the initial press location to the + * current location. The default value is 10 points. + */ +- (CGFloat) allowableMovement; + +/** + * Sets the maximum distance in points that the user can move during + * the press without causing the gesture to fail. Values less than 0 + * are clamped to 0. A smaller value makes the gesture more strict + * about movement but may be difficult to use. A larger value is more + * forgiving but may allow unintended movements to be recognized as + * valid press gestures. Movement is measured from the initial location. + */ +- (void) setAllowableMovement: (CGFloat)movement; + +/** + * Returns the number of touches or mouse clicks required for the + * press gesture to be recognized. For mouse-based systems, this + * typically remains 1 since multiple simultaneous mouse presses + * are not common. The default value is 1, meaning a single press + * is required to trigger the gesture recognition process. + */ +- (NSUInteger) numberOfTouchesRequired; + +/** + * Sets the number of touches or mouse clicks required for the press + * gesture to be recognized. Values of 0 are clamped to 1 to ensure + * at least one touch is required. This property is primarily useful + * for touch-based interfaces where multiple finger presses might + * be desired. For traditional mouse interfaces, this should remain + * at the default value of 1 for optimal user experience. + */ +- (void) setNumberOfTouchesRequired: (NSUInteger)numberOfTouches; @end diff --git a/Source/NSPressGestureRecognizer.m b/Source/NSPressGestureRecognizer.m index 7cf08fb7a9..91f655a945 100644 --- a/Source/NSPressGestureRecognizer.m +++ b/Source/NSPressGestureRecognizer.m @@ -1,21 +1,21 @@ /* Implementation of class NSPressGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -23,8 +23,471 @@ */ #import +#import +#import +#import +#import + +@interface NSPressGestureRecognizer () +- (void)_pressTimerFired:(NSTimer *)timer; +- (void)_invalidateTimer; +- (void)_setState:(NSGestureRecognizerState)state; +@end @implementation NSPressGestureRecognizer +#pragma mark - Class Methods + ++ (void)initialize +{ + if (self == [NSPressGestureRecognizer class]) + { + [self setVersion: 1]; + } +} + +#pragma mark - Initialization + +/** + * Initializes a new press gesture recognizer with the specified target + * and action. The recognizer is configured with default values for + * minimum press duration of 0.5 seconds, allowable movement of 10 points, + * and requires a single touch or mouse press. The internal timer and + * tracking state are initialized to their default values. The recognizer + * begins in the possible state and is ready to process mouse events. + */ +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self) + { + _minimumPressDuration = 0.5; // Default 0.5 seconds + _allowableMovement = 10.0; // Default 10 points + _numberOfTouchesRequired = 1; // Default single touch + _pressTimer = nil; + _initialLocation = NSZeroPoint; + _pressDetected = NO; + } + return self; +} + +/** + * Deallocates the press gesture recognizer and cleans up resources. + * This method invalidates and releases any active timer to prevent + * memory leaks and ensure proper cleanup. The superclass dealloc + * is called to complete the deallocation process. This method is + * called automatically by the runtime when the recognizer is released. + */ +- (void)dealloc +{ + [self _invalidateTimer]; + [super dealloc]; +} + +#pragma mark - Properties + +/** + * Returns the current minimum duration in seconds that a press must + * be held before the gesture is recognized. This value determines the + * timer delay used to transition from the possible state to the began + * state. The duration is measured from the initial mouse down event + * and represents the threshold for distinguishing between a brief + * click and a sustained press gesture. + */ +- (NSTimeInterval)minimumPressDuration +{ + return _minimumPressDuration; +} + +/** + * Sets the minimum duration in seconds that a press must be held + * before the gesture is recognized. Negative values are automatically + * clamped to 0 to prevent invalid timer intervals. This property + * affects when the internal timer fires and transitions the gesture + * from the possible state to the began state. Changes to this value + * take effect on the next gesture recognition cycle. + */ +- (void)setMinimumPressDuration:(NSTimeInterval)duration +{ + if (duration < 0) + duration = 0; + _minimumPressDuration = duration; +} + +/** + * Returns the maximum distance in points that the user can move during + * a press gesture without causing it to fail. This tolerance value + * accommodates natural hand tremor and minor movements during sustained + * presses. The distance is calculated as the Euclidean distance from + * the initial press location to any subsequent location during the + * gesture. Movement beyond this threshold causes the gesture to fail. + */ +- (CGFloat)allowableMovement +{ + return _allowableMovement; +} + +/** + * Sets the maximum distance in points that the user can move during + * a press gesture without causing it to fail. Negative values are + * automatically clamped to 0 to ensure a valid movement threshold. + * This property controls the sensitivity of the gesture recognizer + * to movement during the press. Smaller values create stricter + * movement requirements while larger values are more permissive. + */ +- (void)setAllowableMovement:(CGFloat)movement +{ + if (movement < 0) + movement = 0; + _allowableMovement = movement; +} + +/** + * Returns the number of touches or mouse presses required to trigger + * the gesture recognition process. For mouse-based systems, this is + * typically 1 since simultaneous multiple mouse presses are uncommon. + * This property determines how many concurrent press points must be + * detected before the gesture recognizer begins timing and tracking + * the press gesture. The default value is 1 for single-press gestures. + */ +- (NSUInteger)numberOfTouchesRequired +{ + return _numberOfTouchesRequired; +} + +/** + * Sets the number of touches or mouse presses required to trigger + * the gesture recognition process. Values of 0 are automatically + * clamped to 1 to ensure at least one press is required. This + * property is most relevant for multi-touch interfaces where + * multiple simultaneous presses might be desired. For traditional + * mouse interfaces, this should remain at 1 for optimal usability. + */ +- (void)setNumberOfTouchesRequired:(NSUInteger)numberOfTouches +{ + if (numberOfTouches == 0) + numberOfTouches = 1; + _numberOfTouchesRequired = numberOfTouches; +} + +#pragma mark - Gesture Recognition Override Methods + +/** + * Resets the press gesture recognizer to its initial state after + * a gesture ends or fails. This method cleans up the internal timer, + * clears tracking locations, and resets detection flags. The superclass + * reset method is called first to ensure proper base class cleanup. + * This method is called automatically by the gesture recognition + * framework when transitioning to terminal states. + */ +- (void)reset +{ + [super reset]; + [self _invalidateTimer]; + _initialLocation = NSZeroPoint; + _pressDetected = NO; +} + +/** + * Handles mouse down events to begin press gesture recognition. This + * method validates that the recognizer is enabled and in the possible + * state, then checks if the required number of touches matches the + * current event. If conditions are met, it stores the initial press + * location and starts the minimum duration timer. The gesture remains + * in the possible state until the timer fires or the gesture fails + * due to movement or early release. + */ +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (![self isEnabled] || [self state] != NSGestureRecognizerStatePossible) + { + return; + } + + // Check if we have the required number of touches (simplified for mouse events) + if (_numberOfTouchesRequired != 1) + { + [self _setState:NSGestureRecognizerStateFailed]; + return; + } + + // Store the initial location + _initialLocation = [self locationInView:[self view]]; + _pressDetected = NO; + + // Start the press timer + [self _invalidateTimer]; + _pressTimer = [NSTimer scheduledTimerWithTimeInterval:_minimumPressDuration + target:self + selector:@selector(_pressTimerFired:) + userInfo:nil + repeats:NO]; +} + +/** + * Handles mouse drag events during a potential press gesture. This + * method calculates the distance moved from the initial press location + * and compares it against the allowable movement threshold. If the + * movement exceeds the threshold, the gesture fails immediately. If + * the gesture is already in the began state and movement is within + * tolerance, the state transitions to changed to indicate continued + * gesture recognition with slight movement. + */ +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (![self isEnabled] || [self state] == NSGestureRecognizerStateFailed) + { + return; + } + + // Check if movement exceeds allowable threshold + NSPoint currentLocation = [self locationInView:[self view]]; + CGFloat distance = sqrt(pow(currentLocation.x - _initialLocation.x, 2) + + pow(currentLocation.y - _initialLocation.y, 2)); + + if (distance > _allowableMovement) + { + [self _invalidateTimer]; + [self _setState:NSGestureRecognizerStateFailed]; + return; + } + + // If we've already detected a press and we're within allowable movement, + // update the state to changed + if (_pressDetected && [self state] == NSGestureRecognizerStateBegan) + { + [self _setState:NSGestureRecognizerStateChanged]; + } +} + +/** + * Handles mouse up events to complete or terminate press gesture + * recognition. If the gesture is in an active state and the minimum + * duration timer has fired indicating a successful press, the gesture + * transitions to the ended state. If the timer has not fired, indicating + * an early release, the gesture transitions to the failed state. The + * internal timer is invalidated and tracking state is reset regardless + * of the outcome. + */ +- (void)mouseUp:(NSEvent *)event +{ + if ([self state] == NSGestureRecognizerStatePossible || + [self state] == NSGestureRecognizerStateBegan || + [self state] == NSGestureRecognizerStateChanged) + { + [self _invalidateTimer]; + + if (_pressDetected) + { + [self _setState:NSGestureRecognizerStateEnded]; + } + else + { + [self _setState:NSGestureRecognizerStateFailed]; + } + + _pressDetected = NO; + _initialLocation = NSZeroPoint; + } +} + +/** + * Handles right mouse button down events by delegating to the standard + * mouse down handler. Press gestures can be triggered by any mouse + * button, so right mouse events are treated identically to left mouse + * events. The superclass method is called first to ensure proper + * event propagation and then the standard mouse down processing is + * invoked to begin gesture recognition timing and tracking. + */ +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + [self mouseDown:event]; // Treat right mouse like left mouse for press gestures +} + +/** + * Handles right mouse button drag events by delegating to the standard + * mouse drag handler. Movement tracking and tolerance checking are + * identical regardless of which mouse button initiated the press gesture. + * The superclass method is called for proper event handling and then + * the standard drag processing validates movement against the allowable + * threshold and updates gesture state accordingly. + */ +- (void)rightMouseDragged:(NSEvent *)event +{ + [super rightMouseDragged:event]; + [self mouseDragged:event]; +} + +/** + * Handles right mouse button up events by delegating to the standard + * mouse up handler. Press gesture completion logic is identical for + * all mouse buttons, checking if the minimum duration was met and + * transitioning to the appropriate final state. The superclass method + * ensures proper event handling while the standard mouse up processing + * determines gesture success or failure based on timing. + */ +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + [self mouseUp:event]; +} + +/** + * Handles other mouse button down events by delegating to the standard + * mouse down handler. This includes middle mouse buttons and additional + * buttons on multi-button mice. Press gestures are uniformly supported + * across all mouse buttons to provide consistent behavior. The superclass + * method handles event propagation while the standard processing begins + * gesture recognition with timing and location tracking. + */ +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + [self mouseDown:event]; // Treat other mouse buttons like left mouse +} + +/** + * Handles other mouse button drag events by delegating to the standard + * mouse drag handler. Movement validation and state transitions are + * consistent across all mouse button types to ensure predictable + * behavior. The superclass method manages event handling while the + * standard drag processing checks movement tolerance and updates + * the gesture state based on distance from the initial location. + */ +- (void)otherMouseDragged:(NSEvent *)event +{ + [super otherMouseDragged:event]; + [self mouseDragged:event]; +} + +/** + * Handles other mouse button up events by delegating to the standard + * mouse up handler. Gesture completion processing is uniform across + * all mouse button types, evaluating whether the minimum press duration + * was achieved and transitioning to the appropriate terminal state. + * The superclass method ensures proper event handling while standard + * processing determines the final outcome of the gesture. + */ +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + [self mouseUp:event]; +} + +#pragma mark - Private Methods + +/** + * Internal timer callback method invoked when the minimum press duration + * has elapsed. If the gesture recognizer is still in the possible state, + * indicating that the press has been maintained without excessive movement + * or early release, the gesture transitions to the began state and the + * press is considered successfully detected. The timer is then invalidated + * to prevent further callbacks and conserve resources. + */ +- (void)_pressTimerFired:(NSTimer *)timer +{ + if ([self state] == NSGestureRecognizerStatePossible) + { + _pressDetected = YES; + [self _setState:NSGestureRecognizerStateBegan]; + } + [self _invalidateTimer]; +} + +/** + * Invalidates and cleans up the internal press timer to prevent memory + * leaks and unwanted timer callbacks. This method safely checks for + * timer existence before invalidating and sets the timer reference to + * nil to prevent future access to the invalidated timer. This cleanup + * is essential when gestures fail, succeed, or when the recognizer + * is being deallocated to ensure proper resource management. + */ +- (void)_invalidateTimer +{ + if (_pressTimer) + { + [_pressTimer invalidate]; + _pressTimer = nil; + } +} + +#pragma mark - NSCoding + +#pragma mark - NSCoding Protocol + +/** + * Initializes a press gesture recognizer from encoded data during + * deserialization. This method decodes the minimum press duration, + * allowable movement, and number of touches required from the coder + * using both keyed and non-keyed coding formats for compatibility. + * Invalid decoded values are replaced with sensible defaults to ensure + * proper functionality. Transient state like timers and locations are + * initialized to their default values since they should not persist. + */ +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self) + { + if ([coder allowsKeyedCoding]) + { + _minimumPressDuration = [coder decodeDoubleForKey:@"NSMinimumPressDuration"]; + _allowableMovement = [coder decodeDoubleForKey:@"NSAllowableMovement"]; + _numberOfTouchesRequired = [coder decodeIntegerForKey:@"NSNumberOfTouchesRequired"]; + } + else + { + [coder decodeValueOfObjCType:@encode(NSTimeInterval) at:&_minimumPressDuration]; + [coder decodeValueOfObjCType:@encode(CGFloat) at:&_allowableMovement]; + [coder decodeValueOfObjCType:@encode(NSUInteger) at:&_numberOfTouchesRequired]; + } + + // Set defaults if values are invalid + if (_minimumPressDuration <= 0) + _minimumPressDuration = 0.5; + if (_allowableMovement < 0) + _allowableMovement = 10.0; + if (_numberOfTouchesRequired == 0) + _numberOfTouchesRequired = 1; + + _pressTimer = nil; + _initialLocation = NSZeroPoint; + _pressDetected = NO; + } + return self; +} + +/** + * Encodes the press gesture recognizer's configuration for serialization. + * This method encodes the minimum press duration, allowable movement, + * and number of touches required using both keyed and non-keyed coding + * formats to maintain compatibility with different archiving mechanisms. + * Only persistent configuration properties are encoded since transient + * state like timers and current locations should not be preserved + * across serialization boundaries. + */ +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + + if ([coder allowsKeyedCoding]) + { + [coder encodeDouble:_minimumPressDuration forKey:@"NSMinimumPressDuration"]; + [coder encodeDouble:_allowableMovement forKey:@"NSAllowableMovement"]; + [coder encodeInteger:_numberOfTouchesRequired forKey:@"NSNumberOfTouchesRequired"]; + } + else + { + [coder encodeValueOfObjCType:@encode(NSTimeInterval) at:&_minimumPressDuration]; + [coder encodeValueOfObjCType:@encode(CGFloat) at:&_allowableMovement]; + [coder encodeValueOfObjCType:@encode(NSUInteger) at:&_numberOfTouchesRequired]; + } +} + @end From dcb18c98d1b759e1af8c98a79ddd6f7c9f3988d4 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 11:53:36 -0400 Subject: [PATCH 03/12] Fix error with NSTimer --- Headers/AppKit/NSPressGestureRecognizer.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Headers/AppKit/NSPressGestureRecognizer.h b/Headers/AppKit/NSPressGestureRecognizer.h index a4698cad36..87bc3af25b 100644 --- a/Headers/AppKit/NSPressGestureRecognizer.h +++ b/Headers/AppKit/NSPressGestureRecognizer.h @@ -30,6 +30,8 @@ #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSTimer; + #if defined(__cplusplus) extern "C" { #endif From 35d2c52276c7465f88ef22c44d2fa2466d7ca1c2 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 12:09:36 -0400 Subject: [PATCH 04/12] Add rotation recognizer --- Headers/AppKit/NSRotationGestureRecognizer.h | 30 +- Source/NSRotationGestureRecognizer.m | 290 ++++++++++++++++++- 2 files changed, 310 insertions(+), 10 deletions(-) diff --git a/Headers/AppKit/NSRotationGestureRecognizer.h b/Headers/AppKit/NSRotationGestureRecognizer.h index ae688d58d3..f8153ff6c0 100644 --- a/Headers/AppKit/NSRotationGestureRecognizer.h +++ b/Headers/AppKit/NSRotationGestureRecognizer.h @@ -1,25 +1,25 @@ /* Definition of class NSRotationGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -27,6 +27,7 @@ #define _NSRotationGestureRecognizer_h_GNUSTEP_GUI_INCLUDE #import +#import #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) @@ -36,6 +37,23 @@ extern "C" { APPKIT_EXPORT_CLASS @interface NSRotationGestureRecognizer : NSGestureRecognizer +{ +@private + CGFloat _rotation; + CGFloat _velocity; + NSPoint _initialLocation; + NSPoint _currentLocation; + CGFloat _initialAngle; + CGFloat _currentAngle; + NSTimeInterval _initialTime; + NSTimeInterval _currentTime; + BOOL _rotationStarted; +} + +// Getting the Rotation Values +- (CGFloat) rotation; + +- (CGFloat) velocity; @end diff --git a/Source/NSRotationGestureRecognizer.m b/Source/NSRotationGestureRecognizer.m index 7bf37e641f..266825c37a 100644 --- a/Source/NSRotationGestureRecognizer.m +++ b/Source/NSRotationGestureRecognizer.m @@ -1,21 +1,21 @@ /* Implementation of class NSRotationGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -23,8 +23,290 @@ */ #import +#import +#import +#import +#import + +@interface NSRotationGestureRecognizer () +- (void)_setState:(NSGestureRecognizerState)state; +- (CGFloat)_angleFromPoint:(NSPoint)fromPoint toPoint:(NSPoint)toPoint; +- (CGFloat)_normalizeAngle:(CGFloat)angle; +- (void)_updateRotationWithEvent:(NSEvent *)event; +@end @implementation NSRotationGestureRecognizer +#pragma mark - Class Methods + ++ (void)initialize +{ + if (self == [NSRotationGestureRecognizer class]) + { + [self setVersion: 1]; + } +} + +#pragma mark - Initialization + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self) + { + _rotation = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _currentLocation = NSZeroPoint; + _initialAngle = 0.0; + _currentAngle = 0.0; + _initialTime = 0.0; + _currentTime = 0.0; + _rotationStarted = NO; + } + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +#pragma mark - Properties + +- (CGFloat)rotation +{ + return _rotation; +} + +- (CGFloat)velocity +{ + return _velocity; +} + +#pragma mark - Gesture Recognition Override Methods + +- (void)reset +{ + [super reset]; + _rotation = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _currentLocation = NSZeroPoint; + _initialAngle = 0.0; + _currentAngle = 0.0; + _initialTime = 0.0; + _currentTime = 0.0; + _rotationStarted = NO; +} + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (![self isEnabled] || [self state] != NSGestureRecognizerStatePossible) + { + return; + } + + _initialLocation = [self locationInView:[self view]]; + _currentLocation = _initialLocation; + _initialAngle = 0.0; + _currentAngle = 0.0; + _initialTime = [NSDate timeIntervalSinceReferenceDate]; + _currentTime = _initialTime; + _rotationStarted = NO; + _rotation = 0.0; + _velocity = 0.0; +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (![self isEnabled] || [self state] == NSGestureRecognizerStateFailed) + { + return; + } + + [self _updateRotationWithEvent:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + if ([self state] == NSGestureRecognizerStatePossible || + [self state] == NSGestureRecognizerStateBegan || + [self state] == NSGestureRecognizerStateChanged) + { + if (_rotationStarted) + { + [self _setState:NSGestureRecognizerStateEnded]; + } + else + { + [self _setState:NSGestureRecognizerStateFailed]; + } + } +} + +- (void)scrollWheel:(NSEvent *)event +{ + [super scrollWheel:event]; + + if (![self isEnabled] || [self state] == NSGestureRecognizerStateFailed) + { + return; + } + + // Handle scroll wheel rotation gestures + CGFloat deltaX = [event deltaX]; + CGFloat deltaY = [event deltaY]; + + // Calculate rotation from scroll deltas + CGFloat rotationDelta = atan2(deltaY, deltaX); + + if (!_rotationStarted) + { + _initialTime = [NSDate timeIntervalSinceReferenceDate]; + _rotationStarted = YES; + [self _setState:NSGestureRecognizerStateBegan]; + } + else + { + [self _setState:NSGestureRecognizerStateChanged]; + } + + _rotation += rotationDelta; + _currentTime = [NSDate timeIntervalSinceReferenceDate]; + + // Calculate velocity + NSTimeInterval timeDelta = _currentTime - _initialTime; + if (timeDelta > 0) + { + _velocity = _rotation / timeDelta; + } +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + [self mouseDown:event]; +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [super rightMouseDragged:event]; + [self mouseDragged:event]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + [self mouseUp:event]; +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + [self mouseDown:event]; +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [super otherMouseDragged:event]; + [self mouseDragged:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + [self mouseUp:event]; +} + +#pragma mark - Private Methods + +- (CGFloat)_angleFromPoint:(NSPoint)fromPoint toPoint:(NSPoint)toPoint +{ + CGFloat deltaX = toPoint.x - fromPoint.x; + CGFloat deltaY = toPoint.y - fromPoint.y; + return atan2(deltaY, deltaX); +} + +- (CGFloat)_normalizeAngle:(CGFloat)angle +{ + while (angle > M_PI) + angle -= 2 * M_PI; + while (angle < -M_PI) + angle += 2 * M_PI; + return angle; +} + +- (void)_updateRotationWithEvent:(NSEvent *)event +{ + NSPoint newLocation = [self locationInView:[self view]]; + NSPoint centerPoint = NSMakePoint((_initialLocation.x + newLocation.x) / 2.0, + (_initialLocation.y + newLocation.y) / 2.0); + + // Calculate angles from center point + CGFloat newAngle = [self _angleFromPoint:centerPoint toPoint:newLocation]; + + if (!_rotationStarted) + { + _initialAngle = [self _angleFromPoint:centerPoint toPoint:_initialLocation]; + _currentAngle = newAngle; + _rotation = [self _normalizeAngle:(_currentAngle - _initialAngle)]; + + // Check if rotation threshold is met to start gesture + if (fabs(_rotation) > 0.1) // ~5.7 degrees threshold + { + _rotationStarted = YES; + _initialTime = [NSDate timeIntervalSinceReferenceDate]; + [self _setState:NSGestureRecognizerStateBegan]; + } + } + else + { + _currentAngle = newAngle; + _rotation = [self _normalizeAngle:(_currentAngle - _initialAngle)]; + [self _setState:NSGestureRecognizerStateChanged]; + } + + _currentLocation = newLocation; + _currentTime = [NSDate timeIntervalSinceReferenceDate]; + + // Calculate velocity + NSTimeInterval timeDelta = _currentTime - _initialTime; + if (timeDelta > 0) + { + _velocity = _rotation / timeDelta; + } +} + +#pragma mark - NSCoding Protocol + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self) + { + _rotation = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _currentLocation = NSZeroPoint; + _initialAngle = 0.0; + _currentAngle = 0.0; + _initialTime = 0.0; + _currentTime = 0.0; + _rotationStarted = NO; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + // Transient state is not encoded since it should not persist +} + @end From 99b8dc1db932ca7d2df34abbdc3f1b738e3216a9 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 12:48:55 -0400 Subject: [PATCH 05/12] Add magnification recognizer --- .../AppKit/NSMagnificationGestureRecognizer.h | 28 +- Source/NSMagnificationGestureRecognizer.m | 299 +++++++++++++++++- 2 files changed, 317 insertions(+), 10 deletions(-) diff --git a/Headers/AppKit/NSMagnificationGestureRecognizer.h b/Headers/AppKit/NSMagnificationGestureRecognizer.h index 38cdf546b0..36bae3326e 100644 --- a/Headers/AppKit/NSMagnificationGestureRecognizer.h +++ b/Headers/AppKit/NSMagnificationGestureRecognizer.h @@ -1,25 +1,25 @@ /* Definition of class NSMagnificationGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -36,6 +36,22 @@ extern "C" { APPKIT_EXPORT_CLASS @interface NSMagnificationGestureRecognizer : NSGestureRecognizer +{ +@private + CGFloat _magnification; + CGFloat _velocity; + NSPoint _initialLocation; + CGFloat _initialMagnification; + NSTimeInterval _initialTime; + NSTimeInterval _currentTime; + BOOL _magnificationStarted; + CGFloat _cumulativeMagnification; +} + +// Getting the Magnification Values +- (CGFloat) magnification; + +- (CGFloat) velocity; @end diff --git a/Source/NSMagnificationGestureRecognizer.m b/Source/NSMagnificationGestureRecognizer.m index 01e4d47b91..1caa30886c 100644 --- a/Source/NSMagnificationGestureRecognizer.m +++ b/Source/NSMagnificationGestureRecognizer.m @@ -1,21 +1,21 @@ /* Implementation of class NSMagnificationGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -23,8 +23,299 @@ */ #import +#import +#import +#import +#import + +@interface NSMagnificationGestureRecognizer () +- (void)_setState:(NSGestureRecognizerState)state; +- (void)_updateMagnificationWithEvent:(NSEvent *)event; +- (void)_resetMagnificationTracking; +@end @implementation NSMagnificationGestureRecognizer +#pragma mark - Class Methods + ++ (void)initialize +{ + if (self == [NSMagnificationGestureRecognizer class]) + { + [self setVersion: 1]; + } +} + +#pragma mark - Initialization + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self) + { + _magnification = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _initialMagnification = 1.0; + _initialTime = 0.0; + _currentTime = 0.0; + _magnificationStarted = NO; + _cumulativeMagnification = 0.0; + } + return self; +} + +- (void)dealloc +{ + [super dealloc]; +} + +#pragma mark - Properties + +- (CGFloat)magnification +{ + return _magnification; +} + +- (CGFloat)velocity +{ + return _velocity; +} + +#pragma mark - Gesture Recognition Override Methods + +- (void)reset +{ + [super reset]; + [self _resetMagnificationTracking]; +} + +- (void)scrollWheel:(NSEvent *)event +{ + [super scrollWheel:event]; + + if (![self isEnabled]) + { + return; + } + + // Check for magnification in scroll wheel event + CGFloat magnificationDelta = [event magnification]; + + if (magnificationDelta != 0.0) + { + [self _updateMagnificationWithEvent:event]; + } + else + { + // Check for scroll wheel zoom (when no direct magnification available) + CGFloat deltaY = [event deltaY]; + + if (fabs(deltaY) > 0.1 && ([event modifierFlags] & NSEventModifierFlagCommand)) + { + // Treat Command+scroll as magnification + magnificationDelta = deltaY * 0.01; // Scale factor + [self _updateMagnificationWithEvent:event]; + } + else if ([self state] == NSGestureRecognizerStateBegan || + [self state] == NSGestureRecognizerStateChanged) + { + // End gesture if no more magnification input + [self _setState:NSGestureRecognizerStateEnded]; + } + } +} + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (![self isEnabled] || [self state] != NSGestureRecognizerStatePossible) + { + return; + } + + _initialLocation = [self locationInView:[self view]]; + [self _resetMagnificationTracking]; +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (![self isEnabled] || [self state] == NSGestureRecognizerStateFailed) + { + return; + } + + // For mouse drag magnification, we could implement distance-based zoom + // This is less common but can be useful for some interfaces + NSPoint currentLocation = [self locationInView:[self view]]; + CGFloat distance = sqrt(pow(currentLocation.x - _initialLocation.x, 2) + + pow(currentLocation.y - _initialLocation.y, 2)); + + // Only start magnification if significant movement detected + if (distance > 20.0) // 20 point threshold + { + CGFloat magnificationDelta = (distance - 20.0) * 0.001; // Scale factor + + if (!_magnificationStarted) + { + _initialTime = [NSDate timeIntervalSinceReferenceDate]; + _magnificationStarted = YES; + [self _setState:NSGestureRecognizerStateBegan]; + } + else + { + [self _setState:NSGestureRecognizerStateChanged]; + } + + _magnification = magnificationDelta; + _cumulativeMagnification += magnificationDelta; + _currentTime = [NSDate timeIntervalSinceReferenceDate]; + + // Calculate velocity + NSTimeInterval timeDelta = _currentTime - _initialTime; + if (timeDelta > 0) + { + _velocity = _cumulativeMagnification / timeDelta; + } + } +} + +- (void)mouseUp:(NSEvent *)event +{ + if ([self state] == NSGestureRecognizerStatePossible || + [self state] == NSGestureRecognizerStateBegan || + [self state] == NSGestureRecognizerStateChanged) + { + if (_magnificationStarted) + { + [self _setState:NSGestureRecognizerStateEnded]; + } + else + { + [self _setState:NSGestureRecognizerStateFailed]; + } + } +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + [self mouseDown:event]; +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [super rightMouseDragged:event]; + [self mouseDragged:event]; +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + [self mouseUp:event]; +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + [self mouseDown:event]; +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [super otherMouseDragged:event]; + [self mouseDragged:event]; +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + [self mouseUp:event]; +} + +#pragma mark - Private Methods + +- (void)_updateMagnificationWithEvent:(NSEvent *)event +{ + CGFloat magnificationDelta = [event magnification]; + + if (magnificationDelta == 0.0) + { + // Fallback for systems without direct magnification support + CGFloat deltaY = [event deltaY]; + if (fabs(deltaY) > 0.1 && ([event modifierFlags] & NSEventModifierFlagCommand)) + { + magnificationDelta = deltaY * 0.01; // Scale factor + } + else + { + return; + } + } + + if (!_magnificationStarted) + { + _initialTime = [NSDate timeIntervalSinceReferenceDate]; + _magnificationStarted = YES; + _cumulativeMagnification = 0.0; + [self _setState:NSGestureRecognizerStateBegan]; + } + else + { + [self _setState:NSGestureRecognizerStateChanged]; + } + + _magnification = magnificationDelta; + _cumulativeMagnification += magnificationDelta; + _currentTime = [NSDate timeIntervalSinceReferenceDate]; + + // Calculate velocity + NSTimeInterval timeDelta = _currentTime - _initialTime; + if (timeDelta > 0) + { + _velocity = _cumulativeMagnification / timeDelta; + } +} + +- (void)_resetMagnificationTracking +{ + _magnification = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _initialMagnification = 1.0; + _initialTime = 0.0; + _currentTime = 0.0; + _magnificationStarted = NO; + _cumulativeMagnification = 0.0; +} + +#pragma mark - NSCoding Protocol + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self) + { + _magnification = 0.0; + _velocity = 0.0; + _initialLocation = NSZeroPoint; + _initialMagnification = 1.0; + _initialTime = 0.0; + _currentTime = 0.0; + _magnificationStarted = NO; + _cumulativeMagnification = 0.0; + } + return self; +} + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + // Transient state is not encoded since it should not persist +} + @end From 7040d7c51587b8508a709f4c79a06a490b09e4b3 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 12:52:22 -0400 Subject: [PATCH 06/12] Add magnification recognizer --- .../AppKit/NSMagnificationGestureRecognizer.h | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/Headers/AppKit/NSMagnificationGestureRecognizer.h b/Headers/AppKit/NSMagnificationGestureRecognizer.h index 36bae3326e..fbf24e8478 100644 --- a/Headers/AppKit/NSMagnificationGestureRecognizer.h +++ b/Headers/AppKit/NSMagnificationGestureRecognizer.h @@ -27,6 +27,7 @@ #define _NSMagnificationGestureRecognizer_h_GNUSTEP_GUI_INCLUDE #import +#import #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) @@ -35,6 +36,26 @@ extern "C" { #endif APPKIT_EXPORT_CLASS +/** + * NSMagnificationGestureRecognizer is a concrete subclass of NSGestureRecognizer + * that recognizes magnification gestures, commonly known as pinch-to-zoom or + * zoom gestures. This recognizer detects when the user performs a magnification + * gesture using trackpad pinch gestures, scroll wheel events with modifier keys, + * or mouse drag movements to indicate zooming intent. + * + * The recognizer monitors various input sources including trackpad magnification + * events, Command+scroll wheel combinations, and distance-based mouse drag + * gestures. When magnification input is detected, the recognizer transitions + * from the possible state to began, then to changed for continued magnification, + * and finally to ended when the magnification gesture completes. + * + * Key properties include magnification which provides the current magnification + * factor as a floating-point value, and velocity which indicates the rate of + * magnification change over time. Positive magnification values indicate zoom-in + * operations while negative values represent zoom-out operations. The recognizer + * accumulates magnification values throughout the gesture duration and calculates + * velocity based on the total magnification change over elapsed time. + */ @interface NSMagnificationGestureRecognizer : NSGestureRecognizer { @private @@ -49,8 +70,28 @@ APPKIT_EXPORT_CLASS } // Getting the Magnification Values +/** + * Returns the current magnification factor for the gesture. This value + * represents the incremental magnification change detected in the most + * recent gesture update. Positive values indicate zoom-in magnification + * while negative values indicate zoom-out magnification. The magnification + * value is calculated from trackpad pinch gestures, scroll wheel events + * with modifier keys, or mouse drag distance measurements. The value is + * reset to zero when the gesture begins and updated continuously as the + * magnification gesture progresses through its recognition cycle. + */ - (CGFloat) magnification; +/** + * Returns the velocity of the magnification gesture in magnification units + * per second. This value indicates the rate at which the magnification is + * changing over time, providing information about the speed and intensity + * of the zoom gesture. Positive velocity values correspond to accelerating + * zoom-in operations while negative values indicate accelerating zoom-out + * operations. The velocity is calculated by dividing the cumulative + * magnification change by the elapsed time since the gesture began. This + * property is useful for implementing momentum-based zooming behaviors. + */ - (CGFloat) velocity; @end From 94339da2d698d7a05e64d4adc1ed4d7aedf4fb29 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 15:26:41 -0400 Subject: [PATCH 07/12] Add magnification to NSEvent --- Headers/AppKit/NSEvent.h | 17 +++++- Source/NSEvent.m | 74 ++++++++++++++--------- Source/NSMagnificationGestureRecognizer.m | 10 +-- 3 files changed, 67 insertions(+), 34 deletions(-) diff --git a/Headers/AppKit/NSEvent.h b/Headers/AppKit/NSEvent.h index 89b6bd6c3d..3eb36bbaaa 100644 --- a/Headers/AppKit/NSEvent.h +++ b/Headers/AppKit/NSEvent.h @@ -452,6 +452,8 @@ APPKIT_EXPORT_CLASS CGFloat deltaY; /** Z-axis scroll delta for 3D scroll devices */ CGFloat deltaZ; + /** Magnification factor for gesture events */ + CGFloat magnification; } mouse; /** Keyboard event data including characters and key codes */ struct @@ -588,6 +590,7 @@ APPKIT_EXPORT_CLASS * deltaX: Horizontal scroll delta for scroll events * deltaY: Vertical scroll delta for scroll events * deltaZ: Z-axis scroll delta for 3D scroll devices + * magnificationValue: Magnification factor for gesture events * Returns: A new NSEvent object with extended mouse information */ + (NSEvent*) mouseEventWithType: (NSEventType)type @@ -602,7 +605,8 @@ APPKIT_EXPORT_CLASS buttonNumber: (NSInteger)buttonNum deltaX: (CGFloat)deltaX deltaY: (CGFloat)deltaY - deltaZ: (CGFloat)deltaZ; + deltaZ: (CGFloat)deltaZ + magnification: (CGFloat)magnificationValue; #endif /** @@ -756,6 +760,17 @@ APPKIT_EXPORT_CLASS * Returns: The z-axis scroll distance for this scroll event */ - (CGFloat)deltaZ; + +/** + * Returns the magnification factor for gesture events. + * For trackpad pinch/zoom gestures, this indicates the magnification + * change requested by the user. Positive values indicate zoom-in + * (magnification increase) while negative values indicate zoom-out + * (magnification decrease). The magnitude represents the relative + * change in scale factor for the gesture. + * Returns: The magnification factor for this gesture event + */ +- (CGFloat)magnification; #endif #if OS_API_VERSION(GS_API_MACOSX, GS_API_LATEST) /** diff --git a/Source/NSEvent.m b/Source/NSEvent.m index 8e690a787f..0d239a0e28 100644 --- a/Source/NSEvent.m +++ b/Source/NSEvent.m @@ -26,8 +26,8 @@ You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -187,16 +187,21 @@ + (NSEvent*) mouseEventWithType: (NSEventType)type e->event_data.mouse.event_num = eventNum; e->event_data.mouse.click = clickNum; e->event_data.mouse.pressure = pressureValue; + e->event_data.mouse.button = 0; + e->event_data.mouse.deltaX = 0.0; + e->event_data.mouse.deltaY = 0.0; + e->event_data.mouse.deltaZ = 0.0; + e->event_data.mouse.magnification = 0.0; return e; } -+ (NSEvent*) mouseEventWithType: (NSEventType)type ++ (NSEvent*) mouseEventWithType: (NSEventType)type location: (NSPoint)location modifierFlags: (NSUInteger)flags timestamp: (NSTimeInterval)time windowNumber: (NSInteger)windowNum - context: (NSGraphicsContext*)context + context: (NSGraphicsContext*)context eventNumber: (NSInteger)eventNum clickCount: (NSInteger)clickNum pressure: (float)pressureValue @@ -204,6 +209,7 @@ + (NSEvent*) mouseEventWithType: (NSEventType)type deltaX: (CGFloat)deltaX deltaY: (CGFloat)deltaY deltaZ: (CGFloat)deltaZ + magnification: (CGFloat)magnificationValue { NSEvent *e; @@ -229,6 +235,7 @@ + (NSEvent*) mouseEventWithType: (NSEventType)type e->event_data.mouse.deltaX = deltaX; e->event_data.mouse.deltaY = deltaY; e->event_data.mouse.deltaZ = deltaZ; + e->event_data.mouse.magnification = magnificationValue; return e; } @@ -339,7 +346,7 @@ + (void) _registerRealTimer: (NSTimer*)timer { NSTimeInterval timeInterval; NSEvent *periodicEvent; - + timeInterval = [dateClass timeIntervalSinceReferenceDate]; periodicEvent = [self otherEventWithType: NSPeriodic location: NSZeroPoint @@ -350,7 +357,7 @@ + (void) _registerRealTimer: (NSTimer*)timer subtype: 0 data1: 0 data2: 0]; - + [NSApp postEvent: periodicEvent atStart: NO]; } @@ -518,7 +525,7 @@ - (void) dealloc Returns the movement of the mouse on the X axis.

- This method is only valid for NSMouseMoved, NS*MouseDragged and + This method is only valid for NSMouseMoved, NS*MouseDragged and NSScrollWheel events, otherwise it will return 0.

*/ @@ -536,7 +543,7 @@ - (CGFloat) deltaX Returns the movement of the mouse on the Y axis.

- This method is only valid for NSMouseMoved, NS*MouseDragged and + This method is only valid for NSMouseMoved, NS*MouseDragged and NSScrollWheel events, otherwise it will return 0.

*/ @@ -554,7 +561,7 @@ - (CGFloat) deltaY Returns the movement of the mouse on the Z axis.

- This method is only valid for NSMouseMoved, NS*MouseDragged and + This method is only valid for NSMouseMoved, NS*MouseDragged and NSScrollWheel events, otherwise it will return 0.

@@ -570,6 +577,15 @@ - (CGFloat) deltaZ return event_data.mouse.deltaZ; } +- (CGFloat) magnification +{ + if (!(NSEventMaskFromType(event_type) & GSMouseMovedEventMask)) + { + return 0.0; + } + return event_data.mouse.magnification; +} + - (NSString*) description { static const char *eventTypes[] = { @@ -670,14 +686,15 @@ - (NSString*) description @"NSEvent: eventType = %s, point = { %f, %f }, modifiers = %lu," @" time = %f, window = %ld, dpsContext = %p," @" event number = %ld, click = %ld, pressure = %f" - @" button = %ld, deltaX = %f, deltaY = %f, deltaZ = %f", + @" button = %ld, deltaX = %f, deltaY = %f, deltaZ = %f, magnification = %f", eventTypes[event_type], location_point.x, location_point.y, (unsigned long) modifier_flags, event_time, (long) window_num, event_context, (long ) event_data.mouse.event_num, (long) event_data.mouse.click, event_data.mouse.pressure, (long) event_data.mouse.button, event_data.mouse.deltaX, event_data.mouse.deltaY, - event_data.mouse.deltaZ]; + event_data.mouse.deltaZ, + event_data.mouse.magnification]; break; // FIXME: Tablet events @@ -710,7 +727,7 @@ - (void) encodeWithCoder: (NSCoder*)aCoder [aCoder encodeValueOfObjCType: @encode(NSUInteger) at: &modifier_flags]; [aCoder encodeValueOfObjCType: @encode(NSTimeInterval) at: &event_time]; [aCoder encodeValueOfObjCType: @encode(NSInteger) at: &window_num]; - + switch (event_type) { case NSLeftMouseDown: @@ -724,10 +741,11 @@ - (void) encodeWithCoder: (NSCoder*)aCoder case NSLeftMouseDragged: case NSOtherMouseDragged: case NSRightMouseDragged: - [aCoder encodeValuesOfObjCTypes: "iififff", &event_data.mouse.event_num, + [aCoder encodeValuesOfObjCTypes: "iififfff", &event_data.mouse.event_num, &event_data.mouse.click, &event_data.mouse.pressure, &event_data.mouse.button, &event_data.mouse.deltaX, - &event_data.mouse.deltaY, &event_data.mouse.deltaZ]; + &event_data.mouse.deltaY, &event_data.mouse.deltaZ, + &event_data.mouse.magnification]; break; case NSMouseEntered: @@ -737,7 +755,7 @@ - (void) encodeWithCoder: (NSCoder*)aCoder [aCoder encodeValuesOfObjCTypes: "ii", &event_data.tracking.event_num, &event_data.tracking.tracking_num]; break; - + case NSKeyDown: case NSKeyUp: case NSFlagsChanged: @@ -747,7 +765,7 @@ - (void) encodeWithCoder: (NSCoder*)aCoder [aCoder encodeObject: event_data.key.unmodified_keys]; [aCoder encodeValueOfObjCType: "S" at: &event_data.key.key_code]; break; - + case NSPeriodic: case NSAppKitDefined: case NSSystemDefined: @@ -755,7 +773,7 @@ - (void) encodeWithCoder: (NSCoder*)aCoder [aCoder encodeValuesOfObjCTypes: "sii", &event_data.misc.sub_type, &event_data.misc.data1, &event_data.misc.data2]; break; - + case NSTabletPoint: case NSTabletProximity: // FIXME: Tablet events @@ -774,7 +792,7 @@ - (void) encodeWithCoder: (NSCoder*)aCoder - (NSInteger) eventNumber { /* Make sure it is one of the right event types */ - if (!(NSEventMaskFromType(event_type) & GSMouseEventMask) && + if (!(NSEventMaskFromType(event_type) & GSMouseEventMask) && !(NSEventMaskFromType(event_type) & GSEnterExitEventMask)) [NSException raise: NSInternalInconsistencyException format: @"eventNumber requested for non-tracking event"]; @@ -794,13 +812,13 @@ - (id) initWithCoder: (NSCoder*)aDecoder else { int version = [aDecoder versionForClassName: @"NSEvent"]; - + [aDecoder decodeValueOfObjCType: @encode(NSInteger) at: &event_type]; location_point = [aDecoder decodePoint]; [aDecoder decodeValueOfObjCType: @encode(NSUInteger) at: &modifier_flags]; [aDecoder decodeValueOfObjCType: @encode(NSTimeInterval) at: &event_time]; [aDecoder decodeValueOfObjCType: @encode(NSInteger) at: &window_num]; - + if (version == 1) { // For the unlikely case that old events have been stored, convert them. @@ -828,7 +846,7 @@ - (id) initWithCoder: (NSCoder*)aDecoder case 19: event_type = NSCursorUpdate; break; case 20: event_type = NSScrollWheel; break; default: break; - } + } } // Previously flag change events where encoded wrongly @@ -838,7 +856,7 @@ - (id) initWithCoder: (NSCoder*)aDecoder &event_data.misc.data1, &event_data.misc.data2]; return self; } - + // Decode the event date based upon the event type switch (event_type) { @@ -853,13 +871,13 @@ - (id) initWithCoder: (NSCoder*)aDecoder case NSLeftMouseDragged: case NSOtherMouseDragged: case NSRightMouseDragged: - [aDecoder decodeValuesOfObjCTypes: "iififff", + [aDecoder decodeValuesOfObjCTypes: "iififfff", &event_data.mouse.event_num, &event_data.mouse.click, &event_data.mouse.pressure, &event_data.mouse.button, &event_data.mouse.deltaX, &event_data.mouse.deltaY, - &event_data.mouse.deltaZ]; + &event_data.mouse.deltaZ, &event_data.mouse.magnification]; break; - + case NSMouseEntered: case NSMouseExited: case NSCursorUpdate: @@ -867,7 +885,7 @@ - (id) initWithCoder: (NSCoder*)aDecoder [aDecoder decodeValuesOfObjCTypes: "ii", &event_data.tracking.event_num, &event_data.tracking.tracking_num]; break; - + case NSKeyDown: case NSKeyUp: case NSFlagsChanged: @@ -877,7 +895,7 @@ - (id) initWithCoder: (NSCoder*)aDecoder event_data.key.unmodified_keys = [aDecoder decodeObject]; [aDecoder decodeValueOfObjCType: "S" at: &event_data.key.key_code]; break; - + case NSPeriodic: case NSAppKitDefined: case NSSystemDefined: @@ -968,7 +986,7 @@ - (float) pressure */ - (short) subtype { - if (!(NSEventMaskFromType(event_type) & GSOtherEventMask) && + if (!(NSEventMaskFromType(event_type) & GSOtherEventMask) && !(NSEventMaskFromType(event_type) & GSMouseEventMask)) { [NSException raise: NSInternalInconsistencyException diff --git a/Source/NSMagnificationGestureRecognizer.m b/Source/NSMagnificationGestureRecognizer.m index 1caa30886c..9f7518d76e 100644 --- a/Source/NSMagnificationGestureRecognizer.m +++ b/Source/NSMagnificationGestureRecognizer.m @@ -36,7 +36,7 @@ - (void)_resetMagnificationTracking; @implementation NSMagnificationGestureRecognizer -#pragma mark - Class Methods +// Class Methods + (void)initialize { @@ -46,7 +46,7 @@ + (void)initialize } } -#pragma mark - Initialization +// Initialization - (instancetype)initWithTarget:(id)target action:(SEL)action { @@ -70,7 +70,7 @@ - (void)dealloc [super dealloc]; } -#pragma mark - Properties +// Properties - (CGFloat)magnification { @@ -82,7 +82,7 @@ - (CGFloat)velocity return _velocity; } -#pragma mark - Gesture Recognition Override Methods +// Gesture Recognition Override Methods - (void)reset { @@ -236,7 +236,7 @@ - (void)otherMouseUp:(NSEvent *)event [self mouseUp:event]; } -#pragma mark - Private Methods +// Private Methods - (void)_updateMagnificationWithEvent:(NSEvent *)event { From 7e460b38f3b4fbdaec246aa5af15840d8348e514 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 18:05:24 -0400 Subject: [PATCH 08/12] Recognizers for click, pan, etc --- Headers/AppKit/NSClickGestureRecognizer.h | 60 ++- Headers/AppKit/NSPanGestureRecognizer.h | 96 ++++- Source/NSClickGestureRecognizer.m | 260 +++++++++++- Source/NSPanGestureRecognizer.m | 493 +++++++++++++++++++++- 4 files changed, 889 insertions(+), 20 deletions(-) diff --git a/Headers/AppKit/NSClickGestureRecognizer.h b/Headers/AppKit/NSClickGestureRecognizer.h index fbb5b61d61..1d7cf84e15 100644 --- a/Headers/AppKit/NSClickGestureRecognizer.h +++ b/Headers/AppKit/NSClickGestureRecognizer.h @@ -1,25 +1,25 @@ /* Definition of class NSClickGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -37,6 +37,54 @@ extern "C" { APPKIT_EXPORT_CLASS @interface NSClickGestureRecognizer : NSGestureRecognizer +{ + /** The mouse button mask that triggers this gesture (default: left mouse button) */ + NSUInteger _buttonMask; + /** The number of clicks required to trigger this gesture (default: 1) */ + NSUInteger _numberOfClicksRequired; + /** The number of touches required for the gesture (default: 1) */ + NSUInteger _numberOfTouchesRequired; +} + +/** + * Returns the mouse button mask that triggers this gesture. + * The default value is 1 (left mouse button). + * Returns: The mouse button mask for this gesture + */ +- (NSUInteger)buttonMask; + +/** + * Sets the mouse button mask that triggers this gesture. + * Use NSLeftMouseDownMask, NSRightMouseDownMask, etc. + * mask: The mouse button mask to set + */ +- (void)setButtonMask:(NSUInteger)mask; + +/** + * Returns the number of clicks required to trigger this gesture. + * The default value is 1 (single click). + * Returns: The number of clicks required + */ +- (NSUInteger)numberOfClicksRequired; + +/** + * Sets the number of clicks required to trigger this gesture. + * clicks: The number of clicks required (must be >= 1) + */ +- (void)setNumberOfClicksRequired:(NSUInteger)clicks; + +/** + * Returns the number of touches required for this gesture. + * The default value is 1. + * Returns: The number of touches required + */ +- (NSUInteger)numberOfTouchesRequired; + +/** + * Sets the number of touches required for this gesture. + * touches: The number of touches required (must be >= 1) + */ +- (void)setNumberOfTouchesRequired:(NSUInteger)touches; @end diff --git a/Headers/AppKit/NSPanGestureRecognizer.h b/Headers/AppKit/NSPanGestureRecognizer.h index a41859ca45..df5f583c36 100644 --- a/Headers/AppKit/NSPanGestureRecognizer.h +++ b/Headers/AppKit/NSPanGestureRecognizer.h @@ -1,25 +1,25 @@ /* Definition of class NSPanGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; see the file COPYING.LIB. - If not, see or write to the - Free Software Foundation, 51 Franklin Street, Fifth Floor, + If not, see or write to the + Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ @@ -36,6 +36,90 @@ extern "C" { APPKIT_EXPORT_CLASS @interface NSPanGestureRecognizer : NSGestureRecognizer +{ + /** The minimum number of touches required for the gesture (default: 1) */ + NSUInteger _minimumNumberOfTouches; + /** The maximum number of touches allowed for the gesture (default: NSUIntegerMax) */ + NSUInteger _maximumNumberOfTouches; + /** The current translation of the pan gesture relative to the initial touch point */ + NSPoint _translation; + /** The velocity of the pan gesture in points per second */ + NSPoint _velocity; + /** The starting location of the pan gesture */ + NSPoint _startLocation; + /** The previous location for velocity calculation */ + NSPoint _previousLocation; + /** The timestamp of the previous location update */ + NSTimeInterval _previousTime; + /** Flag indicating whether the gesture is currently in progress */ + BOOL _isTracking; + /** The button mask for which mouse button triggers the pan */ + NSUInteger _buttonMask; +} + +/** + * Returns the minimum number of touches required to trigger the pan gesture. + * The default value is 1. + * Returns: The minimum number of touches required + */ +- (NSUInteger)minimumNumberOfTouches; + +/** + * Sets the minimum number of touches required to trigger the pan gesture. + * touches: The minimum number of touches (must be >= 1) + */ +- (void)setMinimumNumberOfTouches:(NSUInteger)touches; + +/** + * Returns the maximum number of touches allowed for the pan gesture. + * The default value is NSUIntegerMax (unlimited). + * Returns: The maximum number of touches allowed + */ +- (NSUInteger)maximumNumberOfTouches; + +/** + * Sets the maximum number of touches allowed for the pan gesture. + * touches: The maximum number of touches (must be >= minimumNumberOfTouches) + */ +- (void)setMaximumNumberOfTouches:(NSUInteger)touches; + +/** + * Returns the translation of the pan gesture in the coordinate system of the specified view. + * If view is nil, returns the translation in the gesture recognizer's view coordinates. + * view: The view in whose coordinate system the translation should be returned + * Returns: The translation as an NSPoint + */ +- (NSPoint)translationInView:(NSView *)view; + +/** + * Sets the translation value for the pan gesture in the coordinate system of the specified view. + * This allows you to reset or adjust the translation during a pan. + * translation: The new translation value + * view: The view in whose coordinate system the translation is specified + */ +- (void)setTranslation:(NSPoint)translation inView:(NSView *)view; + +/** + * Returns the velocity of the pan gesture in the coordinate system of the specified view. + * The velocity is measured in points per second. + * view: The view in whose coordinate system the velocity should be returned + * Returns: The velocity as an NSPoint with x and y components in points per second + */ +- (NSPoint)velocityInView:(NSView *)view; + +/** + * Returns the mouse button mask that triggers this pan gesture. + * The default value is 1 (left mouse button). + * Returns: The mouse button mask for this gesture + */ +- (NSUInteger)buttonMask; + +/** + * Sets the mouse button mask that triggers this pan gesture. + * Use 1 for left button, 2 for right button, 4 for middle button, etc. + * mask: The mouse button mask to set + */ +- (void)setButtonMask:(NSUInteger)mask; @end diff --git a/Source/NSClickGestureRecognizer.m b/Source/NSClickGestureRecognizer.m index c75c5c01df..c06f14f4a5 100644 --- a/Source/NSClickGestureRecognizer.m +++ b/Source/NSClickGestureRecognizer.m @@ -1,21 +1,21 @@ /* Implementation of class NSClickGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -23,8 +23,260 @@ */ #import +#import +#import @implementation NSClickGestureRecognizer +// Initialization + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self != nil) + { + _buttonMask = 1; // Default to left mouse button + _numberOfClicksRequired = 1; // Default to single click + _numberOfTouchesRequired = 1; // Default to single touch + } + return self; +} + +- (instancetype)init +{ + return [self initWithTarget:nil action:NULL]; +} + +// Button Mask Property + +- (NSUInteger)buttonMask +{ + return _buttonMask; +} + +- (void)setButtonMask:(NSUInteger)mask +{ + _buttonMask = mask; +} + +// Number of Clicks Required Property + +- (NSUInteger)numberOfClicksRequired +{ + return _numberOfClicksRequired; +} + +- (void)setNumberOfClicksRequired:(NSUInteger)clicks +{ + if (clicks >= 1) + { + _numberOfClicksRequired = clicks; + } +} + +// Number of Touches Required Property + +- (NSUInteger)numberOfTouchesRequired +{ + return _numberOfTouchesRequired; +} + +- (void)setNumberOfTouchesRequired:(NSUInteger)touches +{ + if (touches >= 1) + { + _numberOfTouchesRequired = touches; + } +} + +// Gesture Recognition Event Handlers + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (![self isEnabled]) + { + return; + } + + // Check if the button matches our requirements + NSUInteger buttonNumber = [event buttonNumber]; + NSUInteger eventButtonMask = 1 << buttonNumber; + + if ((_buttonMask & eventButtonMask) == 0) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + // Check click count + NSInteger clickCount = [event clickCount]; + if (clickCount == _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStateRecognized]; + } + else if (clickCount < _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStatePossible]; + } + else + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + + if (![self isEnabled]) + { + return; + } + + // Right mouse button has button number 1 + NSUInteger eventButtonMask = 1 << 1; + + if ((_buttonMask & eventButtonMask) == 0) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + // Check click count + NSInteger clickCount = [event clickCount]; + if (clickCount == _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStateRecognized]; + } + else if (clickCount < _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStatePossible]; + } + else + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + + if (![self isEnabled]) + { + return; + } + + // Other mouse buttons start from button number 2 + NSUInteger buttonNumber = [event buttonNumber]; + NSUInteger eventButtonMask = 1 << buttonNumber; + + if ((_buttonMask & eventButtonMask) == 0) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + // Check click count + NSInteger clickCount = [event clickCount]; + if (clickCount == _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStateRecognized]; + } + else if (clickCount < _numberOfClicksRequired) + { + [self setState:NSGestureRecognizerStatePossible]; + } + else + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + // If we're in possible state and user starts dragging, fail the gesture + if ([self state] == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [super rightMouseDragged:event]; + + // If we're in possible state and user starts dragging, fail the gesture + if ([self state] == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [super otherMouseDragged:event]; + + // If we're in possible state and user starts dragging, fail the gesture + if ([self state] == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +// NSCoding Support + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_buttonMask] forKey:@"NSClickGestureRecognizer.buttonMask"]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_numberOfClicksRequired] forKey:@"NSClickGestureRecognizer.numberOfClicksRequired"]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_numberOfTouchesRequired] forKey:@"NSClickGestureRecognizer.numberOfTouchesRequired"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self != nil) + { + NSNumber *buttonMask = [coder decodeObjectForKey:@"NSClickGestureRecognizer.buttonMask"]; + _buttonMask = buttonMask ? [buttonMask unsignedIntegerValue] : 1; + + NSNumber *clicksRequired = [coder decodeObjectForKey:@"NSClickGestureRecognizer.numberOfClicksRequired"]; + _numberOfClicksRequired = clicksRequired ? [clicksRequired unsignedIntegerValue] : 1; + + NSNumber *touchesRequired = [coder decodeObjectForKey:@"NSClickGestureRecognizer.numberOfTouchesRequired"]; + _numberOfTouchesRequired = touchesRequired ? [touchesRequired unsignedIntegerValue] : 1; + } + return self; +} + +// NSCopying Support + +- (id)copyWithZone:(NSZone *)zone +{ + NSClickGestureRecognizer *copy = [super copyWithZone:zone]; + if (copy != nil) + { + copy->_buttonMask = _buttonMask; + copy->_numberOfClicksRequired = _numberOfClicksRequired; + copy->_numberOfTouchesRequired = _numberOfTouchesRequired; + } + return copy; +} + +// Description + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; state = %ld; buttonMask = %lu; numberOfClicksRequired = %lu; numberOfTouchesRequired = %lu>", + [self class], self, (long)[self state], (unsigned long)_buttonMask, + (unsigned long)_numberOfClicksRequired, (unsigned long)_numberOfTouchesRequired]; +} + @end diff --git a/Source/NSPanGestureRecognizer.m b/Source/NSPanGestureRecognizer.m index 1e16778123..73f66eeb76 100644 --- a/Source/NSPanGestureRecognizer.m +++ b/Source/NSPanGestureRecognizer.m @@ -1,21 +1,21 @@ /* Implementation of class NSPanGestureRecognizer Copyright (C) 2019 Free Software Foundation, Inc. - + By: Gregory John Casamento Date: Thu Dec 5 12:54:21 EST 2019 This file is part of the GNUstep Library. - + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. - + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, @@ -23,8 +23,493 @@ */ #import +#import +#import +#import +#import +#include @implementation NSPanGestureRecognizer +// Initialization + +- (instancetype)initWithTarget:(id)target action:(SEL)action +{ + self = [super initWithTarget:target action:action]; + if (self != nil) + { + _minimumNumberOfTouches = 1; + _maximumNumberOfTouches = NSUIntegerMax; + _translation = NSZeroPoint; + _velocity = NSZeroPoint; + _startLocation = NSZeroPoint; + _previousLocation = NSZeroPoint; + _previousTime = 0.0; + _isTracking = NO; + _buttonMask = 1; // Default to left mouse button + } + return self; +} + +- (instancetype)init +{ + return [self initWithTarget:nil action:NULL]; +} + +// Touch Count Properties + +- (NSUInteger)minimumNumberOfTouches +{ + return _minimumNumberOfTouches; +} + +- (void)setMinimumNumberOfTouches:(NSUInteger)touches +{ + if (touches >= 1) + { + _minimumNumberOfTouches = touches; + if (_maximumNumberOfTouches < _minimumNumberOfTouches) + { + _maximumNumberOfTouches = _minimumNumberOfTouches; + } + } +} + +- (NSUInteger)maximumNumberOfTouches +{ + return _maximumNumberOfTouches; +} + +- (void)setMaximumNumberOfTouches:(NSUInteger)touches +{ + if (touches >= _minimumNumberOfTouches) + { + _maximumNumberOfTouches = touches; + } +} + +// Button Mask Property + +- (NSUInteger)buttonMask +{ + return _buttonMask; +} + +- (void)setButtonMask:(NSUInteger)mask +{ + _buttonMask = mask; +} + +// Translation Methods + +- (NSPoint)translationInView:(NSView *)view +{ + if (view == nil || view == [self view]) + { + return _translation; + } + + // Convert translation to the specified view's coordinate system + NSView *gestureView = [self view]; + if (gestureView != nil) + { + NSPoint convertedTranslation = [view convertPoint:_translation fromView:gestureView]; + NSPoint basePoint = [view convertPoint:NSZeroPoint fromView:gestureView]; + return NSMakePoint(convertedTranslation.x - basePoint.x, convertedTranslation.y - basePoint.y); + } + + return _translation; +} + +- (void)setTranslation:(NSPoint)translation inView:(NSView *)view +{ + if (view == nil || view == [self view]) + { + _translation = translation; + } + else + { + // Convert translation from the specified view's coordinate system + NSView *gestureView = [self view]; + if (gestureView != nil) + { + NSPoint convertedTranslation = [gestureView convertPoint:translation fromView:view]; + NSPoint basePoint = [gestureView convertPoint:NSZeroPoint fromView:view]; + _translation = NSMakePoint(convertedTranslation.x - basePoint.x, convertedTranslation.y - basePoint.y); + } + else + { + _translation = translation; + } + } +} + +// Velocity Methods + +- (NSPoint)velocityInView:(NSView *)view +{ + if (view == nil || view == [self view]) + { + return _velocity; + } + + // Convert velocity to the specified view's coordinate system + NSView *gestureView = [self view]; + if (gestureView != nil) + { + // For velocity, we only need to account for scaling, not translation + NSPoint unitPoint = NSMakePoint(1.0, 1.0); + NSPoint convertedUnit = [view convertPoint:unitPoint fromView:gestureView]; + NSPoint basePoint = [view convertPoint:NSZeroPoint fromView:gestureView]; + CGFloat xScale = convertedUnit.x - basePoint.x; + CGFloat yScale = convertedUnit.y - basePoint.y; + return NSMakePoint(_velocity.x * xScale, _velocity.y * yScale); + } + + return _velocity; +} + +// Private Methods + +- (void)_updateTranslationAndVelocityWithEvent:(NSEvent *)event +{ + NSPoint currentLocation = [event locationInWindow]; + NSTimeInterval currentTime = [event timestamp]; + + if (!_isTracking) + { + _startLocation = currentLocation; + _previousLocation = currentLocation; + _previousTime = currentTime; + _translation = NSZeroPoint; + _velocity = NSZeroPoint; + _isTracking = YES; + } + else + { + // Update translation + _translation = NSMakePoint(currentLocation.x - _startLocation.x, + currentLocation.y - _startLocation.y); + + // Update velocity + NSTimeInterval timeDelta = currentTime - _previousTime; + if (timeDelta > 0.0) + { + CGFloat deltaX = currentLocation.x - _previousLocation.x; + CGFloat deltaY = currentLocation.y - _previousLocation.y; + _velocity = NSMakePoint(deltaX / timeDelta, deltaY / timeDelta); + } + + _previousLocation = currentLocation; + _previousTime = currentTime; + } +} + +- (void)_resetPanTracking +{ + _translation = NSZeroPoint; + _velocity = NSZeroPoint; + _startLocation = NSZeroPoint; + _previousLocation = NSZeroPoint; + _previousTime = 0.0; + _isTracking = NO; +} + +- (BOOL)_shouldRecognizeButtonForEvent:(NSEvent *)event +{ + NSUInteger buttonNumber = 0; + NSEventType eventType = [event type]; + + switch (eventType) + { + case NSLeftMouseDown: + case NSLeftMouseDragged: + case NSLeftMouseUp: + buttonNumber = 0; + break; + case NSRightMouseDown: + case NSRightMouseDragged: + case NSRightMouseUp: + buttonNumber = 1; + break; + case NSOtherMouseDown: + case NSOtherMouseDragged: + case NSOtherMouseUp: + buttonNumber = [event buttonNumber]; + break; + default: + return NO; + } + + NSUInteger eventButtonMask = 1 << buttonNumber; + return (_buttonMask & eventButtonMask) != 0; +} + +// Gesture Recognition Event Handlers + +- (void)mouseDown:(NSEvent *)event +{ + [super mouseDown:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + [self setState:NSGestureRecognizerStatePossible]; + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)rightMouseDown:(NSEvent *)event +{ + [super rightMouseDown:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + [self setState:NSGestureRecognizerStatePossible]; + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)otherMouseDown:(NSEvent *)event +{ + [super otherMouseDown:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + [self setState:NSGestureRecognizerStateFailed]; + return; + } + + [self setState:NSGestureRecognizerStatePossible]; + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)mouseDragged:(NSEvent *)event +{ + [super mouseDragged:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateBegan]; + } + else if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateChanged]; + } + + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)rightMouseDragged:(NSEvent *)event +{ + [super rightMouseDragged:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateBegan]; + } + else if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateChanged]; + } + + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)otherMouseDragged:(NSEvent *)event +{ + [super otherMouseDragged:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateBegan]; + } + else if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateChanged]; + } + + [self _updateTranslationAndVelocityWithEvent:event]; +} + +- (void)mouseUp:(NSEvent *)event +{ + [super mouseUp:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateEnded]; + } + else if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)rightMouseUp:(NSEvent *)event +{ + [super rightMouseUp:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateEnded]; + } + else if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +- (void)otherMouseUp:(NSEvent *)event +{ + [super otherMouseUp:event]; + + if (![self isEnabled] || ![self _shouldRecognizeButtonForEvent:event]) + { + return; + } + + NSGestureRecognizerState currentState = [self state]; + + if (currentState == NSGestureRecognizerStateBegan || + currentState == NSGestureRecognizerStateChanged) + { + [self setState:NSGestureRecognizerStateEnded]; + } + else if (currentState == NSGestureRecognizerStatePossible) + { + [self setState:NSGestureRecognizerStateFailed]; + } +} + +// Reset Method + +- (void)reset +{ + [super reset]; + [self _resetPanTracking]; +} + +// NSCoding Support + +- (void)encodeWithCoder:(NSCoder *)coder +{ + [super encodeWithCoder:coder]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_minimumNumberOfTouches] + forKey:@"NSPanGestureRecognizer.minimumNumberOfTouches"]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_maximumNumberOfTouches] + forKey:@"NSPanGestureRecognizer.maximumNumberOfTouches"]; + [coder encodeObject:[NSNumber numberWithUnsignedInteger:_buttonMask] + forKey:@"NSPanGestureRecognizer.buttonMask"]; + [coder encodeObject:[NSValue valueWithPoint:_translation] + forKey:@"NSPanGestureRecognizer.translation"]; + [coder encodeObject:[NSValue valueWithPoint:_velocity] + forKey:@"NSPanGestureRecognizer.velocity"]; +} + +- (instancetype)initWithCoder:(NSCoder *)coder +{ + self = [super initWithCoder:coder]; + if (self != nil) + { + NSNumber *minTouches = [coder decodeObjectForKey:@"NSPanGestureRecognizer.minimumNumberOfTouches"]; + _minimumNumberOfTouches = minTouches ? [minTouches unsignedIntegerValue] : 1; + + NSNumber *maxTouches = [coder decodeObjectForKey:@"NSPanGestureRecognizer.maximumNumberOfTouches"]; + _maximumNumberOfTouches = maxTouches ? [maxTouches unsignedIntegerValue] : NSUIntegerMax; + + NSNumber *buttonMask = [coder decodeObjectForKey:@"NSPanGestureRecognizer.buttonMask"]; + _buttonMask = buttonMask ? [buttonMask unsignedIntegerValue] : 1; + + NSValue *translation = [coder decodeObjectForKey:@"NSPanGestureRecognizer.translation"]; + _translation = translation ? [translation pointValue] : NSZeroPoint; + + NSValue *velocity = [coder decodeObjectForKey:@"NSPanGestureRecognizer.velocity"]; + _velocity = velocity ? [velocity pointValue] : NSZeroPoint; + + _startLocation = NSZeroPoint; + _previousLocation = NSZeroPoint; + _previousTime = 0.0; + _isTracking = NO; + } + return self; +} + +// NSCopying Support + +- (id)copyWithZone:(NSZone *)zone +{ + NSPanGestureRecognizer *copy = [super copyWithZone:zone]; + if (copy != nil) + { + copy->_minimumNumberOfTouches = _minimumNumberOfTouches; + copy->_maximumNumberOfTouches = _maximumNumberOfTouches; + copy->_buttonMask = _buttonMask; + copy->_translation = _translation; + copy->_velocity = _velocity; + copy->_startLocation = _startLocation; + copy->_previousLocation = _previousLocation; + copy->_previousTime = _previousTime; + copy->_isTracking = _isTracking; + } + return copy; +} + +// Description + +- (NSString *)description +{ + return [NSString stringWithFormat:@"<%@: %p; state = %ld; translation = {%f, %f}; velocity = {%f, %f}; minTouches = %lu; maxTouches = %lu; buttonMask = %lu>", + [self class], self, (long)[self state], + _translation.x, _translation.y, _velocity.x, _velocity.y, + (unsigned long)_minimumNumberOfTouches, (unsigned long)_maximumNumberOfTouches, + (unsigned long)_buttonMask]; +} + @end From cb6ff51d34a2dc7fe9b1fcd0efcd6856dc82c4ed Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 18:22:06 -0400 Subject: [PATCH 09/12] Change the API version the recognizers are available in --- Headers/AppKit/NSGestureRecognizer.h | 2 +- Headers/AppKit/NSMagnificationGestureRecognizer.h | 2 +- Headers/AppKit/NSPanGestureRecognizer.h | 2 +- Headers/AppKit/NSPressGestureRecognizer.h | 2 +- Headers/AppKit/NSRotationGestureRecognizer.h | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Headers/AppKit/NSGestureRecognizer.h b/Headers/AppKit/NSGestureRecognizer.h index 286a1f1e5a..1e4b5c6dae 100644 --- a/Headers/AppKit/NSGestureRecognizer.h +++ b/Headers/AppKit/NSGestureRecognizer.h @@ -37,7 +37,7 @@ #import #import -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) #if defined(__cplusplus) extern "C" { diff --git a/Headers/AppKit/NSMagnificationGestureRecognizer.h b/Headers/AppKit/NSMagnificationGestureRecognizer.h index fbf24e8478..ed6f20debd 100644 --- a/Headers/AppKit/NSMagnificationGestureRecognizer.h +++ b/Headers/AppKit/NSMagnificationGestureRecognizer.h @@ -29,7 +29,7 @@ #import #import -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) #if defined(__cplusplus) extern "C" { diff --git a/Headers/AppKit/NSPanGestureRecognizer.h b/Headers/AppKit/NSPanGestureRecognizer.h index df5f583c36..c653823043 100644 --- a/Headers/AppKit/NSPanGestureRecognizer.h +++ b/Headers/AppKit/NSPanGestureRecognizer.h @@ -28,7 +28,7 @@ #import -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) #if defined(__cplusplus) extern "C" { diff --git a/Headers/AppKit/NSPressGestureRecognizer.h b/Headers/AppKit/NSPressGestureRecognizer.h index 87bc3af25b..6487399204 100644 --- a/Headers/AppKit/NSPressGestureRecognizer.h +++ b/Headers/AppKit/NSPressGestureRecognizer.h @@ -28,7 +28,7 @@ #import -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) @class NSTimer; diff --git a/Headers/AppKit/NSRotationGestureRecognizer.h b/Headers/AppKit/NSRotationGestureRecognizer.h index f8153ff6c0..87ae2be502 100644 --- a/Headers/AppKit/NSRotationGestureRecognizer.h +++ b/Headers/AppKit/NSRotationGestureRecognizer.h @@ -29,7 +29,7 @@ #import #import -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) #if defined(__cplusplus) extern "C" { From b6389adacdaf11b463c9a7e4ca12f0e06cd7086d Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 18:42:41 -0400 Subject: [PATCH 10/12] Formatting changes --- Source/NSClickGestureRecognizer.m | 98 +++++++++++------------ Source/NSMagnificationGestureRecognizer.m | 73 +++++++++-------- 2 files changed, 85 insertions(+), 86 deletions(-) diff --git a/Source/NSClickGestureRecognizer.m b/Source/NSClickGestureRecognizer.m index c06f14f4a5..abbaae63ee 100644 --- a/Source/NSClickGestureRecognizer.m +++ b/Source/NSClickGestureRecognizer.m @@ -32,7 +32,7 @@ @implementation NSClickGestureRecognizer - (instancetype)initWithTarget:(id)target action:(SEL)action { - self = [super initWithTarget:target action:action]; + self = [super initWithTarget: target action: action]; if (self != nil) { _buttonMask = 1; // Default to left mouse button @@ -44,29 +44,29 @@ - (instancetype)initWithTarget:(id)target action:(SEL)action - (instancetype)init { - return [self initWithTarget:nil action:NULL]; + return [self initWithTarget: nil action: NULL]; } // Button Mask Property -- (NSUInteger)buttonMask +- (NSUInteger) buttonMask { return _buttonMask; } -- (void)setButtonMask:(NSUInteger)mask +- (void) setButtonMask:(NSUInteger)mask { _buttonMask = mask; } // Number of Clicks Required Property -- (NSUInteger)numberOfClicksRequired +- (NSUInteger) numberOfClicksRequired { return _numberOfClicksRequired; } -- (void)setNumberOfClicksRequired:(NSUInteger)clicks +- (void) setNumberOfClicksRequired:(NSUInteger)clicks { if (clicks >= 1) { @@ -76,12 +76,12 @@ - (void)setNumberOfClicksRequired:(NSUInteger)clicks // Number of Touches Required Property -- (NSUInteger)numberOfTouchesRequired +- (NSUInteger) numberOfTouchesRequired { return _numberOfTouchesRequired; } -- (void)setNumberOfTouchesRequired:(NSUInteger)touches +- (void) setNumberOfTouchesRequired:(NSUInteger)touches { if (touches >= 1) { @@ -91,9 +91,9 @@ - (void)setNumberOfTouchesRequired:(NSUInteger)touches // Gesture Recognition Event Handlers -- (void)mouseDown:(NSEvent *)event +- (void) mouseDown:(NSEvent *)event { - [super mouseDown:event]; + [super mouseDown: event]; if (![self isEnabled]) { @@ -106,7 +106,7 @@ - (void)mouseDown:(NSEvent *)event if ((_buttonMask & eventButtonMask) == 0) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; return; } @@ -114,21 +114,21 @@ - (void)mouseDown:(NSEvent *)event NSInteger clickCount = [event clickCount]; if (clickCount == _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStateRecognized]; + [self setState: NSGestureRecognizerStateRecognized]; } else if (clickCount < _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStatePossible]; + [self setState: NSGestureRecognizerStatePossible]; } else { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } -- (void)rightMouseDown:(NSEvent *)event +- (void) rightMouseDown:(NSEvent *)event { - [super rightMouseDown:event]; + [super rightMouseDown: event]; if (![self isEnabled]) { @@ -140,7 +140,7 @@ - (void)rightMouseDown:(NSEvent *)event if ((_buttonMask & eventButtonMask) == 0) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; return; } @@ -148,21 +148,21 @@ - (void)rightMouseDown:(NSEvent *)event NSInteger clickCount = [event clickCount]; if (clickCount == _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStateRecognized]; + [self setState: NSGestureRecognizerStateRecognized]; } else if (clickCount < _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStatePossible]; + [self setState: NSGestureRecognizerStatePossible]; } else { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } -- (void)otherMouseDown:(NSEvent *)event +- (void) otherMouseDown:(NSEvent *)event { - [super otherMouseDown:event]; + [super otherMouseDown: event]; if (![self isEnabled]) { @@ -175,7 +175,7 @@ - (void)otherMouseDown:(NSEvent *)event if ((_buttonMask & eventButtonMask) == 0) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; return; } @@ -183,73 +183,73 @@ - (void)otherMouseDown:(NSEvent *)event NSInteger clickCount = [event clickCount]; if (clickCount == _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStateRecognized]; + [self setState: NSGestureRecognizerStateRecognized]; } else if (clickCount < _numberOfClicksRequired) { - [self setState:NSGestureRecognizerStatePossible]; + [self setState: NSGestureRecognizerStatePossible]; } else { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } -- (void)mouseDragged:(NSEvent *)event +- (void) mouseDragged:(NSEvent *)event { - [super mouseDragged:event]; + [super mouseDragged: event]; // If we're in possible state and user starts dragging, fail the gesture if ([self state] == NSGestureRecognizerStatePossible) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } -- (void)rightMouseDragged:(NSEvent *)event +- (void) rightMouseDragged:(NSEvent *)event { - [super rightMouseDragged:event]; + [super rightMouseDragged: event]; // If we're in possible state and user starts dragging, fail the gesture if ([self state] == NSGestureRecognizerStatePossible) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } -- (void)otherMouseDragged:(NSEvent *)event +- (void) otherMouseDragged:(NSEvent *)event { - [super otherMouseDragged:event]; + [super otherMouseDragged: event]; // If we're in possible state and user starts dragging, fail the gesture if ([self state] == NSGestureRecognizerStatePossible) { - [self setState:NSGestureRecognizerStateFailed]; + [self setState: NSGestureRecognizerStateFailed]; } } // NSCoding Support -- (void)encodeWithCoder:(NSCoder *)coder +- (void) encodeWithCoder:(NSCoder *)coder { - [super encodeWithCoder:coder]; - [coder encodeObject:[NSNumber numberWithUnsignedInteger:_buttonMask] forKey:@"NSClickGestureRecognizer.buttonMask"]; - [coder encodeObject:[NSNumber numberWithUnsignedInteger:_numberOfClicksRequired] forKey:@"NSClickGestureRecognizer.numberOfClicksRequired"]; - [coder encodeObject:[NSNumber numberWithUnsignedInteger:_numberOfTouchesRequired] forKey:@"NSClickGestureRecognizer.numberOfTouchesRequired"]; + [super encodeWithCoder: coder]; + [coder encodeObject: [NSNumber numberWithUnsignedInteger: _buttonMask] forKey: @"NSClickGestureRecognizer.buttonMask"]; + [coder encodeObject: [NSNumber numberWithUnsignedInteger: _numberOfClicksRequired] forKey: @"NSClickGestureRecognizer.numberOfClicksRequired"]; + [coder encodeObject: [NSNumber numberWithUnsignedInteger: _numberOfTouchesRequired] forKey: @"NSClickGestureRecognizer.numberOfTouchesRequired"]; } -- (instancetype)initWithCoder:(NSCoder *)coder +- (instancetype) initWithCoder:(NSCoder *)coder { - self = [super initWithCoder:coder]; + self = [super initWithCoder: coder]; if (self != nil) { - NSNumber *buttonMask = [coder decodeObjectForKey:@"NSClickGestureRecognizer.buttonMask"]; + NSNumber *buttonMask = [coder decodeObjectForKey: @"NSClickGestureRecognizer.buttonMask"]; _buttonMask = buttonMask ? [buttonMask unsignedIntegerValue] : 1; - NSNumber *clicksRequired = [coder decodeObjectForKey:@"NSClickGestureRecognizer.numberOfClicksRequired"]; + NSNumber *clicksRequired = [coder decodeObjectForKey: @"NSClickGestureRecognizer.numberOfClicksRequired"]; _numberOfClicksRequired = clicksRequired ? [clicksRequired unsignedIntegerValue] : 1; - NSNumber *touchesRequired = [coder decodeObjectForKey:@"NSClickGestureRecognizer.numberOfTouchesRequired"]; + NSNumber *touchesRequired = [coder decodeObjectForKey: @"NSClickGestureRecognizer.numberOfTouchesRequired"]; _numberOfTouchesRequired = touchesRequired ? [touchesRequired unsignedIntegerValue] : 1; } return self; @@ -257,9 +257,9 @@ - (instancetype)initWithCoder:(NSCoder *)coder // NSCopying Support -- (id)copyWithZone:(NSZone *)zone +- (id) copyWithZone:(NSZone *)zone { - NSClickGestureRecognizer *copy = [super copyWithZone:zone]; + NSClickGestureRecognizer *copy = [super copyWithZone: zone]; if (copy != nil) { copy->_buttonMask = _buttonMask; @@ -271,9 +271,9 @@ - (id)copyWithZone:(NSZone *)zone // Description -- (NSString *)description +- (NSString *) description { - return [NSString stringWithFormat:@"<%@: %p; state = %ld; buttonMask = %lu; numberOfClicksRequired = %lu; numberOfTouchesRequired = %lu>", + return [NSString stringWithFormat: @"<%@: %p; state = %ld; buttonMask = %lu; numberOfClicksRequired = %lu; numberOfTouchesRequired = %lu>", [self class], self, (long)[self state], (unsigned long)_buttonMask, (unsigned long)_numberOfClicksRequired, (unsigned long)_numberOfTouchesRequired]; } diff --git a/Source/NSMagnificationGestureRecognizer.m b/Source/NSMagnificationGestureRecognizer.m index 9f7518d76e..418ea39b6b 100644 --- a/Source/NSMagnificationGestureRecognizer.m +++ b/Source/NSMagnificationGestureRecognizer.m @@ -48,9 +48,9 @@ + (void)initialize // Initialization -- (instancetype)initWithTarget:(id)target action:(SEL)action +- (instancetype)initWithTarget: (id)target action: (SEL)action { - self = [super initWithTarget:target action:action]; + self = [super initWithTarget: target action: action]; if (self) { _magnification = 0.0; @@ -90,9 +90,9 @@ - (void)reset [self _resetMagnificationTracking]; } -- (void)scrollWheel:(NSEvent *)event +- (void)scrollWheel: (NSEvent *)event { - [super scrollWheel:event]; + [super scrollWheel: event]; if (![self isEnabled]) { @@ -104,7 +104,7 @@ - (void)scrollWheel:(NSEvent *)event if (magnificationDelta != 0.0) { - [self _updateMagnificationWithEvent:event]; + [self _updateMagnificationWithEvent: event]; } else { @@ -121,7 +121,7 @@ - (void)scrollWheel:(NSEvent *)event [self state] == NSGestureRecognizerStateChanged) { // End gesture if no more magnification input - [self _setState:NSGestureRecognizerStateEnded]; + [self _setState: NSGestureRecognizerStateEnded]; } } } @@ -135,7 +135,7 @@ - (void)mouseDown:(NSEvent *)event return; } - _initialLocation = [self locationInView:[self view]]; + _initialLocation = [self locationInView: [self view]]; [self _resetMagnificationTracking]; } @@ -163,11 +163,11 @@ - (void)mouseDragged:(NSEvent *)event { _initialTime = [NSDate timeIntervalSinceReferenceDate]; _magnificationStarted = YES; - [self _setState:NSGestureRecognizerStateBegan]; + [self _setState: NSGestureRecognizerStateBegan]; } else { - [self _setState:NSGestureRecognizerStateChanged]; + [self _setState: NSGestureRecognizerStateChanged]; } _magnification = magnificationDelta; @@ -191,54 +191,54 @@ - (void)mouseUp:(NSEvent *)event { if (_magnificationStarted) { - [self _setState:NSGestureRecognizerStateEnded]; + [self _setState: NSGestureRecognizerStateEnded]; } else { - [self _setState:NSGestureRecognizerStateFailed]; + [self _setState: NSGestureRecognizerStateFailed]; } } } -- (void)rightMouseDown:(NSEvent *)event +- (void) rightMouseDown: (NSEvent *)event { - [super rightMouseDown:event]; - [self mouseDown:event]; + [super rightMouseDown: event]; + [self mouseDown: event]; } -- (void)rightMouseDragged:(NSEvent *)event +- (void) rightMouseDragged: (NSEvent *)event { - [super rightMouseDragged:event]; - [self mouseDragged:event]; + [super rightMouseDragged: event]; + [self mouseDragged: event]; } -- (void)rightMouseUp:(NSEvent *)event +- (void) rightMouseUp: (NSEvent *)event { - [super rightMouseUp:event]; - [self mouseUp:event]; + [super rightMouseUp: event]; + [self mouseUp: event]; } -- (void)otherMouseDown:(NSEvent *)event +- (void) otherMouseDown: (NSEvent *)event { - [super otherMouseDown:event]; - [self mouseDown:event]; + [super otherMouseDown: event]; + [self mouseDown: event]; } -- (void)otherMouseDragged:(NSEvent *)event +- (void) otherMouseDragged: (NSEvent *)event { - [super otherMouseDragged:event]; - [self mouseDragged:event]; + [super otherMouseDragged: event]; + [self mouseDragged: event]; } -- (void)otherMouseUp:(NSEvent *)event +- (void) otherMouseUp: (NSEvent *)event { - [super otherMouseUp:event]; - [self mouseUp:event]; + [super otherMouseUp: event]; + [self mouseUp: event]; } // Private Methods -- (void)_updateMagnificationWithEvent:(NSEvent *)event +- (void) _updateMagnificationWithEvent: (NSEvent *)event { CGFloat magnificationDelta = [event magnification]; @@ -261,11 +261,11 @@ - (void)_updateMagnificationWithEvent:(NSEvent *)event _initialTime = [NSDate timeIntervalSinceReferenceDate]; _magnificationStarted = YES; _cumulativeMagnification = 0.0; - [self _setState:NSGestureRecognizerStateBegan]; + [self _setState: NSGestureRecognizerStateBegan]; } else { - [self _setState:NSGestureRecognizerStateChanged]; + [self _setState: NSGestureRecognizerStateChanged]; } _magnification = magnificationDelta; @@ -280,7 +280,7 @@ - (void)_updateMagnificationWithEvent:(NSEvent *)event } } -- (void)_resetMagnificationTracking +- (void) _resetMagnificationTracking { _magnification = 0.0; _velocity = 0.0; @@ -294,9 +294,9 @@ - (void)_resetMagnificationTracking #pragma mark - NSCoding Protocol -- (instancetype)initWithCoder:(NSCoder *)coder +- (instancetype) initWithCoder: (NSCoder *)coder { - self = [super initWithCoder:coder]; + self = [super initWithCoder: coder]; if (self) { _magnification = 0.0; @@ -311,11 +311,10 @@ - (instancetype)initWithCoder:(NSCoder *)coder return self; } -- (void)encodeWithCoder:(NSCoder *)coder +- (void)encodeWithCoder: (NSCoder *)coder { [super encodeWithCoder:coder]; // Transient state is not encoded since it should not persist } @end - From a74d7f95f36ba31afcacd35528f2ce01001f4083 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Sun, 21 Sep 2025 18:46:20 -0400 Subject: [PATCH 11/12] Update Source/NSPressGestureRecognizer.m Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Source/NSPressGestureRecognizer.m | 2 -- 1 file changed, 2 deletions(-) diff --git a/Source/NSPressGestureRecognizer.m b/Source/NSPressGestureRecognizer.m index 91f655a945..260e504594 100644 --- a/Source/NSPressGestureRecognizer.m +++ b/Source/NSPressGestureRecognizer.m @@ -418,8 +418,6 @@ - (void)_invalidateTimer #pragma mark - NSCoding -#pragma mark - NSCoding Protocol - /** * Initializes a press gesture recognizer from encoded data during * deserialization. This method decodes the minimum press duration, From 3e70aa5f498f3db0bd5f0d7fd14af2c16775777b Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sun, 21 Sep 2025 19:02:33 -0400 Subject: [PATCH 12/12] Minor fixes --- Source/NSMagnificationGestureRecognizer.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSMagnificationGestureRecognizer.m b/Source/NSMagnificationGestureRecognizer.m index 418ea39b6b..ae15965632 100644 --- a/Source/NSMagnificationGestureRecognizer.m +++ b/Source/NSMagnificationGestureRecognizer.m @@ -28,7 +28,7 @@ #import #import -@interface NSMagnificationGestureRecognizer () +@interface NSMagnificationGestureRecognizer (Private) - (void)_setState:(NSGestureRecognizerState)state; - (void)_updateMagnificationWithEvent:(NSEvent *)event; - (void)_resetMagnificationTracking;