Сохранение содержимого масштабированного NSScrollView по центру и видимым при изменении размера окна - PullRequest
0 голосов
/ 02 мая 2019

Я пытаюсь увеличить NSScrollView, который содержит NSTextView, и всегда держать его в центре его содержимого.NSTextView имеет левую / правую вставки, чтобы обеспечить согласованность переноса слов и держать абзацы в центре вида.

И у [NSScrollView scaleUnitSquareToSize:...], и у setMagnification:... есть свои причуды и проблемы, нона данный момент setMagnification кажется лучшим вариантом, поскольку он не относительный.

Вот что происходит (среди прочих странных вещей): enter image description here

При изменении размера я обновляювставки:

CGFloat inset = self.textScrollView.frame.size.width / 2 - _documentWidth / 2;
self.textView.textContainerInset = NSMakeSize(inset, TEXT_INSET_TOP);
self.textView.textContainer.size = NSMakeSize(_documentWidth, self.textView.textContainer.size.height);

Увеличение:

CGFloat magnification = [self.textScrollView magnification];
NSPoint center = NSMakePoint(self.textScrollView.frame.size.width / 2, self.textScrollView.frame.size.height / 2);

if (zoomIn) magnification += .05; else magnification -= .05;
[self.textScrollView setMagnification:magnification centeredAtPoint:center];

Все вроде работает какое-то время.Иногда, в зависимости от того, из какого угла окна изменяется размер окна, ScrollView теряет свой центр, и я не нашел решения для повторного центрирования вида увеличенного NSScrollView.

После увеличения макетаограничения могут также нарушаться при изменении размера окна, особенно когда textContainer обрезается вне поля зрения, и приложение вылетает со следующей ошибкой: *** Assertion failure in -[NSISLinearExpression addVariable:coefficient:], /Library/Caches/com.apple.xbs/Sources/Foundation/Foundation-1349.91/Layout.subproj/IncrementalSimplex/NSISLinearExpression.m:716

Одной из проблем может быть то, что я устанавливаю вставкив соответствии с UIScrollView размером кадра, потому что содержащиеся в NSTextView координаты кажутся не относительными, а абсолютными после увеличения.

Существует ли какой-либо безопасный способ увеличения вида такого вида и сохранения его в центре его содержимого ввсе время?И почему мои ограничения ломаются?

1 Ответ

1 голос
/ 07 мая 2019

У меня возникли похожие проблемы, и, к сожалению, я сам занялся центрированием. Вот некоторые основные моменты моего решения.

  1. нуждается в профилактике рекурсии! (в противном случае stackoverflow:)
  2. создайте неотрисовываемый NSView в качестве documentView, а затем добавьте нарисованное представление в качестве подпредставления, центрированного вручную, и вручную установите рамку в visibleRect родительского элемента.
  3. переопределить visibleRect, вызвать его второй раз, если он недействителен, и отладить, чтобы убедиться, что он действителен!
  4. масштабирование многослойных поддерживаемых видов sux. Вы можете попробовать использовать NSTiledLayer, но я несколько раз отказывался от этого решения.

Код ниже:

@interface FlippedParentView : NSView
@end

@implementation FlippedParentView
- (BOOL) isFlipped { return YES; }
@end




- (void)awakeFromNib
{
    [self resetMouseInfo];
    [[self window] setAcceptsMouseMovedEvents:YES];
    needsFullRedraw = YES;
    [self setAcceptsTouchEvents:YES];

    // problem: when zoomed-in, CALayer backed NSOpenGLView becomes too large
    // and hurts performance.
    // solution: create a fullsizeView for the NSScrollView to resize,
    // and make NSOpenGLView a subview.  Keep NSOpenGLView size the same as visibleRect,
    // positioning it as needed on the fullsizeView.
    NSScrollView *scrollvw = [self enclosingScrollView];
    [scrollvw setBackgroundColor:[NSColor darkStrokeColor]];
    fullsizeView = [[FlippedParentView alloc] initWithFrame: [self frame]];
    [scrollvw setDocumentView:fullsizeView];
    [fullsizeView setAutoresizesSubviews:NO];
    //printf("mask %d\n", [self autoresizingMask]);
    [fullsizeView setAutoresizingMask: NSViewHeightSizable | NSViewWidthSizable | NSViewMinYMargin | NSViewMaxYMargin | NSViewMaxXMargin | NSViewMinXMargin];
    [self setAutoresizingMask: NSViewNotSizable];
    [fullsizeView addSubview:self];
}

- (NSRect) visibleRect
{
    NSRect visRect = [super visibleRect];
    if ( visRect.size.width == 0 )
    {
        visRect = [[self superview] visibleRect];
        if ( visRect.size.width == 0 )
        {
            // this jacks up everything
            DUMP( @"bad visibleRect" );
        }
        visRect.origin = NSZeroPoint;
    }
    return visRect;
}

