started macos port by adapting the old SGL code
[miniglut] / miniglut_osx.m
1 /*
2 MiniGLUT - minimal GLUT subset without dependencies
3 Copyright (C) 2020-2023  John Tsiombikas <nuclear@member.fsf.org>
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  */
18 #import <Cocoa/Cocoa.h>
19 #include "miniglut.h"
20
21 @interface OpenGLView : NSOpenGLView
22 {
23         int foo;
24 }
25
26 -(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf;
27
28 -(void) drawRect: (NSRect) rect;
29 -(void) reshape;
30 -(void) keyDown: (NSEvent*) ev;
31 -(void) keyUp: (NSEvent*) ev;
32 -(void) mouseDown: (NSEvent*) ev;
33 -(void) mouseUp: (NSEvent*) ev;
34 -(void) rightMouseDown: (NSEvent*) ev;
35 -(void) rightMouseUp: (NSEvent*) ev;
36 -(void) otherMouseDown: (NSEvent*) ev;
37 -(void) otherMouseUp: (NSEvent*) ev;
38 -(void) mouseDragged: (NSEvent*) ev;
39 -(void) rightMouseDragged: (NSEvent*) ev;
40 -(void) otherMouseDragged: (NSEvent*) ev;
41
42 -(BOOL) acceptsFirstResponder;
43 @end
44
45
46 @interface AppDelegate : NSObject
47 {
48         int foo;
49 }
50
51 -(void) applicationWillFinishLaunching: (NSNotification*) notification;
52 -(void) applicationDidFinishLaunching: (NSNotification*) notification;
53
54 -(BOOL) applicationShouldTerminate: (NSApplication*) app;
55 -(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app;
56 -(void) applicationWillTerminate: (NSNotification*) notification;
57 @end
58
59 struct window;
60
61 @interface WinDelegate : NSObject <NSWindowDelegate>
62 {
63         @public
64         struct window *win;
65 }
66 -(id) init;
67 -(void) dealloc;
68
69 -(void) windowDidExpose: (NSNotification*) notification;
70 -(void) windowDidResize: (NSNotification*) notification;
71 -(BOOL) windowShouldClose: (id) win;
72 -(void) windowWillClose: (NSNotification*) notification;
73 @end
74
75
76 static int init(void);
77 static void shutdown(void);
78
79 /* video mode switching */
80 static int set_vidmode(int xsz, int ysz);
81 static int get_vidmode(int *xsz, int *ysz);
82
83 /* create/destroy windows */
84 static int create_window(int xsz, int ysz, unsigned int flags);
85 static void close_window(void);
86
87 /* window management */
88 static int set_title(const char *str);
89 static void redisplay(void);
90 static void swap_buffers(void);
91
92 static int get_modifiers(void);
93
94 /* event handling and friends */
95 static void set_event(int idx, int enable);
96 static int process_events(void);
97
98 static void select_event_window(NSEvent *ev);
99 static void handle_key(NSEvent *ev, int state);
100 static void handle_mouse(NSEvent *ev, int state);
101 static void handle_motion(NSEvent *ev);
102
103 static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags);
104
105
106 static glut_cb cb_display;
107 static glut_cb cb_idle;
108 static glut_cb_reshape cb_reshape;
109 static glut_cb_state cb_vis, cb_entry;
110 static glut_cb_keyb cb_keydown, cb_keyup;
111 static glut_cb_special cb_skeydown, cb_skeyup;
112 static glut_cb_mouse cb_mouse;
113 static glut_cb_motion cb_motion, cb_passive;
114 static glut_cb_sbmotion cb_sball_motion, cb_sball_rotate;
115 static glut_cb_sbbutton cb_sball_button;
116
117 static int win_width, win_height;
118 static NSWindow *glwin;
119 static OpenGLView *glview;
120 static NSOpenGLContext *glctx;
121 static int needs_redisplay;
122 static int quit_main_loop;
123
124 static NSAutoreleasePool *global_pool;
125
126
127 void glutInit(int *argc, char **argv)
128 {
129 }
130
131
132
133 @implementation OpenGLView
134
135 -(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf
136 {
137         self = [super initWithFrame: frame pixelFormat: pf];
138         return self;
139 }
140
141 -(void) drawRect: (NSRect) rect
142 {
143         cb_display();
144 }
145
146 -(void) reshape
147 {
148         NSSize sz = [self bounds].size;
149
150         if(cb_reshape && (sz.width != win_width || sz.height != win_height)) {
151                 win_width = sz.width;
152                 win_height = sz.height;
153                 cb_reshape(sz.width, sz.height);
154         }
155 }
156
157 -(void) keyDown: (NSEvent*) ev
158 {
159         handle_key(ev, 1);
160 }
161
162 -(void) keyUp: (NSEvent*) ev
163 {
164         handle_key(ev, 0);
165 }
166
167 -(void) mouseDown: (NSEvent*) ev
168 {
169         handle_mouse(ev, 1);
170 }
171
172 -(void) mouseUp: (NSEvent*) ev
173 {
174         handle_mouse(ev, 0);
175 }
176
177 -(void) rightMouseDown: (NSEvent*) ev
178 {
179         handle_mouse(ev, 1);
180 }
181
182 -(void) rightMouseUp: (NSEvent*) ev
183 {
184         handle_mouse(ev, 0);
185 }
186
187 -(void) otherMouseDown: (NSEvent*) ev
188 {
189         handle_mouse(ev, 1);
190 }
191
192 -(void) otherMouseUp: (NSEvent*) ev
193 {
194         handle_mouse(ev, 0);
195 }
196
197 -(void) mouseDragged: (NSEvent*) ev
198 {
199         handle_motion(ev);
200 }
201
202 -(void) rightMouseDragged: (NSEvent*) ev
203 {
204         handle_motion(ev);
205 }
206
207 -(void) otherMouseDragged: (NSEvent*) ev
208 {
209         handle_motion(ev);
210 }
211
212
213 -(BOOL) acceptsFirstResponder
214 {
215         return YES;
216 }
217 @end
218
219 @implementation AppDelegate
220 -(void) applicationWillFinishLaunching: (NSNotification*) notification
221 {
222 }
223
224 -(void) applicationDidFinishLaunching: (NSNotification*) notification
225 {
226 }
227
228 -(BOOL) applicationShouldTerminate: (NSApplication*) app
229 {
230         return NSTerminateNow;
231 }
232
233 -(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app
234 {
235         return YES;
236 }
237
238 -(void) applicationWillTerminate: (NSNotification*) notification
239 {
240         /*[NSApp setDelegate: nil];
241         [global_pool drain];*/
242 }
243 @end
244
245 @implementation WinDelegate
246 -(id) init
247 {
248         self = [super init];
249         return self;
250 }
251
252 -(void) dealloc
253 {
254         [super dealloc];
255 }
256
257 -(void) windowDidExpose: (NSNotification*) notification
258 {
259 }
260
261 -(void) windowDidResize: (NSNotification*) notification
262 {
263 }
264
265 -(BOOL) windowShouldClose: (id) win
266 {
267         close_window();
268         return YES;
269 }
270
271 -(void) windowWillClose: (NSNotification*) notification
272 {
273         /*[NSApp terminate: nil];*/
274 }
275 @end
276
277 static int init(void)
278 {
279         AppDelegate *delegate;
280
281         global_pool = [[NSAutoreleasePool alloc] init];
282
283         [NSApplication sharedApplication];
284
285         delegate = [[AppDelegate alloc] init];
286         [NSApp setDelegate: delegate];
287         return 0;
288 }
289
290 static void shutdown(void)
291 {
292         close_window();
293
294         quit_main_loop = 1;
295         [NSApp terminate: nil];
296 }
297
298
299 /* create/destroy windows */
300 static int create_window(int xsz, int ysz, unsigned int flags)
301 {
302         NSAutoreleasePool *pool;
303         WinDelegate *delegate;
304         NSWindow *nswin;
305         NSRect rect;
306         OpenGLView *view;
307         NSOpenGLPixelFormat *pf;
308         NSOpenGLPixelFormatAttribute attr[32];
309         unsigned int style;
310
311         pool = [[NSAutoreleasePool alloc] init];
312
313         /* create the view */
314         fill_attr(attr, flags);
315         pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes: attr] autorelease];
316         view = [[OpenGLView alloc] initWithFrame: rect pixelFormat: pf];
317
318         /* create the window and attach the OpenGL view */
319         rect.origin.x = rect.origin.y = 0;
320         rect.size.width = xsz;
321         rect.size.height = ysz;
322
323         style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
324                 NSResizableWindowMask;
325
326         nswin = [[NSWindow alloc] initWithContentRect: rect styleMask: style
327                 backing: NSBackingStoreBuffered defer: YES];
328
329         delegate = [[WinDelegate alloc] init];
330
331         [nswin setDelegate: delegate];
332         [nswin setTitle: @"OpenGL/Cocoa"];
333         [nswin setReleasedWhenClosed: YES];
334         [nswin setContentView: view];
335         [nswin makeFirstResponder: view];
336         [nswin makeKeyAndOrderFront: nil];
337         [view release];
338
339         glwin = nswin;
340         glview = view;
341         glctx = [view openGLContext];
342         needs_redisplay = 1;
343
344         delegate->win = glwin;
345
346         [glctx makeCurrentContext];
347         [pool drain];
348         return win->wid;
349 }
350
351 static void close_window(void)
352 {
353         [glwin close];
354         shutdown();
355 }
356
357 static int set_title(const char *str)
358 {
359         NSString *nsstr;
360
361         nsstr = [[NSString alloc] initWithCString: str encoding: NSASCIIStringEncoding];
362         [glwin setTitle: nsstr];
363         [nsstr release];
364         return 0;
365 }
366
367 static void redisplay(void)
368 {
369         needs_redisplay = 1;
370 }
371
372 static void swap_buffers(void)
373 {
374         [glctx flushBuffer];
375 }
376
377
378 static int get_modifiers(void)
379 {
380         unsigned int nsmod = [NSEvent modifierFlags];
381         unsigned int mod = 0;
382
383         if(nsmod & NSShiftKeyMask) {
384                 mod |= GLUT_ACTIVE_SHIFT;
385         }
386         if(nsmod & NSControlKeyMask) {
387                 mod |= GLUT_ACTIVE_CTRL;
388         }
389         if(nsmod & NSAlternateKeyMask) {
390                 mod |= GLUT_ACTIVE_ALT;
391         }
392         return mod;
393 }
394
395 static int process_events(void)
396 {
397         NSAutoreleasePool *pool;
398         NSRunLoop *runloop;
399         NSDate *block, *nonblock, *limdate;
400
401         pool = [[NSAutoreleasePool alloc] init];
402
403         if(needs_redisplay) {
404                 needs_redisplay = 0;
405                 cb_display();
406         }
407
408         runloop = [[NSRunLoop currentRunLoop] retain];
409         block = [runloop limitDateForMode: NSDefaultRunLoopMode];
410         nonblock = [[NSDate distantPast] retain];
411         limdate = idle ? nonblock : block;
412
413         while(!quit_main_loop) {
414                 NSEvent *ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: limdate
415                         inMode: NSDefaultRunLoopMode dequeue: YES];
416                 if(!ev) break;
417
418                 [NSApp sendEvent: ev];
419                 if(limdate == block) {
420                         limdate = nonblock;
421                 }
422         }
423
424         if(cb_idle) {
425                 cb_idle();
426         }
427
428         [runloop release];
429         [pool drain];
430
431         return quit_main_loop ? -1 : 0;
432 }
433
434 static void handle_key(NSEvent *ev, int state)
435 {
436         NSPoint pt;
437         NSString *str;
438         unichar c;
439
440         str = [ev characters];
441         if(![str length]) {
442                 return;
443         }
444
445         pt = [ev locationInWindow];
446         c = [str characterAtIndex: 0];
447         if(state) {
448                 if(c < 256) {
449                         if(cb_keydown) cb_keydown(c, pt.x, pt.y);
450                 } else {
451                         if(cb_skeydown) cb_skeydown(c, pt.x, pt.y);
452                 }
453         } else {
454                 if(c < 256) {
455                         if(cb_keyup) cb_keyup(c, pt.x, pt.y);
456                 } else {
457                         if(cb_skeyup) cb_skeyup(c, pt.x, pt.y);
458                 }
459         }
460 }
461
462 static void handle_mouse(NSEvent *ev, int state)
463 {
464         int bn;
465         NSPoint pt;
466
467         if(cb_mouse) {
468                 bn = [ev buttonNumber];
469                 if(bn == 2) {
470                         bn = 1;
471                 } else if(bn == 1) {
472                         bn = 2;
473                 }
474                 pt = [ev locationInWindow];
475
476                 cb_mouse(0, bn, state, pt.x, pt.y - 1);
477         }
478 }
479
480 static void handle_motion(NSEvent *ev)
481 {
482         NSPoint pt;
483
484         if(cb_motion) {
485                 pt = [ev locationInWindow];
486                 cb_motion(0, pt.x, pt.y - 1);
487         }
488 }
489
490 static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags)
491 {
492         int i = 0;
493
494         /* this is very important. makes pixelformat selection behave like GLX
495          * where any non-zero value will denote "choose highest possible". This
496          * is pretty much what we intend, as the user doesn't actually pass any
497          * of these numbers.
498          */
499         attr[i++] = NSOpenGLPFAMaximumPolicy;
500
501         attr[i++] = NSOpenGLPFAColorSize;
502         attr[i++] = 1;
503
504         if(flags & SGL_DOUBLE) {
505                 attr[i++] = NSOpenGLPFADoubleBuffer;
506         }
507         if(flags & SGL_DEPTH) {
508                 attr[i++] = NSOpenGLPFADepthSize;
509                 attr[i++] = 1;
510         }
511         if(flags & SGL_STENCIL) {
512                 attr[i++] = NSOpenGLPFAStencilSize;
513                 attr[i++] = 8;  /* max-policy has no effect on stencil selection */
514         }
515         if(flags & SGL_STEREO) {
516                 attr[i++] = NSOpenGLPFAStereo;
517         }
518         if(flags & SGL_MULTISAMPLE) {
519                 attr[i++] = NSOpenGLPFASampleBuffers;
520                 attr[i++] = 1;
521                 attr[i++] = NSOpenGLPFASamples;
522                 attr[i++] = 4;  /* TODO don't hardcode, query */
523         }
524         attr[i++] = 0;
525 }