mirror of
				https://github.com/davidgiven/fluxengine.git
				synced 2025-10-24 11:11:02 -07:00 
			
		
		
		
	
		
			
				
	
	
		
			254 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
			
		
		
	
	
			254 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Objective-C
		
	
	
	
	
	
| // 1 november 2016
 | |
| #import "uipriv_darwin.h"
 | |
| 
 | |
| // TODO option while resizing resizes both opposing sides at once (thanks swillits in irc.freenode.net/#macdev for showing this to me); figure out how far back that behavior goes when we do implement it
 | |
| 
 | |
| // because we are changing the window frame each time the mouse moves, the successive -[NSEvent locationInWindow]s cannot be meaningfully used together
 | |
| // make sure they are all following some sort of standard to avoid this problem; the screen is the most obvious possibility since it requires only one conversion (the only one that a NSWindow provides)
 | |
| static NSPoint makeIndependent(NSPoint p, NSWindow *w)
 | |
| {
 | |
| 	NSRect r;
 | |
| 
 | |
| 	r.origin = p;
 | |
| 	// mikeash in irc.freenode.net/#macdev confirms both that any size will do and that we can safely ignore the resultant size
 | |
| 	r.size = NSZeroSize;
 | |
| 	return [w convertRectToScreen:r].origin;
 | |
| }
 | |
| 
 | |
| struct onMoveDragParams {
 | |
| 	NSWindow *w;
 | |
| 	// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
 | |
| 	// TODO will this make things like the menubar and dock easier too?
 | |
| 	NSRect initialFrame;
 | |
| 	NSPoint initialPoint;
 | |
| };
 | |
| 
 | |
| void onMoveDrag(struct onMoveDragParams *p, NSEvent *e)
 | |
| {
 | |
| 	NSPoint new;
 | |
| 	NSRect frame;
 | |
| 	CGFloat offx, offy;
 | |
| 
 | |
| 	new = makeIndependent([e locationInWindow], p->w);
 | |
| 	frame = p->initialFrame;
 | |
| 
 | |
| 	offx = new.x - p->initialPoint.x;
 | |
| 	offy = new.y - p->initialPoint.y;
 | |
| 	frame.origin.x += offx;
 | |
| 	frame.origin.y += offy;
 | |
| 
 | |
| 	// TODO handle the menubar
 | |
| 	// TODO wait the system does this for us already?!
 | |
| 
 | |
| 	[p->w setFrameOrigin:frame.origin];
 | |
| }
 | |
| 
 | |
| void uiprivDoManualMove(NSWindow *w, NSEvent *initialEvent)
 | |
| {
 | |
| 	__block struct onMoveDragParams mdp;
 | |
| 	uiprivNextEventArgs nea;
 | |
| 	BOOL (^handleEvent)(NSEvent *e);
 | |
| 	__block BOOL done;
 | |
| 
 | |
| 	// 10.11 gives us a method to handle this for us
 | |
| 	// use it if available; this lets us use the real OS dragging code, which means we can take advantage of OS features like Spaces
 | |
| 	if (uiprivFUTURE_NSWindow_performWindowDragWithEvent(w, initialEvent))
 | |
| 		return;
 | |
| 
 | |
| 	mdp.w = w;
 | |
| 	mdp.initialFrame = [mdp.w frame];
 | |
| 	mdp.initialPoint = makeIndependent([initialEvent locationInWindow], mdp.w);
 | |
| 
 | |
| 	nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
 | |
| 	nea.duration = [NSDate distantFuture];
 | |
| 	nea.mode = NSEventTrackingRunLoopMode;		// nextEventMatchingMask: docs suggest using this for manual mouse tracking
 | |
| 	nea.dequeue = YES;
 | |
| 	handleEvent = ^(NSEvent *e) {
 | |
| 		if ([e type] == NSLeftMouseUp) {
 | |
| 			done = YES;
 | |
| 			return YES;	// do not send
 | |
| 		}
 | |
| 		onMoveDrag(&mdp, e);
 | |
| 		return YES;		// do not send
 | |
| 	};
 | |
| 	done = NO;
 | |
| 	while (uiprivMainStep(&nea, handleEvent))
 | |
| 		if (done)
 | |
| 			break;
 | |
| }
 | |
