Added the Mac OS X implementation of Area... somewhat messily, but eh.

This commit is contained in:
Pietro Gagliardi 2014-08-05 14:33:25 -04:00
parent 6de4565e0c
commit 6b7660a671
3 changed files with 428 additions and 0 deletions

228
redo/area_darwin.go Normal file
View File

@ -0,0 +1,228 @@
// 29 march 2014
package ui
import (
"image"
"unsafe"
)
//// #include <HIToolbox/Events.h>
// #include "objc_darwin.h"
import "C"
type area struct {
*areabase
_id C.id
scroller *scroller
}
func newArea(ab *areabase) Area {
a := &area{
areabase: ab,
}
a._id = C.newArea(unsafe.Pointer(a))
a.scroller = newScroller(a._id)
a.SetSize(a.width, a.height)
return a
}
func (a *area) SetSize(width, height int) {
a.width = width
a.height = height
// TODO different?
C.moveControl(a._id, 0, 0, C.intptr_t(a.width), C.intptr_t(a.height))
}
func (a *area) RepaintAll() {
C.display(a._id)
}
//export areaView_drawRect
func areaView_drawRect(self C.id, rect C.struct_xrect, data unsafe.Pointer) {
a := (*area)(data)
// no need to clear the clip rect; the NSScrollView does that for us (see the setDrawsBackground: call in objc_darwin.m)
// rectangles in Cocoa are origin/size, not point0/point1; if we don't watch for this, weird things will happen when scrolling
cliprect := image.Rect(int(rect.x), int(rect.y), int(rect.x+rect.width), int(rect.y+rect.height))
max := C.frame(self)
cliprect = image.Rect(0, 0, int(max.width), int(max.height)).Intersect(cliprect)
if cliprect.Empty() { // no intersection; nothing to paint
return
}
i := a.handler.Paint(cliprect)
C.drawImage(
unsafe.Pointer(pixelData(i)), C.intptr_t(i.Rect.Dx()), C.intptr_t(i.Rect.Dy()), C.intptr_t(i.Stride),
C.intptr_t(cliprect.Min.X), C.intptr_t(cliprect.Min.Y))
}
func parseModifiers(e C.id) (m Modifiers) {
const (
// TODO define these on the Objective-C side
_NSShiftKeyMask = 1 << 17
_NSControlKeyMask = 1 << 18
_NSAlternateKeyMask = 1 << 19
_NSCommandKeyMask = 1 << 20
)
mods := uintptr(C.modifierFlags(e))
if (mods & _NSControlKeyMask) != 0 {
m |= Ctrl
}
if (mods & _NSAlternateKeyMask) != 0 {
m |= Alt
}
if (mods & _NSShiftKeyMask) != 0 {
m |= Shift
}
if (mods & _NSCommandKeyMask) != 0 {
m |= Super
}
return m
}
func areaMouseEvent(self C.id, e C.id, click bool, up bool, data unsafe.Pointer) {
var me MouseEvent
a := (*area)(data)
xp := C.getTranslatedEventPoint(self, e)
me.Pos = image.Pt(int(xp.x), int(xp.y))
// for the most part, Cocoa won't geenerate an event outside the Area... except when dragging outside the Area, so check for this
max := C.frame(self)
if !me.Pos.In(image.Rect(0, 0, int(max.width), int(max.height))) {
return
}
me.Modifiers = parseModifiers(e)
which := uint(C.buttonNumber(e)) + 1
if which == 3 { // swap middle and right button numbers
which = 2
} else if which == 2 {
which = 3
}
if click && up {
me.Up = which
} else if click {
me.Down = which
// this already works the way we want it to so nothing special needed like with Windows and GTK+
me.Count = uint(C.clickCount(e))
} else {
which = 0 // reset for Held processing below
}
// the docs do say don't use this for tracking (mouseMoved:) since it returns the state now, and mouse move events work by tracking, but as far as I can tell dragging the mouse over the inactive window does not generate an event on Mac OS X, so :/ (tracking doesn't touch dragging anyway except during mouseEntered: and mouseExited:, which we don't handle, and the only other tracking message, cursorChanged:, we also don't handle (yet...? need to figure out if this is how to set custom cursors or not), so)
held := C.pressedMouseButtons()
if which != 1 && (held&1) != 0 { // button 1
me.Held = append(me.Held, 1)
}
if which != 2 && (held&4) != 0 { // button 2; mind the swap
me.Held = append(me.Held, 2)
}
if which != 3 && (held&2) != 0 { // button 3
me.Held = append(me.Held, 3)
}
held >>= 3
for i := uint(4); held != 0; i++ {
if which != i && (held&1) != 0 {
me.Held = append(me.Held, i)
}
held >>= 1
}
repaint := a.handler.Mouse(me)
if repaint {
C.display(self)
}
}
//export areaView_mouseMoved_mouseDragged
func areaView_mouseMoved_mouseDragged(self C.id, e C.id, data unsafe.Pointer) {
// for moving, this is handled by the tracking rect stuff above
// for dragging, if multiple buttons are held, only one of their xxxMouseDragged: messages will be sent, so this is OK to do
areaMouseEvent(self, e, false, false, data)
}
//export areaView_mouseDown
func areaView_mouseDown(self C.id, e C.id, data unsafe.Pointer) {
// no need to manually set focus; Mac OS X has already done that for us by this point since we set our view to be a first responder
areaMouseEvent(self, e, true, false, data)
}
//export areaView_mouseUp
func areaView_mouseUp(self C.id, e C.id, data unsafe.Pointer) {
areaMouseEvent(self, e, true, true, data)
}
func sendKeyEvent(self C.id, ke KeyEvent, data unsafe.Pointer) {
a := (*area)(data)
repaint := a.handler.Key(ke)
if repaint {
C.display(self)
}
}
func areaKeyEvent(self C.id, e C.id, up bool, data unsafe.Pointer) {
var ke KeyEvent
keyCode := uintptr(C.keyCode(e))
ke, ok := fromKeycode(keyCode)
if !ok {
// no such key; modifiers by themselves are handled by -[self flagsChanged:]
return
}
// either ke.Key or ke.ExtKey will be set at this point
ke.Modifiers = parseModifiers(e)
ke.Up = up
sendKeyEvent(self, ke, data)
}
//export areaView_keyDown
func areaView_keyDown(self C.id, e C.id, data unsafe.Pointer) {
areaKeyEvent(self, e, false, data)
}
//export areaView_keyUp
func areaView_keyUp(self C.id, e C.id, data unsafe.Pointer) {
areaKeyEvent(self, e, true, data)
}
//export areaView_flagsChanged
func areaView_flagsChanged(self C.id, e C.id, data unsafe.Pointer) {
var ke KeyEvent
// 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
keyCode := uintptr(C.keyCode(e))
mod, ok := keycodeModifiers[keyCode] // comma-ok form to avoid adding entries
if !ok { // unknown modifier; ignore
return
}
ke.Modifiers = parseModifiers(e)
ke.Up = (ke.Modifiers & mod) == 0
ke.Modifier = mod
// don't include the modifier in ke.Modifiers
ke.Modifiers &^= mod
sendKeyEvent(self, ke, data)
}
func (a *area) id() C.id {
return a._id
}
func (a *area) setParent(p *controlParent) {
a.scroller.setParent(p)
}
func (a *area) allocate(x int, y int, width int, height int, d *sizing) []*allocation {
return baseallocate(a, x, y, width, height, d)
}
func (a *area) preferredSize(d *sizing) (width, height int) {
// the preferred size of an Area is its size
return a.width, a.height
}
func (a *area) commitResize(c *allocation, d *sizing) {
a.scroller.commitResize(c, d)
}
func (a *area) getAuxResizeInfo(d *sizing) {
basegetAuxResizeInfo(a, d)
}

