2 MiniGLUT - minimal GLUT subset without dependencies
3 Copyright (C) 2020-2023 John Tsiombikas <nuclear@member.fsf.org>
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.
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.
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/>.
18 #import <Cocoa/Cocoa.h>
21 @interface OpenGLView : NSOpenGLView
26 -(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf;
28 -(void) drawRect: (NSRect) rect;
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;
42 -(BOOL) acceptsFirstResponder;
46 @interface AppDelegate : NSObject
51 -(void) applicationWillFinishLaunching: (NSNotification*) notification;
52 -(void) applicationDidFinishLaunching: (NSNotification*) notification;
54 -(BOOL) applicationShouldTerminate: (NSApplication*) app;
55 -(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app;
56 -(void) applicationWillTerminate: (NSNotification*) notification;
61 @interface WinDelegate : NSObject <NSWindowDelegate>
69 -(void) windowDidExpose: (NSNotification*) notification;
70 -(void) windowDidResize: (NSNotification*) notification;
71 -(BOOL) windowShouldClose: (id) win;
72 -(void) windowWillClose: (NSNotification*) notification;
76 static int init(void);
77 static void shutdown(void);
79 /* video mode switching */
80 static int set_vidmode(int xsz, int ysz);
81 static int get_vidmode(int *xsz, int *ysz);
83 /* create/destroy windows */
84 static int create_window(int xsz, int ysz, unsigned int flags);
85 static void close_window(void);
87 /* window management */
88 static int set_title(const char *str);
89 static void redisplay(void);
90 static void swap_buffers(void);
92 static int get_modifiers(void);
94 /* event handling and friends */
95 static void set_event(int idx, int enable);
96 static int process_events(void);
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);
103 static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags);
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;
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;
124 static NSAutoreleasePool *global_pool;
127 void glutInit(int *argc, char **argv)
133 @implementation OpenGLView
135 -(id) initWithFrame: (NSRect) frame pixelFormat: (NSOpenGLPixelFormat*) pf
137 self = [super initWithFrame: frame pixelFormat: pf];
141 -(void) drawRect: (NSRect) rect
148 NSSize sz = [self bounds].size;
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);
157 -(void) keyDown: (NSEvent*) ev
162 -(void) keyUp: (NSEvent*) ev
167 -(void) mouseDown: (NSEvent*) ev
172 -(void) mouseUp: (NSEvent*) ev
177 -(void) rightMouseDown: (NSEvent*) ev
182 -(void) rightMouseUp: (NSEvent*) ev
187 -(void) otherMouseDown: (NSEvent*) ev
192 -(void) otherMouseUp: (NSEvent*) ev
197 -(void) mouseDragged: (NSEvent*) ev
202 -(void) rightMouseDragged: (NSEvent*) ev
207 -(void) otherMouseDragged: (NSEvent*) ev
213 -(BOOL) acceptsFirstResponder
219 @implementation AppDelegate
220 -(void) applicationWillFinishLaunching: (NSNotification*) notification
224 -(void) applicationDidFinishLaunching: (NSNotification*) notification
228 -(BOOL) applicationShouldTerminate: (NSApplication*) app
230 return NSTerminateNow;
233 -(BOOL) applicationShouldTerminateAfterLastWindowClosed: (NSApplication*) app
238 -(void) applicationWillTerminate: (NSNotification*) notification
240 /*[NSApp setDelegate: nil];
241 [global_pool drain];*/
245 @implementation WinDelegate
257 -(void) windowDidExpose: (NSNotification*) notification
261 -(void) windowDidResize: (NSNotification*) notification
265 -(BOOL) windowShouldClose: (id) win
271 -(void) windowWillClose: (NSNotification*) notification
273 /*[NSApp terminate: nil];*/
277 static int init(void)
279 AppDelegate *delegate;
281 global_pool = [[NSAutoreleasePool alloc] init];
283 [NSApplication sharedApplication];
285 delegate = [[AppDelegate alloc] init];
286 [NSApp setDelegate: delegate];
290 static void shutdown(void)
295 [NSApp terminate: nil];
299 /* create/destroy windows */
300 static int create_window(int xsz, int ysz, unsigned int flags)
302 NSAutoreleasePool *pool;
303 WinDelegate *delegate;
307 NSOpenGLPixelFormat *pf;
308 NSOpenGLPixelFormatAttribute attr[32];
311 pool = [[NSAutoreleasePool alloc] init];
313 /* create the view */
314 fill_attr(attr, flags);
315 pf = [[[NSOpenGLPixelFormat alloc] initWithAttributes: attr] autorelease];
316 view = [[OpenGLView alloc] initWithFrame: rect pixelFormat: pf];
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;
323 style = NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask |
324 NSResizableWindowMask;
326 nswin = [[NSWindow alloc] initWithContentRect: rect styleMask: style
327 backing: NSBackingStoreBuffered defer: YES];
329 delegate = [[WinDelegate alloc] init];
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];
341 glctx = [view openGLContext];
344 delegate->win = glwin;
346 [glctx makeCurrentContext];
351 static void close_window(void)
357 static int set_title(const char *str)
361 nsstr = [[NSString alloc] initWithCString: str encoding: NSASCIIStringEncoding];
362 [glwin setTitle: nsstr];
367 static void redisplay(void)
372 static void swap_buffers(void)
378 static int get_modifiers(void)
380 unsigned int nsmod = [NSEvent modifierFlags];
381 unsigned int mod = 0;
383 if(nsmod & NSShiftKeyMask) {
384 mod |= GLUT_ACTIVE_SHIFT;
386 if(nsmod & NSControlKeyMask) {
387 mod |= GLUT_ACTIVE_CTRL;
389 if(nsmod & NSAlternateKeyMask) {
390 mod |= GLUT_ACTIVE_ALT;
395 static int process_events(void)
397 NSAutoreleasePool *pool;
399 NSDate *block, *nonblock, *limdate;
401 pool = [[NSAutoreleasePool alloc] init];
403 if(needs_redisplay) {
408 runloop = [[NSRunLoop currentRunLoop] retain];
409 block = [runloop limitDateForMode: NSDefaultRunLoopMode];
410 nonblock = [[NSDate distantPast] retain];
411 limdate = idle ? nonblock : block;
413 while(!quit_main_loop) {
414 NSEvent *ev = [NSApp nextEventMatchingMask: NSAnyEventMask untilDate: limdate
415 inMode: NSDefaultRunLoopMode dequeue: YES];
418 [NSApp sendEvent: ev];
419 if(limdate == block) {
431 return quit_main_loop ? -1 : 0;
434 static void handle_key(NSEvent *ev, int state)
440 str = [ev characters];
445 pt = [ev locationInWindow];
446 c = [str characterAtIndex: 0];
449 if(cb_keydown) cb_keydown(c, pt.x, pt.y);
451 if(cb_skeydown) cb_skeydown(c, pt.x, pt.y);
455 if(cb_keyup) cb_keyup(c, pt.x, pt.y);
457 if(cb_skeyup) cb_skeyup(c, pt.x, pt.y);
462 static void handle_mouse(NSEvent *ev, int state)
468 bn = [ev buttonNumber];
474 pt = [ev locationInWindow];
476 cb_mouse(0, bn, state, pt.x, pt.y - 1);
480 static void handle_motion(NSEvent *ev)
485 pt = [ev locationInWindow];
486 cb_motion(0, pt.x, pt.y - 1);
490 static void fill_attr(NSOpenGLPixelFormatAttribute *attr, unsigned int flags)
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
499 attr[i++] = NSOpenGLPFAMaximumPolicy;
501 attr[i++] = NSOpenGLPFAColorSize;
504 if(flags & SGL_DOUBLE) {
505 attr[i++] = NSOpenGLPFADoubleBuffer;
507 if(flags & SGL_DEPTH) {
508 attr[i++] = NSOpenGLPFADepthSize;
511 if(flags & SGL_STENCIL) {
512 attr[i++] = NSOpenGLPFAStencilSize;
513 attr[i++] = 8; /* max-policy has no effect on stencil selection */
515 if(flags & SGL_STEREO) {
516 attr[i++] = NSOpenGLPFAStereo;
518 if(flags & SGL_MULTISAMPLE) {
519 attr[i++] = NSOpenGLPFASampleBuffers;
521 attr[i++] = NSOpenGLPFASamples;
522 attr[i++] = 4; /* TODO don't hardcode, query */