Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  VLCCarouselView.m  Sprache: unbekannt

 
Spracherkennung für: .m vermutete Sprache: Cobol {Cobol[64] C[60] CS[60]} [Methode: maximale Elemente, drei Dimensionen]

/*****************************************************************************
 * VLCCarouselView.m: MacOS X interface module
 *****************************************************************************
 * Copyright (C) 2026 VLC authors and VideoLAN
 *
 * Authors: Serhii Bykov <esphynox@gmail.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *****************************************************************************/

#import "VLCCarouselView.h"

#import "extensions/NSAnimationContext+VLCAdditions.h"

#import <QuartzCore/QuartzCore.h>

#include <math.h>

// Minimum mouse movement, in points, before a click becomes a drag.
static const CGFloat VLCCarouselMouseDragThreshold = 3.;
// Minimum horizontal release velocity, in points per second, that starts inertia.
static const CGFloat VLCCarouselMinimumInertialVelocity = 40.;
// Horizontal velocity, in points per second, at which inertia stops.
static const CGFloat VLCCarouselDecelerationStopVelocity = 8.;
// Remaining velocity proportion after one second of deceleration.
static const CGFloat VLCCarouselDecelerationRate = .02;
// Time, in seconds, between inertial scrolling updates.
static const NSTimeInterval VLCCarouselDecelerationInterval = 1. / 60.;
// Duration, in seconds, of animated item-to-item navigation.
static const NSTimeInterval VLCCarouselItemScrollDuration = .2;
// Weight of the previous velocity estimate when smoothing mouse drag velocity.
static const CGFloat VLCCarouselDragVelocitySmoothingFactor = .75;
// Minimum horizontal scroll delta handled by the carousel.
static const CGFloat VLCCarouselMinimumScrollWheelDelta = .1;
// Minimum vertical scroll delta forwarded to the enclosing scroll view.
static const CGFloat VLCCarouselMinimumVerticalScrollDelta = .5;
// Distance, in points, used when choosing the next item to navigate to.
static const CGFloat VLCCarouselItemNavigationTolerance = 1.;
// Minimum movement, in points, before deceleration stops at a content edge.
static const CGFloat VLCCarouselDecelerationEdgeTolerance = .01;

@implementation VLCCarouselView
{
    NSTimer *_decelerationTimer;
    CGFloat _decelerationVelocity;
    CFTimeInterval _lastDecelerationTime;
}

#pragma mark - Lifecycle

- (void)dealloc
{
    [self stopDecelerating];
}

#pragma mark - Public methods

- (void)scrollByOneItemForward:(BOOL)forward
{
    [self stopDecelerating];

    NSScrollView * const scrollView = self.enclosingScrollView;
    if (scrollView == nil) {
        return;
    }

    const CGFloat visibleOffset = NSMinX(scrollView.documentVisibleRect);
    NSArray<NSCollectionViewLayoutAttributes *> * const attributes = self.itemLayoutAttributes;
    NSCollectionViewLayoutAttributes *targetAttributes = nil;
    NSEnumerator * const enumerator = forward ? attributes.objectEnumerator : attributes.reverseObjectEnumerator;
    for (NSCollectionViewLayoutAttributes * const attributes in enumerator) {
        const CGFloat itemOffset = MAX(NSMinX(attributes.frame) - self.leadingItemInset,
                                       NSMinX(self.frame));
        if ((forward && itemOffset > visibleOffset + VLCCarouselItemNavigationTolerance) ||
            (!forward && itemOffset < visibleOffset - VLCCarouselItemNavigationTolerance)) {
            targetAttributes = attributes;
            break;
        }
    }

    if (targetAttributes != nil) {
        const CGFloat targetOffset = MAX(NSMinX(targetAttributes.frame) - self.leadingItemInset,
                                         NSMinX(self.frame));
        [self scrollToHorizontalOffset:targetOffset
                              animated:YES
                              duration:VLCCarouselItemScrollDuration];
    }
}

#pragma mark - Event handling

- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    NSString * const characters = event.charactersIgnoringModifiers;
    if (characters.length == 0) {
        [super keyDown:event];
        return;
    }

    switch ([characters characterAtIndex:0]) {
        case NSLeftArrowFunctionKey:
            [self scrollByOneItemForward:NO];
            return;
        case NSRightArrowFunctionKey:
            [self scrollByOneItemForward:YES];
            return;
        default:
            [super keyDown:event];
            return;
    }
}

