2014-03-29 22:57:49 -05:00
// 29 march 2014
package ui
import (
"unsafe"
2014-03-30 13:25:01 -05:00
"image"
2014-03-29 22:57:49 -05:00
)
// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include <stdlib.h>
//// #include <HIToolbox/Events.h>
// #include "objc_darwin.h"
2014-03-30 16:52:27 -05:00
// extern BOOL areaView_isFlipped_acceptsFirstResponder(id, SEL);
2014-05-10 20:03:04 -05:00
// extern void areaView_updateTrackingAreas(id, SEL);
2014-05-07 18:40:46 -05:00
// extern void areaView_mouseMoved_mouseDragged(id, SEL, id);
// extern void areaView_mouseDown(id, SEL, id);
2014-03-30 16:52:27 -05:00
// extern void areaView_mouseUp(id, SEL, id);
2014-03-30 18:53:44 -05:00
// extern void areaView_keyDown(id, SEL, id);
// extern void areaView_keyUp(id, SEL, id);
// extern void areaView_flagsChanged(id, SEL, id);
2014-03-29 22:57:49 -05:00
import "C"
const (
2014-03-30 10:19:13 -05:00
__goArea = "goArea"
2014-03-29 22:57:49 -05:00
)
var (
2014-03-30 10:19:13 -05:00
_goArea C . id
2014-04-04 20:32:10 -05:00
_NSView = objc_getClass ( "NSView" )
2014-03-29 22:57:49 -05:00
)
2014-04-04 20:32:10 -05:00
var goAreaSels = [ ] selector {
2014-04-04 21:21:53 -05:00
selector { "drawRect:" , uintptr ( C . _areaView_drawRect ) , sel_void_rect ,
"actually drawing Areas" } ,
2014-04-04 20:32:10 -05:00
selector { "isFlipped" , uintptr ( C . areaView_isFlipped_acceptsFirstResponder ) , sel_bool ,
"ensuring that an Area's coordinate system has (0,0) at the top-left corner" } ,
selector { "acceptsFirstResponder" , uintptr ( C . areaView_isFlipped_acceptsFirstResponder ) , sel_bool ,
"registering that Areas are to receive events" } ,
2014-05-10 20:03:04 -05:00
selector { "updateTrackingAreas" , uintptr ( C . areaView_updateTrackingAreas ) , sel_void ,
"updating tracking areas for handling mouse movements in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "mouseMoved:" , uintptr ( C . areaView_mouseMoved_mouseDragged ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse movements in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "mouseDown:" , uintptr ( C . areaView_mouseDown ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 1 presses in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "mouseDragged:" , uintptr ( C . areaView_mouseMoved_mouseDragged ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 1 dragging in Area" } ,
selector { "mouseUp:" , uintptr ( C . areaView_mouseUp ) , sel_void_id ,
"handling mouse button 1 releases in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "rightMouseDown:" , uintptr ( C . areaView_mouseDown ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 3 presses in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "rightMouseDragged:" , uintptr ( C . areaView_mouseMoved_mouseDragged ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 3 dragging in Area" } ,
selector { "rightMouseUp:" , uintptr ( C . areaView_mouseUp ) , sel_void_id ,
"handling mouse button 3 releases in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "otherMouseDown:" , uintptr ( C . areaView_mouseDown ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 2 (and 4 and higher) presses in Area" } ,
2014-05-07 18:40:46 -05:00
selector { "otherMouseDragged:" , uintptr ( C . areaView_mouseMoved_mouseDragged ) , sel_void_id ,
2014-04-04 20:32:10 -05:00
"handling mouse button 2 (and 4 and higher) dragging in Area" } ,
selector { "otherMouseUp:" , uintptr ( C . areaView_mouseUp ) , sel_void_id ,
"handling mouse button 2 (and 4 and higher) releases in Area" } ,
selector { "keyDown:" , uintptr ( C . areaView_keyDown ) , sel_void_id ,
"handling key presses in Area" } ,
selector { "keyUp:" , uintptr ( C . areaView_keyUp ) , sel_void_id ,
"handling key releases in Area" } ,
selector { "flagsChanged:" , uintptr ( C . areaView_flagsChanged ) , sel_void_id ,
"handling Modifiers presses and releases in Area" } ,
2014-03-30 16:52:27 -05:00
}
2014-03-29 22:57:49 -05:00
func mkAreaClass ( ) error {
2014-04-04 21:21:53 -05:00
id , err := makeClass ( __goArea , _NSView , goAreaSels ,
2014-04-04 20:32:10 -05:00
"the implementation of Area on Mac OS X" )
2014-03-29 22:57:49 -05:00
if err != nil {
2014-04-04 20:32:10 -05:00
return err
2014-03-29 22:57:49 -05:00
}
2014-04-04 20:32:10 -05:00
_goArea = id
2014-03-29 22:57:49 -05:00
return nil
}
2014-05-10 13:59:11 -05:00
func makeArea ( parentWindow C . id , alternate bool , s * sysData ) C . id {
2014-04-13 11:52:10 -05:00
area := C . objc_msgSend_noargs ( _goArea , _alloc )
area = initWithDummyFrame ( area )
// TODO others?
2014-05-10 20:03:04 -05:00
retrack ( area , s )
2014-04-13 11:52:10 -05:00
area = newScrollView ( area )
addControl ( parentWindow , area )
return area
}
func areaInScrollView ( scrollview C . id ) C . id {
return getScrollViewContent ( scrollview )
}
2014-03-30 13:25:01 -05:00
var (
_drawAtPoint = sel_getUid ( "drawAtPoint:" )
)
2014-03-29 22:57:49 -05:00
//export areaView_drawRect
func areaView_drawRect ( self C . id , rect C . struct_xrect ) {
2014-03-30 13:25:01 -05:00
s := getSysData ( self )
// TODO clear clip rect
2014-03-30 13:29:02 -05:00
// rectangles in Cocoa are origin/size, not point0/point1; if we don't watch for this, weird things will happen when scrolling
// TODO change names EVERYWHERE ELSE to match
cliprect := image . Rect ( int ( rect . x ) , int ( rect . y ) , int ( rect . x + rect . width ) , int ( rect . y + rect . height ) )
2014-03-30 13:25:01 -05:00
max := C . objc_msgSend_stret_rect_noargs ( self , _frame )
cliprect = image . Rect ( 0 , 0 , int ( max . width ) , int ( max . height ) ) . Intersect ( cliprect )
if cliprect . Empty ( ) { // no intersection; nothing to paint
return
}
i := s . handler . Paint ( cliprect )
C . drawImage (
2014-04-09 17:26:20 -05:00
unsafe . Pointer ( pixelData ( i ) ) , C . int64_t ( i . Rect . Dx ( ) ) , C . int64_t ( i . Rect . Dy ( ) ) , C . int64_t ( i . Stride ) ,
2014-03-30 13:25:01 -05:00
C . int64_t ( cliprect . Min . X ) , C . int64_t ( cliprect . Min . Y ) )
}
2014-03-30 16:52:27 -05:00
//export areaView_isFlipped_acceptsFirstResponder
func areaView_isFlipped_acceptsFirstResponder ( self C . id , sel C . SEL ) C . BOOL {
// yes use the same function for both methods since they're just going to return YES anyway
// isFlipped gives us a coordinate system with (0,0) at the top-left
// acceptsFirstResponder lets us respond to events
2014-03-30 13:25:01 -05:00
return C . BOOL ( C . YES )
2014-03-30 10:19:13 -05:00
}
2014-05-10 20:03:04 -05:00
var (
_addTrackingArea = sel_getUid ( "addTrackingArea:" )
_removeTrackingArea = sel_getUid ( "removeTrackingArea:" )
)
func retrack ( area C . id , s * sysData ) {
s . trackingArea = C . makeTrackingArea ( area )
C . objc_msgSend_id ( area , _addTrackingArea , s . trackingArea )
}
//export areaView_updateTrackingAreas
func areaView_updateTrackingAreas ( self C . id , sel C . SEL ) {
s := getSysData ( self )
C . objc_msgSend_id ( self , _removeTrackingArea , s . trackingArea )
C . objc_msgSend_noargs ( s . trackingArea , _release )
retrack ( self , s )
}
2014-03-30 16:52:27 -05:00
var (
2014-04-03 22:52:26 -05:00
_NSEvent = objc_getClass ( "NSEvent" )
2014-03-30 16:52:27 -05:00
_modifierFlags = sel_getUid ( "modifierFlags" )
_buttonNumber = sel_getUid ( "buttonNumber" )
_clickCount = sel_getUid ( "clickCount" )
2014-04-03 22:52:26 -05:00
_pressedMouseButtons = sel_getUid ( "pressedMouseButtons" )
2014-03-30 16:52:27 -05:00
)
func parseModifiers ( e C . id ) ( m Modifiers ) {
const (
_NSShiftKeyMask = 1 << 17
_NSControlKeyMask = 1 << 18
_NSAlternateKeyMask = 1 << 19
_NSCommandKeyMask = 1 << 20
)
mods := uintptr ( C . objc_msgSend_uintret_noargs ( e , _modifierFlags ) )
if ( mods & _NSShiftKeyMask ) != 0 {
m |= Shift
}
if ( mods & _NSControlKeyMask ) != 0 {
// TODO
}
if ( mods & _NSAlternateKeyMask ) != 0 {
m |= Alt
}
if ( mods & _NSCommandKeyMask ) != 0 {
m |= Ctrl
}
return m
}
2014-05-07 18:40:46 -05:00
// TODO pressing both buttons 1 and 3 simultaneously gets turned into button 2; see if we can turn that off for our NSView only
2014-03-30 16:52:27 -05:00
func areaMouseEvent ( self C . id , e C . id , click bool , up bool ) {
var me MouseEvent
s := getSysData ( self )
xp := C . getTranslatedEventPoint ( self , e )
me . Pos = image . Pt ( int ( xp . x ) , int ( xp . y ) )
2014-05-12 10:34:24 -05:00
// 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 . objc_msgSend_stret_rect_noargs ( self , _frame )
if ! me . Pos . In ( image . Rect ( 0 , 0 , int ( max . width ) , int ( max . height ) ) ) {
return
}
2014-03-30 16:52:27 -05:00
me . Modifiers = parseModifiers ( e )
which := uint ( C . objc_msgSend_intret_noargs ( e , _buttonNumber ) ) + 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
me . Count = uint ( C . objc_msgSend_intret_noargs ( e , _clickCount ) )
} else {
which = 0 // reset for Held processing below
}
2014-04-03 22:52:26 -05:00
// pressedMouseButtons is a class method; calling objc_msgSend() directly with e as an argument on these will panic (in real Objective-C the compiler can detect [e pressedMouseButtons])
2014-05-10 20:03:04 -05:00
// the docs do say don't use this for tracking 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 n ot generate an event on Mac OS X, so :/ (TODO see what happens when the program is the current one; in my own separate tests no harm was done so eh; also no need for this if tracking doesn't touch dragging)
2014-04-03 22:52:26 -05:00
held := C . objc_msgSend_uintret_noargs ( _NSEvent , _pressedMouseButtons )
2014-03-30 16:52:27 -05:00
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 := s . handler . Mouse ( me )
if repaint {
C . objc_msgSend_noargs ( self , _display )
}
}
2014-05-07 18:40:46 -05:00
//export areaView_mouseMoved_mouseDragged
func areaView_mouseMoved_mouseDragged ( self C . id , sel C . SEL , e C . id ) {
2014-05-10 20:03:04 -05:00
// for moving, this is handled by the tracking rect stuff above
2014-05-10 13:31:22 -05:00
// for dragging, if multiple buttons are held, only one of their xxxMouseDragged: messages will be sent, so this is OK to do
2014-03-30 16:52:27 -05:00
areaMouseEvent ( self , e , false , false )
}
2014-05-07 18:40:46 -05:00
//export areaView_mouseDown
func areaView_mouseDown ( self C . id , sel C . SEL , e C . id ) {
2014-03-30 16:52:27 -05:00
areaMouseEvent ( self , e , true , false )
}
//export areaView_mouseUp
func areaView_mouseUp ( self C . id , sel C . SEL , e C . id ) {
areaMouseEvent ( self , e , true , true )
}
2014-03-30 18:53:44 -05:00
var (
_keyCode = sel_getUid ( "keyCode" )
)
func handleKeyEvent ( self C . id ) {
// TODO
}
func sendKeyEvent ( self C . id , ke KeyEvent ) {
s := getSysData ( self )
handled , repaint := s . handler . Key ( ke )
if repaint {
C . objc_msgSend_noargs ( self , _display )
}
if ! handled {
handleKeyEvent ( self )
}
}
func areaKeyEvent ( self C . id , e C . id , up bool ) {
var ke KeyEvent
keyCode := uintptr ( C . objc_msgSend_ushortret_noargs ( e , _keyCode ) )
ke , ok := fromKeycode ( keyCode )
if ! ok {
// no such key; modifiers by themselves are handled by -[self flagsChanged:]
handleKeyEvent ( self )
return
}
// either ke.Key or ke.ExtKey will be set at this point
ke . Modifiers = parseModifiers ( e )
ke . Up = up
sendKeyEvent ( self , ke )
}
//export areaView_keyDown
func areaView_keyDown ( self C . id , sel C . SEL , e C . id ) {
areaKeyEvent ( self , e , false )
}
//export areaView_keyUp
func areaView_keyUp ( self C . id , sel C . SEL , e C . id ) {
areaKeyEvent ( self , e , true )
}
//export areaView_flagsChanged
func areaView_flagsChanged ( self C . id , sel C . SEL , e C . id ) {
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], the respective bit is set or not — that will give us the up/down state
keyCode := uintptr ( C . objc_msgSend_ushortret_noargs ( e , _keyCode ) )
mod , ok := keycodeModifiers [ keyCode ] // comma-ok form to avoid adding entries
if ! ok { // unknown modifier; ignore
handleKeyEvent ( self )
return
}
ke . Modifiers = parseModifiers ( e )
ke . Up = ( ke . Modifiers & mod ) == 0
sendKeyEvent ( self , ke )
}