discuss-gnustep
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: scaling, rotating, flipping subview


From: H. Nikolaus Schaller
Subject: Re: scaling, rotating, flipping subview
Date: Mon, 24 Feb 2020 19:04:59 +0100

Hi,

> Am 09.12.2019 um 11:33 schrieb H. Nikolaus Schaller <hns@goldelico.com>:
> 
> 
>> Am 09.12.2019 um 10:22 schrieb David Chisnall <gnustep@theravensnest.org>:
>> 
>> Hi,
>> 
>> The problem that you identify (input and output coordinate spaces are 
>> different) is also a problem with CoreAnimation and with X11's XRENDER 
>> extension: in both you can apply an affine transform to the output, but 
>> input coordinates are not transformed.
>> 
>> This is something that you can work around by modifying the responder chain. 
>>  NSView is an NSResponder and is responsible for transforming input events 
>> into the child view's coordinate space before delegating them.  You can 
>> apply the inverse of the affine transform to the coordinates of the event 
>> (after determining which view actually handles them).
> 
> Looks quite fragile... And I am a friend of the KISS principle.
> 
> Do you know some example code that is general enough?
> 
> I have attached my current experimental code, maybe it becomes more clear 
> what I want to do.
> 
> To use it:
> 1. add a (simple) header file
> 2. make the NSScaleRotateFlipView a subview/documentView of an NSClipView 
> embedded in some NSScrollView
> 3. add a subview to the NSScaleRotateFlipView, e.g. an NSImageView
> 4. connect buttons to the action methods

I have significantly reworked the code in the past weeks and the concept
I had described now works almost well on Cocoa. So the @interface hasn't
changed much and the concept of making the NSScaleRotateFlipView sit in
the view hierarhy between NSClipView and the document view.

With mySTEP there is also an almost correct result, except that clipping
in rotated frame/bounds doesn't look right but zoom and flipping works.

Before I publish it (plan is for GPL) I need to fix some very subtle glitches
when scrolling which sometimes makes the document jump to a wrong position.

BR,
Nikolaus

