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;
|
|
}
|