summaryrefslogtreecommitdiff
path: root/delegate_darwin.go
blob: 599994138b921c04f13e72266d1b5e441ce4bff4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 27 february 2014

package ui

import (
	"fmt"
	"unsafe"
)

/*
This creates a class goAppDelegate that will be used as the delegate for /everything/. Specifically, it:
	- runs uitask requests (uitask:)
	- handles window close events (windowShouldClose:)
	- handles window resize events (windowDidResize:)
	- handles button click events (buttonClicked:)
*/

// #cgo LDFLAGS: -lobjc -framework Foundation -framework AppKit
// #include <stdlib.h>
// #include "objc_darwin.h"
// extern void appDelegate_uitask(id, SEL, id);		/* from uitask_darwin.go */
// extern BOOL appDelegate_windowShouldClose(id, SEL, id);
// extern void appDelegate_windowDidResize(id, SEL, id);
// extern void appDelegate_buttonClicked(id, SEL, id);
import "C"

var (
	appDelegate C.id
)

const (
	_goAppDelegate = "goAppDelegate"
)

var (
	_uitask = sel_getUid("uitask:")
	_windowShouldClose = sel_getUid("windowShouldClose:")
	_windowDidResize = sel_getUid("windowDidResize:")
	_buttonClicked = sel_getUid("buttonClicked:")
)

func mkAppDelegate() error {
	appdelegateclass, err := makeDelegateClass(_goAppDelegate)
	if err != nil {
		return fmt.Errorf("error creating NSApplication delegate: %v", err)
	}
	err = addDelegateMethod(appdelegateclass, _uitask,
		C.appDelegate_uitask, delegate_void)
	if err != nil {
		return fmt.Errorf("error adding NSApplication delegate uitask: method (to do UI tasks): %v", err)
	}
	err = addDelegateMethod(appdelegateclass, _windowShouldClose,
		C.appDelegate_windowShouldClose, delegate_bool)
	if err != nil {
		return fmt.Errorf("error adding NSApplication delegate windowShouldClose: method (to handle window close button events): %v", err)
	}
	err = addDelegateMethod(appdelegateclass, _windowDidResize,
		C.appDelegate_windowDidResize, delegate_void)
	if err != nil {
		return fmt.Errorf("error adding NSApplication delegate windowDidResize: method (to handle window resize events): %v", err)
	}
	err = addDelegateMethod(appdelegateclass, _buttonClicked,
		C.appDelegate_buttonClicked, delegate_void)
	if err != nil {
		return fmt.Errorf("error adding NSApplication delegate buttonClicked: method (to handle button clicks): %v", err)
	}
	// TODO using objc_new() causes a segfault; find out why
	// TODO make alloc followed by init (I thought NSObject provided its own init?)
	appDelegate = objc_alloc(objc_getClass(_goAppDelegate))
	return nil
}

//export appDelegate_windowShouldClose
func appDelegate_windowShouldClose(self C.id, sel C.SEL, win C.id) C.BOOL {
	sysData := getSysData(win)
	sysData.signal()
	return C.BOOL(C.NO)		// don't close
}

var (
	_object = sel_getUid("object")
	_display = sel_getUid("display")
)

//export appDelegate_windowDidResize
func appDelegate_windowDidResize(self C.id, sel C.SEL, notification C.id) {
	win := C.objc_msgSend_noargs(notification, _object)
	sysData := getSysData(win)
	wincv := C.objc_msgSend_noargs(win, _contentView)		// we want the content view's size, not the window's; selector defined in sysdata_darwin.go
	r := C.objc_msgSend_stret_rect_noargs(wincv, _frame)
	if sysData.resize != nil {
		// winheight is used here because (0,0) is the bottom-left corner, not the top-left corner
		resizeList := sysData.resize(int(r.x), int(r.y), int(r.width), int(r.height))
		for _, s := range resizeList {
			err := s.sysData.setRect(s.x, s.y, s.width, s.height, int(r.height))
			if err != nil {
				panic("child resize failed: " + err.Error())
			}
		}
	}
	C.objc_msgSend_noargs(win, _display)		// redraw everything; TODO only if resize() was called?
}

//export appDelegate_buttonClicked
func appDelegate_buttonClicked(self C.id, sel C.SEL, button C.id) {
	sysData := getSysData(button)
	sysData.signal()
}

// this actually constructs the delegate class

var (
	_NSObject_Class = C.object_getClass(_NSObject)
)

func makeDelegateClass(name string) (C.Class, error) {
	cname := C.CString(name)
	defer C.free(unsafe.Pointer(cname))

	c := C.objc_allocateClassPair(_NSObject_Class, cname, 0)
	if c == C.NilClass {
		return C.NilClass, fmt.Errorf("unable to create Objective-C class %s; reason unknown", name)
	}
	C.objc_registerClassPair(c)
	return c, nil
}

var (
	delegate_void = []C.char{'v', '@', ':', '@', 0}		// void (*)(id, SEL, id)
	delegate_bool = []C.char{'c', '@', ':', '@', 0}		// BOOL (*)(id, SEL, id)
)

// according to errors spit out by cgo, C function pointers are unsafe.Pointer
func addDelegateMethod(class C.Class, sel C.SEL, imp unsafe.Pointer, ty []C.char) error {
	ok := C.class_addMethod(class, sel, C.IMP(imp), &ty[0])
	if ok == C.BOOL(C.NO) {
		// TODO get function name
		return fmt.Errorf("unable to add selector %v/imp %v (reason unknown)", sel, imp)
	}
	return nil
}