185
redo/area_darwin.m Normal file
View File

@ -0,0 +1,185 @@
// 13 may 2014
#include "objc_darwin.h"
#include "_cgo_export.h"
#import <Cocoa/Cocoa.h>
#define toNSEvent(x) ((NSEvent *) (x))
// TODO rename to goAreaView
#define toAreaView(x) ((areaView *) (x))
#define toNSView(x) ((NSView *) (x))
#define toNSInteger(x) ((NSInteger) (x))
#define fromNSInteger(x) ((intptr_t) (x))
#define toNSUInteger(x) ((NSUInteger) (x))
#define fromNSUInteger(x) ((uintptr_t) (x))
@interface areaView : NSView {
@public
void *goarea;
NSTrackingArea *trackingArea;
}
@end
@implementation areaView
- (id)initWithFrame:(NSRect)r
{
self = [super initWithFrame:r];
if (self)
[self retrack];
// TODO other properties?
return self;
}
- (void)drawRect:(NSRect)cliprect
{
struct xrect rect;
rect.x = (intptr_t) cliprect.origin.x;
rect.y = (intptr_t) cliprect.origin.y;
rect.width = (intptr_t) cliprect.size.width;
rect.height = (intptr_t) cliprect.size.height;
areaView_drawRect(self, rect, self->goarea);
}
- (BOOL)isFlipped
{
return YES;
}
- (BOOL)acceptsFirstResponder
{
return YES;
}
// this will have the Area receive a click that switches to the Window it is in from another one
- (BOOL)acceptsFirstMouse:(NSEvent *)e
{
return YES;
}
- (void)retrack
{
self->trackingArea = [[NSTrackingArea alloc]
initWithRect:[self bounds]
// this bit mask (except for NSTrackingInVisibleRect, which was added later to prevent events from being triggered outside the visible area of the Area) comes from https://github.com/andlabs/misctestprogs/blob/master/cocoaviewmousetest.m (and I wrote this bit mask on 25 april 2014) and yes I know it includes enter/exit even though we don't watch those events; it probably won't really matter anyway but if it does I can change it easily
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways | NSTrackingEnabledDuringMouseDrag | NSTrackingInVisibleRect)
owner:self
userInfo:nil];
[self addTrackingArea:self->trackingArea];
}
- (void)updateTrackingAreas
{
[self removeTrackingArea:self->trackingArea];
[self->trackingArea release];
[self retrack];
}
#define event(m, f) \
- (void)m:(NSEvent *)e \
{ \
f(self, e, self->goarea); \
}
event(mouseMoved, areaView_mouseMoved_mouseDragged)
event(mouseDragged, areaView_mouseMoved_mouseDragged)
event(rightMouseDragged, areaView_mouseMoved_mouseDragged)
event(otherMouseDragged, areaView_mouseMoved_mouseDragged)
event(mouseDown, areaView_mouseDown)
event(rightMouseDown, areaView_mouseDown)
event(otherMouseDown, areaView_mouseDown)
event(mouseUp, areaView_mouseUp)
event(rightMouseUp, areaView_mouseUp)
event(otherMouseUp, areaView_mouseUp)
event(keyDown, areaView_keyDown)
event(keyUp, areaView_keyUp)
event(flagsChanged, areaView_flagsChanged)
@end
Class areaClass;
void initAreaClass(void)
{
areaClass = [areaView class];
}
id newArea(void *goarea)
{
areaView *a;
a = [[areaView alloc] initWithFrame:NSZeroRect];
a->goarea = goarea;
return (id) a;
}
void drawImage(void *pixels, intptr_t width, intptr_t height, intptr_t stride, intptr_t xdest, intptr_t ydest)
{
unsigned char *planes[1]; // NSBitmapImageRep wants an array of planes; we have one plane
NSBitmapImageRep *bitmap;
planes[0] = (unsigned char *) pixels;
bitmap = [[NSBitmapImageRep alloc]
initWithBitmapDataPlanes:planes
pixelsWide:toNSInteger(width)
pixelsHigh:toNSInteger(height)
bitsPerSample:8
samplesPerPixel:4
hasAlpha:YES
isPlanar:NO
colorSpaceName:NSCalibratedRGBColorSpace // TODO NSDeviceRGBColorSpace?
bitmapFormat:0 // this is where the flag for placing alpha first would go if alpha came first; the default is alpha last, which is how we're doing things (otherwise the docs say "Color planes are arranged in the standard order—for example, red before green before blue for RGB color."); this is also where the flag for non-premultiplied colors would go if we used it (the default is alpha-premultiplied)
bytesPerRow:toNSInteger(stride)
bitsPerPixel:32];
// TODO this CAN fali; check error
[bitmap drawInRect:NSMakeRect((CGFloat) xdest, (CGFloat) ydest, (CGFloat) width, (CGFloat) height)
fromRect:NSZeroRect // draw whole image
operation:NSCompositeSourceOver
fraction:1.0
respectFlipped:YES
hints:nil];
[bitmap release];
}
uintptr_t modifierFlags(id e)
{
return fromNSUInteger([toNSEvent(e) modifierFlags]);
}
struct xpoint getTranslatedEventPoint(id area, id e)
{
NSPoint p;
struct xpoint q;
p = [toAreaView(area) convertPoint:[toNSEvent(e) locationInWindow] fromView:nil];
q.x = (intptr_t) p.x;
q.y = (intptr_t) p.y;
return q;
}
intptr_t buttonNumber(id e)
{
return fromNSInteger([toNSEvent(e) buttonNumber]);
}
intptr_t clickCount(id e)
{
return fromNSInteger([toNSEvent(e) clickCount]);
}
uintptr_t pressedMouseButtons(void)
{
return fromNSUInteger([NSEvent pressedMouseButtons]);
}
uintptr_t keyCode(id e)
{
return (uintptr_t) ([toNSEvent(e) keyCode]);
}
// TODO move to common_darwin.m?
void display(id view)
{
[toNSView(view) display];
}

View File

@ -89,4 +89,19 @@ extern struct xsize areaPrefSize(id);
extern struct xalignment alignmentInfo(id, struct xrect);
extern struct xrect frame(id);
/* area_darwin.h */
struct xpoint {
intptr_t x;
intptr_t y;
};
extern id newArea(void *);
extern void drawImage(void *, intptr_t, intptr_t, intptr_t, intptr_t, intptr_t);
extern uintptr_t modifierFlags(id);
extern struct xpoint getTranslatedEventPoint(id, id);
extern intptr_t buttonNumber(id);
extern intptr_t clickCount(id);
extern uintptr_t pressedMouseButtons(void);
extern uintptr_t keyCode(id);
extern void display(id);
#endif