| 
 | |
| // see http://stackoverflow.com/a/40352996/3408572
 | |
| static void minMaxAutoLayoutSizes(NSWindow *w, NSSize *min, NSSize *max)
 | |
| {
 | |
| 	NSLayoutConstraint *cw, *ch;
 | |
| 	NSView *contentView;
 | |
| 	NSRect prevFrame;
 | |
| 
 | |
| 	// if adding these constraints causes the window to change size somehow, don't show it to the user and change it back afterwards
 | |
| 	NSDisableScreenUpdates();
 | |
| 	prevFrame = [w frame];
 | |
| 
 | |
| 	// minimum: encourage the window to be as small as possible
 | |
| 	contentView = [w contentView];
 | |
| 	cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,
 | |
| 		NSLayoutRelationEqual,
 | |
| 		nil, NSLayoutAttributeNotAnAttribute,
 | |
| 		0, 0,
 | |
| 		@"window minimum width finding constraint");
 | |
| 	[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
 | |
| 	[contentView addConstraint:cw];
 | |
| 	ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,
 | |
| 		NSLayoutRelationEqual,
 | |
| 		nil, NSLayoutAttributeNotAnAttribute,
 | |
| 		0, 0,
 | |
| 		@"window minimum height finding constraint");
 | |
| 	[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
 | |
| 	[contentView addConstraint:ch];
 | |
| 	*min = [contentView fittingSize];
 | |
| 	[contentView removeConstraint:cw];
 | |
| 	[contentView removeConstraint:ch];
 | |
| 
 | |
| 	// maximum: encourage the window to be as large as possible
 | |
| 	contentView = [w contentView];
 | |
| 	cw = uiprivMkConstraint(contentView, NSLayoutAttributeWidth,
 | |
| 		NSLayoutRelationEqual,
 | |
| 		nil, NSLayoutAttributeNotAnAttribute,
 | |
| 		0, CGFLOAT_MAX,
 | |
| 		@"window maximum width finding constraint");
 | |
| 	[cw setPriority:NSLayoutPriorityDragThatCanResizeWindow];
 | |
| 	[contentView addConstraint:cw];
 | |
| 	ch = uiprivMkConstraint(contentView, NSLayoutAttributeHeight,
 | |
| 		NSLayoutRelationEqual,
 | |
| 		nil, NSLayoutAttributeNotAnAttribute,
 | |
| 		0, CGFLOAT_MAX,
 | |
| 		@"window maximum height finding constraint");
 | |
| 	[ch setPriority:NSLayoutPriorityDragThatCanResizeWindow];
 | |
| 	[contentView addConstraint:ch];
 | |
| 	*max = [contentView fittingSize];
 | |
| 	[contentView removeConstraint:cw];
 | |
| 	[contentView removeConstraint:ch];
 | |
| 
 | |
| 	[w setFrame:prevFrame display:YES];		// TODO really YES?
 | |
| 	NSEnableScreenUpdates();
 | |
| }
 | |
| 
 | |
| static void handleResizeLeft(NSRect *frame, NSPoint old, NSPoint new)
 | |
| {
 | |
| 	frame->origin.x += new.x - old.x;
 | |
| 	frame->size.width -= new.x - old.x;
 | |
| }
 | |
| 
 | |
| // TODO properly handle the menubar
 | |
| // TODO wait, OS X does it for us?!
 | |
| static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new)
 | |
| {
 | |
| 	frame->size.height += new.y - old.y;
 | |
| }
 | |
| 
 | |
| static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new)
 | |
| {
 | |
| 	frame->size.width += new.x - old.x;
 | |
| }
 | |
| 
 | |
| 
 | |
| // TODO properly handle the menubar
 | |
| static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new)
 | |
| {
 | |
| 	frame->origin.y += new.y - old.y;
 | |
| 	frame->size.height -= new.y - old.y;
 | |
| }
 | |
| 
 | |
| struct onResizeDragParams {
 | |
| 	NSWindow *w;
 | |
| 	// using the previous point causes weird issues like the mouse seeming to fall behind the window edge... so do this instead
 | |
| 	// TODO will this make things like the menubar and dock easier too?
 | |
| 	NSRect initialFrame;
 | |
| 	NSPoint initialPoint;
 | |
| 	uiWindowResizeEdge edge;
 | |
| 	NSSize min;
 | |
| 	NSSize max;
 | |
| };
 | |
