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;