// 9 september 2015 #import "uipriv_darwin.h" @interface areaView : NSView { uiArea *libui_a; NSTrackingArea *libui_ta; } - (id)initWithFrame:(NSRect)r area:(uiArea *)a; - (uiModifiers)parseModifiers:(NSEvent *)e; - (void)doMouseEvent:(NSEvent *)e; - (int)sendKeyEvent:(uiAreaKeyEvent *)ke; - (int)doKeyDownUp:(NSEvent *)e up:(int)up; - (int)doKeyDown:(NSEvent *)e; - (int)doKeyUp:(NSEvent *)e; - (int)doFlagsChanged:(NSEvent *)e; - (void)setupNewTrackingArea; @end struct uiArea { uiDarwinControl c; NSView *view; // either sv or area depending on whether it is scrolling NSScrollView *sv; areaView *area; uiAreaHandler *ah; BOOL scrolling; }; uiDarwinDefineControl( uiArea, // type name view // handle ) @implementation areaView - (id)initWithFrame:(NSRect)r area:(uiArea *)a { self = [super initWithFrame:r]; if (self) { self->libui_a = a; [self setupNewTrackingArea]; } return self; } - (void)drawRect:(NSRect)r { uiArea *a = self->libui_a; CGContextRef c; uiAreaDrawParams dp; c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort]; // see draw.m under text for why we need the height dp.Context = newContext(c, [self bounds].size.height); dp.AreaWidth = 0; dp.AreaHeight = 0; if (!a->scrolling) { // TODO frame or bounds? dp.AreaWidth = [self frame].size.width; dp.AreaHeight = [self frame].size.height; } dp.ClipX = r.origin.x; dp.ClipY = r.origin.y; dp.ClipWidth = r.size.width; dp.ClipHeight = r.size.height; // no need to save or restore the graphics state to reset transformations; Cocoa creates a brand-new context each time (*(a->ah->Draw))(a->ah, a, &dp); freeContext(dp.Context); } - (BOOL)isFlipped { return YES; } - (BOOL)acceptsFirstResponder { return YES; } - (uiModifiers)parseModifiers:(NSEvent *)e { NSEventModifierFlags mods; uiModifiers m; m = 0; mods = [e modifierFlags]; if ((mods & NSControlKeyMask) != 0) m |= uiModifierCtrl; if ((mods & NSAlternateKeyMask) != 0) m |= uiModifierAlt; if ((mods & NSShiftKeyMask) != 0) m |= uiModifierShift; if ((mods & NSCommandKeyMask) != 0) m |= uiModifierSuper; return m; } - (void)setupNewTrackingArea { // TODO NSTrackingAssumeInside? self->libui_ta = [[NSTrackingArea alloc] initWithRect:[self bounds] options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingInVisibleRect | NSTrackingEnabledDuringMouseDrag) owner:self userInfo:nil]; [self addTrackingArea:self->libui_ta]; } // TODO when do we call super here? - (void)updateTrackingAreas { [self removeTrackingArea:self->libui_ta]; [self->libui_ta release]; [self setupNewTrackingArea]; } // capture on drag is done automatically on OS X - (void)doMouseEvent:(NSEvent *)e { uiArea *a = self->libui_a; uiAreaMouseEvent me; NSPoint point; uintmax_t buttonNumber; NSUInteger pmb; unsigned int i, max; // this will convert point to drawing space // thanks swillits in irc.freenode.net/#macdev point = [self convertPoint:[e locationInWindow] fromView:nil]; me.X = point.x; me.Y = point.y; me.AreaWidth = 0; me.AreaHeight = 0; if (!a->scrolling) { // TODO frame or bounds? me.AreaWidth = [self frame].size.width; me.AreaHeight = [self frame].size.height; } buttonNumber = [e buttonNumber] + 1; // swap button numbers 2 and 3 (right and middle) if (buttonNumber == 2) buttonNumber = 3; else if (buttonNumber == 3) buttonNumber = 2; me.Down = 0; me.Up = 0; me.Count = 0; switch ([e type]) { case NSLeftMouseDown: case NSRightMouseDown: case NSOtherMouseDown: me.Down = buttonNumber; me.Count = [e clickCount]; break; case NSLeftMouseUp: case NSRightMouseUp: case NSOtherMouseUp: me.Up = buttonNumber; break; case NSLeftMouseDragged: case NSRightMouseDragged: case NSOtherMouseDragged: // we include the button that triggered the dragged event in the Held fields buttonNumber = 0; break; } me.Modifiers = [self parseModifiers:e]; pmb = [NSEvent pressedMouseButtons]; me.Held1To64 = 0; if (buttonNumber != 1 && (pmb & 1) != 0) me.Held1To64 |= 1; if (buttonNumber != 2 && (pmb & 4) != 0) me.Held1To64 |= 2; if (buttonNumber != 3 && (pmb & 2) != 0) me.Held1To64 |= 4; // buttons 4..64 max = 32; // TODO are the upper 32 bits just mirrored? // if (sizeof (NSUInteger) == 8) // max = 64; for (i = 4; i <= max; i++) { uint64_t j; if (buttonNumber == i) continue; j = 1 << (i - 1); if ((pmb & j) != 0) me.Held1To64 |= j; } (*(a->ah->MouseEvent))(a->ah, a, &me); } #define mouseEvent(name) \ - (void)name:(NSEvent *)e \ { \ [self doMouseEvent:e]; \ } mouseEvent(mouseMoved) mouseEvent(mouseDragged) mouseEvent(rightMouseDragged) mouseEvent(otherMouseDragged) mouseEvent(mouseDown) mouseEvent(rightMouseDown) mouseEvent(otherMouseDown) mouseEvent(mouseUp) mouseEvent(rightMouseUp) mouseEvent(otherMouseUp) - (void)mouseEntered:(NSEvent *)e { uiArea *a = self->libui_a; (*(a->ah->MouseCrossed))(a->ah, a, 0); } - (void)mouseExited:(NSEvent *)e { uiArea *a = self->libui_a; (*(a->ah->MouseCrossed))(a->ah, a, 1); } // note: there is no equivalent to WM_CAPTURECHANGED on Mac OS X; there literally is no way to break a grab like that // even if I invoke the task switcher and switch processes, the mouse grab will still be held until I let go of all buttons // therefore, no DragBroken() - (int)sendKeyEvent:(uiAreaKeyEvent *)ke { uiArea *a = self->libui_a; return (*(a->ah->KeyEvent))(a->ah, a, ke); } - (int)doKeyDownUp:(NSEvent *)e up:(int)up { uiAreaKeyEvent ke; ke.Key = 0; ke.ExtKey = 0; ke.Modifier = 0; ke.Modifiers = [self parseModifiers:e]; ke.Up = up; if (!fromKeycode([e keyCode], &ke)) return 0; return [self sendKeyEvent:&ke]; } - (int)doKeyDown:(NSEvent *)e { return [self doKeyDownUp:e up:0]; } - (int)doKeyUp:(NSEvent *)e { return [self doKeyDownUp:e up:1]; } - (int)doFlagsChanged:(NSEvent *)e { uiAreaKeyEvent ke; uiModifiers whichmod; ke.Key = 0; ke.ExtKey = 0; // Mac OS X sends this event on both key up and key down. // Fortunately -[e keyCode] IS valid here, so we can simply map from key code to Modifiers, get the value of [e modifierFlags], and check if the respective bit is set or not — that will give us the up/down state if (!keycodeModifier([e keyCode], &whichmod)) return 0; ke.Modifier = whichmod; ke.Modifiers = [self parseModifiers:e]; ke.Up = (ke.Modifiers & ke.Modifier) == 0; // and then drop the current modifier from Modifiers ke.Modifiers &= ~ke.Modifier; return [self sendKeyEvent:&ke]; } - (void)setFrameSize:(NSSize)size { uiArea *a = self->libui_a; [super setFrameSize:size]; if (!a->scrolling) // we must redraw everything on resize because Windows requires it [self setNeedsDisplay:YES]; } @end // called by subclasses of -[NSApplication sendEvent:] // by default, NSApplication eats some key events // this prevents that from happening with uiArea // see http://stackoverflow.com/questions/24099063/how-do-i-detect-keyup-in-my-nsview-with-the-command-key-held and http://lists.apple.com/archives/cocoa-dev/2003/Oct/msg00442.html int sendAreaEvents(NSEvent *e) { NSEventType type; id focused; areaView *view; type = [e type]; if (type != NSKeyDown && type != NSKeyUp && type != NSFlagsChanged) return 0; focused = [[e window] firstResponder]; if (focused == nil) return 0; if (![focused isKindOfClass:[areaView class]]) return 0; view = (areaView *) focused; switch (type) { case NSKeyDown: return [view doKeyDown:e]; case NSKeyUp: return [view doKeyUp:e]; case NSFlagsChanged: return [view doFlagsChanged:e]; } return 0; } void uiAreaSetSize(uiArea *a, intmax_t width, intmax_t height) { if (!a->scrolling) complain("attempt to call uiAreaSetSize() on a non-scrolling uiArea"); [a->area setFrameSize:NSMakeSize(width, height)]; } void uiAreaQueueRedrawAll(uiArea *a) { [a->area setNeedsDisplay:YES]; } void uiAreaScrollTo(uiArea *a, double x, double y, double width, double height) { if (!a->scrolling) complain("attempt to call uiAreaScrollTo() on a non-scrolling uiArea"); [a->area scrollRectToVisible:NSMakeRect(x, y, width, height)]; // don't worry about the return value; it just says whether scrolling was needed } uiArea *uiNewArea(uiAreaHandler *ah) { uiArea *a; a = (uiArea *) uiNewControl(uiArea); a->ah = ah; a->scrolling = NO; a->area = [[areaView alloc] initWithFrame:NSZeroRect area:a]; a->view = a->area; uiDarwinFinishNewControl(a, uiArea); return a; } uiArea *uiNewScrollingArea(uiAreaHandler *ah, intmax_t width, intmax_t height) { uiArea *a; a = (uiArea *) uiNewControl(uiAreaType()); a->ah = ah; a->scrolling = YES; a->sv = [[NSScrollView alloc] initWithFrame:NSZeroRect]; // TODO configure a->sv for real [a->sv setHasHorizontalScroller:YES]; [a->sv setHasVerticalScroller:YES]; a->area = [[areaView alloc] initWithFrame:NSMakeRect(0, 0, width, height) area:a]; a->view = a->sv; [a->sv setDocumentView:a->area]; uiDarwinFinishNewControl(a, uiArea); return a; }