> 
> BR,
> Nikolaus
> 
>> 
>> David
>> 
>> On 08/12/2019 19:17, H. Nikolaus Schaller wrote:
>>> Hi,
>>> I am currently working on some CAD tool for GNUstep/mySTEP
>>> and for that I would need a NSView class that can become
>>> the documentView of a NSClipView, embedded in some
>>> NSScrollView. And the view class I am looking for should
>>> allow to rotate, flip and scale a subview (where I do the
>>> drawing).
>>> There is no standard class which can do that in Cocoa or OpenSTEP.
>>> I have experimented a little on Cocoa and got scaling work
>>> (by setting the bounds of the drawing view scaled relative to
>>> its frame) but flipping and rotation is difficult to achieve.
>>> It partially works with setBoundsRotation or scaleUnitSquareToSize,
>>> but as a side-effect that breaks operation of the scrollers of
>>> the NSScrollView.
>>> Scroller size and position seems to assume that the frame and
>>> bounds are not rotated so that changing the bounds origin can
>>> simply move around the view under the NSClipView.
>>> The standard recommendation is to set a transform matrix in
>>> drawRect: and by that I could make drawing work, but coordinate
>>> transforms for mouse clicks do not take this into account.
>>> And scrollers do not adjust for different scaling.
>>> Finally, this is not a general approach which can rotate,
>>> flip and scale an arbitrary subview.
>>> Before I invest more time in this topic, I'd like to ask
>>> if someone knows an open source implementation of such a
>>> general NSView subclass.
>>> Thanks,
>>> Nikolaus
>> 
> 
> @implementation NSScaleRotateFlipView
> 
> - (id) initWithFrame:(NSRect)frame
> {
>       if((self = [super initWithFrame:frame]))
>               {
>               [self setAutoresizingMask:0];   // do not use autoresizing - 
> setFrame/setScale also define new bounds
>               [self setScale:1.0];    // initialize bounds
>               }
>       return self;
> }
> 
> - (void) viewDidMoveToSuperview
> { // set default scale - we now have a superview and enclosingScrollView
>       [self setScale:10.0];
> }
> 
> - (BOOL) wantsDefaultClipping; { return NO; } // do not clip subview to our 
> bounds
> 
> - (BOOL) isFlipped; { return _isFlipped; }
> - (void) setFlipped:(BOOL) flag; { _isFlipped=flag; [self 
> setNeedsDisplay:YES]; }
> 
> - (NSView *) contentView;
> {
>       NSArray *a=[self subviews];
>       return [a count] ? [a objectAtIndex:0]:nil;
> }
> 
> - (void) setContentView:(NSView *) object;
> { // replace subview or add subview
>       NSView *cv=[self contentView];
>       if(!cv)
>               [self addSubview:object];
>       else if(cv != object)
>               [self replaceSubview:cv with:object];
>       [self setNeedsDisplay:YES];
> }
> 
> - (NSPoint) center
> { // get center of currently visible area
>       NSScrollView *scrollView=[self enclosingScrollView];
>       if(scrollView)
>               {
>               NSClipView *clipView=[scrollView contentView];
>               NSRect cvbounds=[clipView bounds];
>               NSPoint cvcenter=NSMakePoint(NSMidX(cvbounds), 
> NSMidY(cvbounds));
>               NSPoint center=[self convertPoint:cvcenter fromView:clipView];
>               return center;
>               }
>       return NSZeroPoint;
> }
> 
> - (void) setFrame:(NSRect) frame
> {
>       NSClipView *clipView=[[self enclosingScrollView] contentView];
>       NSView *cv=[self contentView];
>       if(clipView && cv)
>               {
>               NSRect clipFrame=[clipView frame];      // "window" of ClipView
>               NSRect frame=clipFrame, bounds;
>               NSRect area=[cv bounds];        // document bounds
>               frame.size.width *= _scale;
>               frame.size.height *= _scale;
>               // CHECKME: there may be an upper limit how big frame and 
> bounds can become!
>               [super setFrame:frame];
>               bounds.size.width=clipFrame.size.width;
>               bounds.size.height=clipFrame.size.height;
>               bounds.origin.x=NSMidX(area)-0.5*bounds.size.width;     // 
> center area
>               bounds.origin.y=NSMidY(area)-0.5*bounds.size.height;
>               [self setBounds:bounds];        // apply scaling
> #if 1
>               [cv setFrameRotation:_rotationAngle];
>               double rad=M_PI*_rotationAngle/180;
>               double s=sin(rad);
>               double c=cos(rad);
>               bounds.origin.x += 0.5*bounds.size.width*(1-c) + 
> 0.5*bounds.size.height*s;
>               bounds.origin.y += 0.5*bounds.size.height*(1-c) - 
> 0.5*bounds.size.width*s;
>               [cv setFrame:bounds];
> #else
> // does not work properly
>               NSPoint center=[self center];
> //            [cv translateOriginToPoint:center];
>               [cv setBoundsRotation:_rotationAngle];
> //            [self scaleUnitSquareToSize:NSMakeSize((_flags & 
> SHOW_HFLIPPED)?-1.0:1.0, (_flags & SHOW_VFLIPPED)?-1.0:1.0)];
> //            [cv translateOriginToPoint:NSMakePoint(-center.x, -center.y)];
> #endif
>               }
>       else
>               [super setFrame:frame];
> }
> 
> - (float) scale; { return _scale; }
> 
> - (void) setScale:(float) scale
> {
>       if(scale < 1e-3 || scale > 1e3)
>               return; // ignore
>       _scale=scale;
>       [self setFrame:[self frame]];   // trigger update of bounds
>       [self setNeedsDisplay:YES];
> }
> 
> - (void) zoom:(float) factor atCenter:(NSPoint) center
> { // zoom to absolute scale
>       NSScrollView *scrollView=[self enclosingScrollView];
>       if(scrollView)
>               {
>               NSClipView *clipView=[scrollView contentView];
>               NSRect bounds=[self bounds];
>               NSPoint origin;
>               [self setScale:factor]; // may change our bounds!
>               bounds=[self bounds];   // scaled bounds
>               origin.x = 0.5*NSWidth(bounds)*(factor-1.0) + factor*(center.x 
> - NSMidX(bounds));
>               origin.y = 0.5*NSHeight(bounds)*(factor-1.0) + factor*(center.y 
> - NSMidY(bounds));
>               [clipView scrollToPoint:origin];
>               // irgendwie beachten ob der subview geflippt ist!
>               // das wirkt sich auf den scroller aus
>               [scrollView reflectScrolledClipView:clipView];
>               [self setNeedsDisplay:YES];
>               }
>       else
>               [self setScale:factor];
> }
> 
> - (void) zoomRectToVisible:(NSRect) area;
> {
>       NSRect frame=[[[self enclosingScrollView] contentView] frame];
>       if(!NSIsEmptyRect(area))
>               [self zoom:0.5*MIN(NSWidth(frame)/NSWidth(area), 
> NSHeight(frame)/NSHeight(area)) atCenter:(NSPoint) { NSMidX(area), 
> NSMidY(area) }];
> }
> 
> - (int) rotationAngle; { return _rotationAngle; }
> - (void) setRotationAngle:(int) angle;
> {
>       _rotationAngle = ((angle % 360) + 360) % 360;   // also works for 
> negative values
>       [self setFrame:[self frame]];   // trigger update of bounds
>       [self setNeedsDisplay:YES];
> }
> 
> /* menu actions */
> 
> - (IBAction) center:(id) sender;
> { // center the main view (independently of scaling)
>       NSRect area=[[self contentView] frame];
>       [self zoom:[self scale] atCenter:NSMakePoint(NSMidX(area), 
> NSMidY(area))];
> }
> 
> - (IBAction) zoomFit:(id) sender;
> {
>       NSRect area=[[self contentView] frame];
>       NSRect frame=[[[self enclosingScrollView] contentView] frame];  // 
> NSClipView frame
>       if(!NSIsEmptyRect(area))
>               [self zoom:MIN(NSWidth(frame)/NSWidth(area), 
> NSHeight(frame)/NSHeight(area)) atCenter:NSMakePoint(NSMidX(area), 
> NSMidY(area))];
> }
> 
> - (IBAction) zoomIn:(id) sender;
> {
>       [self zoom:sqrt(2.0)*[self scale] atCenter:[self center]];
> }
> 
> - (IBAction) zoomOut:(id) sender;
> {
>       [self zoom:sqrt(0.5)*[self scale] atCenter:[self center]];
> }
> 
> - (IBAction) zoomUnity:(id) sender;
> {
>       [self setScale:1.0];
>       [self center:sender];
> }
> 
> - (IBAction) rotateImageLeft:(id) sender;
> {
>       [self setRotationAngle:[self rotationAngle]+10];
> }
> 
> - (IBAction) rotateImageRight:(id) sender;
> {
>       [self setRotationAngle:[self rotationAngle]-10];
> }
> 
> - (IBAction) rotateNormal:(id) sender;
> {
>       [self setRotationAngle:0];
> }
> 
> - (IBAction) flipHorizontal:(id) sender;
> {
>       [self flipVertical:sender];
>       [self rotateImageLeft:sender];
>       [self rotateImageLeft:sender];
> }
> 
> - (IBAction) flipVertical:(id) sender;
> {
>       [self setFlipped:![self isFlipped]];
> }
> 
> - (IBAction) unflip:(id) sender;
> {
>       [self setFlipped:NO];
> }
> 
> 
> 
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]