- (void) _my_zoom: (double)newZoom
{
    mouseFocusPt = [self focusPt];
    NSRect oldVisRect = [[self superview] visibleRect];
    if ( newZoom < 1.0 )
        newZoom = 1.0;
    if ( newZoom > kZoomFactorMax ) newZoom = kZoomFactorMax;

    float xpct = (mouseFocusPt.x - oldVisRect.origin.x) /
    ( NSMaxX(oldVisRect) - oldVisRect.origin.x );

    float ypct = (mouseFocusPt.y  - oldVisRect.origin.y) /
    ( NSMaxY(oldVisRect) - oldVisRect.origin.y );

    float oldZoom = zoomFactor;

    zoomFactor = newZoom;

    /////////////////////////////////////////////////////////////////////////////////////////////////////
    // Stay locked on users' relative mouse location, so user can zoom in and back out without
    // the view scrolling out from under the mouse location.
    NSPoint newFocusPt = NSMakePoint (mouseFocusPt.x * newZoom/oldZoom,
                                      mouseFocusPt.y * newZoom/oldZoom) ;

    NSRect myFrame = fullsizeFrame; // [self frame];
    float marginPercent = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;

    [self updateContext];

    NSRect newVisRect;
    newVisRect.size = [self visibleRect].size;
    newVisRect.origin.x = (newFocusPt.x) - (xpct * newVisRect.size.width);
    //DLog( @"xpct %0.2f, zoomFactor %0.2f, newVisRect.origin.x %0.2f", xpct, zoomFactor, newVisRect.origin.x);

    myFrame = fullsizeFrame; // [self frame];
    float marginPercent2 = (myFrame.size.height - drawableSizeWithMargins.height) / drawableSizeWithMargins.height;
    float marginDiff = (marginPercent - marginPercent2) * drawableSizeWithMargins.height;
    newVisRect.origin.y = (newFocusPt.y ) - (ypct * newVisRect.size.height) - marginDiff;
    //DLog( @"ypct %0.2f, zoomFactor %0.2f, newVisRect.origin.y %0.2f", ypct, zoomFactor, newVisRect.origin.y);
    //DLog( @"marginPercent %0.2f newVisRect %@", marginPercent, NSStringFromRect(newVisRect) );
    if ( newVisRect.origin.x < 1 ) newVisRect.origin.x = 1;
    if ( newVisRect.origin.y < 1 ) newVisRect.origin.y = 1;


     //   NSLog( @"zoom scrollRectToVisible %@ bounds %@", NSStringFromRect(newVisRect), NSStringFromRect([[self superview] bounds]) );
    // if ( iUseMousePt || isSlider )
        [[self superview] scrollRectToVisible:newVisRect];
}

// - zoomFactor of 1.0 is defined as the zoomFactor needed to show entire selected context within visibleRect,
//   including margins of 5% of the context size
// - zoomFactor > 1.0 will make pixels look bigger (view a subsection of a larger total drawableSize)
// - zoomFactor < 1.0 will make pixels look smaller (selectedContext size will be less than drawableSize)
-(void)updateContext
{
    static BOOL sRecursing = NO;
    if ( sRecursing ) return; // prevent recursion
    sRecursing = YES;

    //NSRect scrollRect = [[self superview]  frame];
    NSRect clipViewRect = [[[self enclosingScrollView] contentView] frame];
    NSRect visRect = [[self superview] visibleRect]; // careful... visibleRect is sometimes NSZeroRect

    float layoutWidth = clipViewRect.size.width;
    float layoutHeight = clipViewRect.size.height;



    marginPct = layoutHeight / (layoutHeight - (overlayViewMargin*2) );

    // Satisfy the constraints fully-zoomed-out case:
    //  1) the drawable rect is centered in the view with at margins.
    //     Allow for 5% margins (1.025 = 2.5% left, right, top, bottom)
    //  2) guarantee the drawable rect does not overlap the mini-map in upper right corner.
    NSRect baseRect = NSZeroRect;
    baseRect.size = visRect.size;
    NSRect drawableBaseRect = getCenteredRectFloat(baseRect, metaUnionRect.size );

    //drawableSizeWithMargins = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor ) );
    drawableSizeWithMargins = nsScaleSize( drawableBaseRect.size, zoomFactor );

    // drawableSize will NOT include the margins.  We loop until we've satisfied
    // the constraints above.
    drawableSize = drawableSizeWithMargins;

    do
    {
        NSSize shrunkSize;
        shrunkSize.width = layoutWidth / marginPct;
        shrunkSize.height = layoutHeight /  marginPct;
        //drawableSize = nsIntegralSize( nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct ));
        drawableSize = nsScaleSize( drawableBaseRect.size, zoomFactor / marginPct );

        [self calculateMiniMapRect]; // get approx. size.  Will calculate once more below.

        NSRect shrunkRect = getCenteredRectNoScaling(baseRect, shrunkSize );

        // DLog( @"rough miniMapRect %@ shrunk %@", NSStringFromRect(miniMapRect), NSStringFromRect(shrunkRect));

        // make sure minimap doesn't overlap drawable when you scroll to top-left
        NSRect topMiniMapRect = miniMapRect;
        topMiniMapRect.origin.x -= visRect.origin.x;
        topMiniMapRect.origin.y = 0;
        if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
        {
            topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y)  / baseRect.size.height;
            break;
        }

        float topMarginOffset = shrunkRect.size.height + (baseRect.size.height * 0.025);
        shrunkRect.origin.y = NSMaxY(baseRect) - topMarginOffset;

        if ( !NSIntersectsRect( topMiniMapRect, shrunkRect ) )
        {
            topMarginPercent = fabs(shrunkRect.origin.y - drawableBaseRect.origin.y)  / baseRect.size.height;
            break;
        }

        marginPct *= 1.025;
    } while (1);

    fullsizeFrame.origin = NSZeroPoint;
    fullsizeFrame.size.width  = fmax(drawableSizeWithMargins.width, layoutWidth);
    fullsizeFrame.size.height = fmax(drawableSizeWithMargins.height, layoutHeight);

    [fullsizeView setFrame:fullsizeFrame];

    NSRect myNewFrame = [fullsizeView visibleRect];
    if (myNewFrame.size.width > 0)
       [self setFrame: myNewFrame]; //NSView

    sRecursing = NO;
}
...