[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, 9 Dec 2019 11:33:37 +0100 |
> 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
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];
}
- scaling, rotating, flipping subview, H. Nikolaus Schaller, 2019/12/08
- Re: scaling, rotating, flipping subview, Fred Kiefer, 2019/12/08
- Re: scaling, rotating, flipping subview, David Chisnall, 2019/12/09
- Re: scaling, rotating, flipping subview,
H. Nikolaus Schaller <=