- (void)scrollWheel:(NSEvent *)event
{
    [self stopDecelerating];

    const CGFloat horizontalDelta = event.scrollingDeltaX;
    const CGFloat verticalDelta = event.scrollingDeltaY;
    const BOOL isTrackpadGesture = event.hasPreciseScrollingDeltas &&
                                  (event.phase != NSEventPhaseNone || event.momentumPhase != NSEventPhaseNone);

    if (fabs(horizontalDelta) > fabs(verticalDelta) &&
        fabs(horizontalDelta) > VLCCarouselMinimumScrollWheelDelta) {
        if (isTrackpadGesture) {
            [self scrollByHorizontalDelta:horizontalDelta];
        } else {
            [self scrollByOneItemForward:horizontalDelta < 0.];
        }
        return;
    }

    if (fabs(verticalDelta) > VLCCarouselMinimumVerticalScrollDelta &&
        fabs(horizontalDelta) <= VLCCarouselMinimumScrollWheelDelta) {
        [super scrollWheel:event];
    }
}

- (void)mouseDown:(NSEvent *)event
{
    [self stopDecelerating];

    const NSPoint downPoint = [self convertPoint:event.locationInWindow fromView:nil];
    NSIndexPath * const clickedIndexPath = [self indexPathForItemAtPoint:downPoint];
    if (clickedIndexPath != nil) {
        [self.window makeFirstResponder:self];
    }
    NSTimeInterval previousTimestamp = event.timestamp;
    CGFloat horizontalVelocity = 0.;
    BOOL didDrag = NO;
    NSEvent *mouseUpEvent = nil;

    while (true) {
        NSEvent * const trackedEvent =
            [self.window nextEventMatchingMask:NSEventMaskLeftMouseDragged | NSEventMaskLeftMouseUp
                                      untilDate:NSDate.distantFuture
                                         inMode:NSEventTrackingRunLoopMode
                                        dequeue:YES];
        const NSPoint point = [self convertPoint:trackedEvent.locationInWindow fromView:nil];

        if (trackedEvent.type == NSEventTypeLeftMouseDragged) {
            const NSTimeInterval elapsed = trackedEvent.timestamp - previousTimestamp;
            if (elapsed > 0.) {
                const CGFloat instantVelocity = trackedEvent.deltaX / elapsed;
                horizontalVelocity = horizontalVelocity * VLCCarouselDragVelocitySmoothingFactor +
                    instantVelocity * (1. - VLCCarouselDragVelocitySmoothingFactor);
            }
            previousTimestamp = trackedEvent.timestamp;

            const CGFloat horizontalDistance = point.x - downPoint.x;
            const CGFloat verticalDistance = point.y - downPoint.y;
            if (!didDrag && hypot(horizontalDistance, verticalDistance) >= VLCCarouselMouseDragThreshold) {
                didDrag = YES;
            }
            if (didDrag) {
                [self scrollByHorizontalDelta:trackedEvent.deltaX];
            }
            continue;
        }

        mouseUpEvent = trackedEvent;
        break;
    }

    if (!didDrag && clickedIndexPath != nil) {
        if (mouseUpEvent.modifierFlags & NSEventModifierFlagControl) {
            [self requestContextMenuForItemAtIndexPath:clickedIndexPath event:mouseUpEvent];
            return;
        }
        [self activateItemAtIndexPath:clickedIndexPath];
    } else if (didDrag && fabs(horizontalVelocity) >= VLCCarouselMinimumInertialVelocity) {
        [self startDeceleratingWithVelocity:horizontalVelocity];
    }
}

- (void)rightMouseDown:(NSEvent *)event
{
    const NSPoint point = [self convertPoint:event.locationInWindow fromView:nil];
    NSIndexPath * const indexPath = [self indexPathForItemAtPoint:point];

    if (indexPath != nil && [self requestContextMenuForItemAtIndexPath:indexPath event:event]) {
        return;
    }

    [super rightMouseDown:event];
}

#pragma mark - Subclass hooks

- (void)activateItemAtIndexPath:(NSIndexPath *)indexPath
{
}

- (BOOL)requestContextMenuForItemAtIndexPath:(NSIndexPath *)indexPath event:(NSEvent *)event
{
    return NO;
}

#pragma mark - Inertial scrolling

