diff --git a/darwin/CMakeLists.txt b/darwin/CMakeLists.txt index 2bc34ec9..f0c936b5 100644 --- a/darwin/CMakeLists.txt +++ b/darwin/CMakeLists.txt @@ -39,6 +39,7 @@ list(APPEND _LIBUI_SOURCES darwin/text.m darwin/util.m darwin/window.m + darwin/winmoveresize.m ) set(_LIBUI_SOURCES ${_LIBUI_SOURCES} PARENT_SCOPE) diff --git a/darwin/area.m b/darwin/area.m index 6c6ac4b7..6929f292 100644 --- a/darwin/area.m +++ b/darwin/area.m @@ -32,6 +32,7 @@ struct uiArea { struct scrollViewData *d; uiAreaHandler *ah; BOOL scrolling; + NSEvent *dragevent; }; @implementation areaView @@ -200,8 +201,12 @@ struct uiArea { me.Held1To64 |= j; } - if (self->libui_enabled) + if (self->libui_enabled) { + // and allow dragging here + a->dragevent = e; (*(a->ah->MouseEvent))(a->ah, a, &me); + a->dragevent = nil; + } } #define mouseEvent(name) \ @@ -403,12 +408,26 @@ void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) void uiAreaBeginUserWindowMove(uiArea *a) { - // TODO + libuiNSWindow *w; + + w = (libuiNSWindow *) [a->area window]; + if (w == nil) + return; // TODO + if (a->dragevent == nil) + return; // TODO + [w libui_doMove:a->dragevent]; } void uiAreaBeginUserWindowResize(uiArea *a, uiWindowResizeEdge edge) { - // TODO + libuiNSWindow *w; + + w = (libuiNSWindow *) [a->area window]; + if (w == nil) + return; // TODO + if (a->dragevent == nil) + return; // TODO + [w libui_doResize:a->dragevent on:edge]; } uiArea *uiNewArea(uiAreaHandler *ah) diff --git a/darwin/uipriv_darwin.h b/darwin/uipriv_darwin.h index eca2d9b0..7332b050 100644 --- a/darwin/uipriv_darwin.h +++ b/darwin/uipriv_darwin.h @@ -138,3 +138,6 @@ extern NSTextField *newLabel(NSString *str); // image.m extern NSImage *imageImage(uiImage *); + +// winmoveresize.m +extern void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge); diff --git a/darwin/window.m b/darwin/window.m index 4164f07a..8456c767 100644 --- a/darwin/window.m +++ b/darwin/window.m @@ -27,7 +27,7 @@ struct uiWindow { - (void)libui_doResize:(NSEvent *)initialEvent on:(uiWindowResizeEdge)edge { - // TODO + doManualResize(self, initialEvent, edge); } @end diff --git a/darwin/winmoveresize.m b/darwin/winmoveresize.m new file mode 100644 index 00000000..58155683 --- /dev/null +++ b/darwin/winmoveresize.m @@ -0,0 +1,210 @@ +// 1 november 2016 +#import "uipriv_darwin.h" + +// 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 = mkConstraint(contentView, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, 0, + @"window minimum width finding constraint"); + [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:cw]; + ch = mkConstraint(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 = mkConstraint(contentView, NSLayoutAttributeWidth, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, DBL_MAX, + @"window maximum width finding constraint"); + [cw setPriority:NSLayoutPriorityDragThatCanResizeWindow]; + [contentView addConstraint:cw]; + ch = mkConstraint(contentView, NSLayoutAttributeHeight, + NSLayoutRelationEqual, + nil, NSLayoutAttributeNotAnAttribute, + 0, DBL_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; +} + +// TODO properly handle crossing the menubar; this just stops at the menubar +static void handleResizeTop(NSRect *frame, NSPoint old, NSPoint new) +{ + CGFloat offset; + CGFloat newHeight; + CGFloat oldTop, newTop; + NSRect mainWorkArea; + CGFloat menubarBottom; + + offset = new.y - old.y; + newHeight = frame->size.height + offset; + + // we have gone too high if we started under the menubar AND we are about to cross it + oldTop = frame->origin.y + frame->size.height; + newTop = frame->origin.y + newHeight; + mainWorkArea = [[NSScreen mainScreen] visibleFrame]; + menubarBottom = mainWorkArea.origin.y + mainWorkArea.size.height; + if (oldTop < menubarBottom) + if (newTop >= menubarBottom) + return; + + frame->size.height = newHeight; +} + +static void handleResizeRight(NSRect *frame, NSPoint old, NSPoint new) +{ + frame->size.width += new.x - old.x; +} + + +// TODO properly handle crossing the menubar; this just stops at the menubar +static void handleResizeBottom(NSRect *frame, NSPoint old, NSPoint new) +{ + CGFloat offset; + CGFloat newY; + NSRect mainFrame; + CGFloat menubarTop; + + offset = new.y - old.y; + newY = frame->origin.y + offset; + + // we have gone too low if we started above the menubar AND we are about to cross it + mainFrame = [[NSScreen mainScreen] frame]; + menubarTop = mainFrame.origin.y + mainFrame.size.height; + if (frame->origin.y >= menubarTop) + if (newY < menubarTop) + return; + + frame->origin.y = newY; +} + +struct onResizeDragParams { + NSWindow *w; + NSPoint old; + uiWindowResizeEdge edge; + NSSize min; + NSSize max; +}; + +static void onResizeDrag(struct onResizeDragParams *p, NSEvent *e) +{ + NSPoint new; + NSRect frame; + + new = [e locationInWindow]; + frame = [p->w frame]; + +NSLog(@"old %@ new %@", NSStringFromPoint(p->old), NSStringFromPoint(new)); +NSLog(@"frame %@", NSStringFromRect(frame)); + + // horizontal + switch (p->edge) { + case uiWindowResizeEdgeLeft: + case uiWindowResizeEdgeTopLeft: + case uiWindowResizeEdgeBottomLeft: + handleResizeLeft(&frame, p->old, new); + break; + case uiWindowResizeEdgeRight: + case uiWindowResizeEdgeTopRight: + case uiWindowResizeEdgeBottomRight: + handleResizeRight(&frame, p->old, new); + break; + } + // vertical + switch (p->edge) { + case uiWindowResizeEdgeTop: + case uiWindowResizeEdgeTopLeft: + case uiWindowResizeEdgeTopRight: + handleResizeTop(&frame, p->old, new); + break; + case uiWindowResizeEdgeBottom: + case uiWindowResizeEdgeBottomLeft: + case uiWindowResizeEdgeBottomRight: + handleResizeBottom(&frame, p->old, new); + break; + } + + // constrain + 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; + +NSLog(@"becomes %@", NSStringFromRect(frame)); + + [p->w setFrame:frame display:YES]; // and do reflect the new frame immediately + // and set it up for the next run + p->old = new; +} + +void doManualResize(NSWindow *w, NSEvent *initialEvent, uiWindowResizeEdge edge) +{ + __block struct onResizeDragParams rdp; + struct nextEventArgs nea; + BOOL (^handleEvent)(NSEvent *e); + __block BOOL done; + + rdp.w = w; + rdp.old = [initialEvent locationInWindow]; + rdp.edge = edge; + // TODO what happens if these change during the loop? + minMaxAutoLayoutSizes(rdp.w, &(rdp.min), &(rdp.max)); +NSLog(@"min %@", NSStringFromSize(rdp.min)); +NSLog(@"max %@", NSStringFromSize(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 (mainStep(&nea, handleEvent)) + if (done) + break; +}