| 
 | |
| static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e)
 | |
| {
 | |
| 	NSPoint new;
 | |
| 	NSRect frame;
 | |
| 
 | |
| 	new = makeIndependent([e locationInWindow], p->w);
 | |
| 	frame = p->initialFrame;
 | |
| 
 | |
| 	// horizontal
 | |
| 	switch (p->edge) {
 | |
| 	case uiWindowResizeEdgeLeft:
 | |
| 	case uiWindowResizeEdgeTopLeft:
 | |
| 	case uiWindowResizeEdgeBottomLeft:
 | |
| 		handleResizeLeft(&frame, p->initialPoint, new);
 | |
| 		break;
 | |
| 	case uiWindowResizeEdgeRight:
 | |
| 	case uiWindowResizeEdgeTopRight:
 | |
| 	case uiWindowResizeEdgeBottomRight:
 | |
| 		handleResizeRight(&frame, p->initialPoint, new);
 | |
| 		break;
 | |
| 	}
 | |
| 	// vertical
 | |
| 	switch (p->edge) {
 | |
| 	case uiWindowResizeEdgeTop:
 | |
| 	case uiWindowResizeEdgeTopLeft:
 | |
| 	case uiWindowResizeEdgeTopRight:
 | |
| 		handleResizeTop(&frame, p->initialPoint, new);
 | |
| 		break;
 | |
| 	case uiWindowResizeEdgeBottom:
 | |
| 	case uiWindowResizeEdgeBottomLeft:
 | |
| 	case uiWindowResizeEdgeBottomRight:
 | |
| 		handleResizeBottom(&frame, p->initialPoint, new);
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	// constrain
 | |
| 	// TODO should we constrain against anything else as well? minMaxAutoLayoutSizes() already gives us nonnegative sizes, but...
 | |
| 	if (frame.size.width < p->min.width)
 | |
| 		frame.size.width = p->min.width;
 | |
| 	if (frame.size.height < p->min.height)
 | |
| 		frame.size.height = p->min.height;
 | |
| 	// TODO > or >= ?
 | |
| 	if (frame.size.width > p->max.width)
 | |
| 		frame.size.width = p->max.width;
 | |
| 	if (frame.size.height > p->max.height)
 | |
| 		frame.size.height = p->max.height;
 | |
| 
 | |
| 	[p->w setFrame:frame display:YES];			// and do reflect the new frame immediately
 | |
| }
 | |
| 
 | |
| // TODO do our events get fired with this? *should* they?
 | |
| void uiprivDoManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge)
 | |
| {
 | |
| 	__block struct onResizeDragParams rdp;
 | |
| 	uiprivNextEventArgs nea;
 | |
| 	BOOL (^handleEvent)(NSEvent *e);
 | |
| 	__block BOOL done;
 | |
| 
 | |
| 	rdp.w = w;
 | |
| 	rdp.initialFrame = [rdp.w frame];
 | |
| 	rdp.initialPoint = makeIndependent([initialEvent locationInWindow], rdp.w);
 | |
| 	rdp.edge = edge;
 | |
| 	// TODO what happens if these change during the loop?
 | |
| 	minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max));
 | |
| 
 | |
| 	nea.mask = NSLeftMouseDraggedMask | NSLeftMouseUpMask;
 | |
| 	nea.duration = [NSDate distantFuture];
 | |
| 	nea.mode = NSEventTrackingRunLoopMode;		// nextEventMatchingMask: docs suggest using this for manual mouse tracking
 | |
| 	nea.dequeue = YES;
 | |
| 	handleEvent = ^(NSEvent *e) {
 | |
| 		if ([e type] == NSLeftMouseUp) {
 | |
| 			done = YES;
 | |
| 			return YES;	// do not send
 | |
| 		}
 | |
| 		onResizeDrag(&rdp, e);
 | |
| 		return YES;		// do not send
 | |
| 	};
 | |
| 	done = NO;
 | |
| 	while (uiprivMainStep(&nea, handleEvent))
 | |
| 		if (done)
 | |
| 			break;
 | |
| }
 |