- (void)startDeceleratingWithVelocity:(CGFloat)velocity
{
    [self stopDecelerating];
    _decelerationVelocity = velocity;
    _lastDecelerationTime = CACurrentMediaTime();

    __weak typeof(self) weakSelf = self;
    _decelerationTimer = [NSTimer scheduledTimerWithTimeInterval:VLCCarouselDecelerationInterval
                                                            repeats:YES
                                                              block:^(__unused NSTimer *timer) {
        [weakSelf decelerate];
    }];
}

- (void)decelerate
{
    const CFTimeInterval currentTime = CACurrentMediaTime();
    const CFTimeInterval elapsed = currentTime - _lastDecelerationTime;
    _lastDecelerationTime = currentTime;

    NSScrollView * const scrollView = self.enclosingScrollView;
    const CGFloat previousOffset = NSMinX(scrollView.documentVisibleRect);
    [self scrollByHorizontalDelta:_decelerationVelocity * elapsed];
    const CGFloat currentOffset = NSMinX(scrollView.documentVisibleRect);

    _decelerationVelocity *= pow(VLCCarouselDecelerationRate, elapsed);
    if (fabs(_decelerationVelocity) < VLCCarouselDecelerationStopVelocity ||
        fabs(currentOffset - previousOffset) < VLCCarouselDecelerationEdgeTolerance) {
        [self stopDecelerating];
    }
}

- (void)stopDecelerating
{
    [_decelerationTimer invalidate];
    _decelerationTimer = nil;
}

#pragma mark - Scrolling helpers

- (void)scrollByHorizontalDelta:(CGFloat)delta
{
    NSScrollView * const scrollView = self.enclosingScrollView;
    if (scrollView == nil) {
        return;
    }

    const NSRect visibleRect = scrollView.documentVisibleRect;
    [self scrollToHorizontalOffset:NSMinX(visibleRect) - delta animated:NO duration:0.];
}

- (void)scrollToHorizontalOffset:(CGFloat)offset
                         animated:(BOOL)animated
                         duration:(NSTimeInterval)duration
{
    NSScrollView * const scrollView = self.enclosingScrollView;
    if (scrollView == nil) {
        return;
    }

    NSClipView * const clipView = scrollView.contentView;
    const NSRect visibleRect = scrollView.documentVisibleRect;
    const CGFloat minimumOffset = NSMinX(self.frame);
    const CGFloat maximumOffset = MAX(NSMaxX(self.frame) - NSWidth(visibleRect), minimumOffset);
    offset = MIN(MAX(offset, minimumOffset), maximumOffset);
    const NSPoint point = NSMakePoint(offset, NSMinY(visibleRect));

    if (!animated) {
        [clipView scrollToPoint:point];
        [scrollView reflectScrolledClipView:clipView];
        return;
    }

    [NSAnimationContext runAnimationRespectingPreferencesWithDuration:duration
                                                            changes:^(NSAnimationContext * const context) {
        context.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [[clipView animator] setBoundsOrigin:point];
    } completionHandler:^{
        [scrollView reflectScrolledClipView:clipView];
    }];
}

- (NSArray<NSCollectionViewLayoutAttributes *> *)itemLayoutAttributes
{
    NSArray<NSCollectionViewLayoutAttributes *> * const attributes =
        [self.collectionViewLayout layoutAttributesForElementsInRect:self.bounds];
    NSPredicate * const itemPredicate = [NSPredicate predicateWithBlock:^BOOL(
        NSCollectionViewLayoutAttributes * const attributes, NSDictionary * const __unused bindings
    ) {
        return attributes.representedElementCategory == NSCollectionElementCategoryItem;
    }];
    return [[attributes filteredArrayUsingPredicate:itemPredicate]
        sortedArrayUsingComparator:^NSComparisonResult(
            NSCollectionViewLayoutAttributes * const first,
            NSCollectionViewLayoutAttributes * const second
        ) {
            const CGFloat firstX = NSMinX(first.frame);
            const CGFloat secondX = NSMinX(second.frame);
            return firstX < secondX ? NSOrderedAscending : firstX > secondX ? NSOrderedDescending : NSOrderedSame;
        }];
}

@end

[Dauer der Verarbeitung: 0.2 Sekunden, vorverarbeitet 2026-10-24]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Open Source Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Statistik
#Sources=1104856
#Domains=417801