1 /* Clutter - An OpenGL based 'interactive canvas' library.
2 * OSX backend - integration with NSWindow and NSView
4 * Copyright (C) 2007-2008 Tommi Komulainen <tommi.komulainen@iki.fi>
5 * Copyright (C) 2007 OpenedHand Ltd.
6 * Copyright (C) 2011 Crystalnix <vgachkaylo@gmail.com>
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
25 #import "clutter-osx.h"
26 #import "clutter-stage-osx.h"
27 #import "clutter-backend-osx.h"
29 #include "clutter-debug.h"
30 #include "clutter-private.h"
31 #include "clutter-stage-private.h"
41 static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
43 #define clutter_stage_osx_get_type _clutter_stage_osx_get_type
45 G_DEFINE_TYPE_WITH_CODE (ClutterStageOSX,
48 G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
49 clutter_stage_window_iface_init))
52 clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window);
54 #define CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL (NSMainMenuWindowLevel + 1)
56 /*************************************************************************/
57 @implementation ClutterGLWindow
58 - (id)initWithView:(NSView *)aView UTF8Title:(const char *)aTitle stage:(ClutterStageOSX *)aStage
60 if ((self = [super initWithContentRect: [aView frame]
61 styleMask: NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask
62 backing: NSBackingStoreBuffered
65 [self setDelegate: self];
66 [self useOptimizedDrawing: YES];
67 [self setAcceptsMouseMovedEvents:YES];
68 [self setContentView: aView];
69 [self setTitle:[NSString stringWithUTF8String: aTitle ? aTitle : ""]];
70 self->stage_osx = aStage;
75 - (BOOL) windowShouldClose: (id) sender
79 CLUTTER_NOTE (BACKEND, "[%p] windowShouldClose", self->stage_osx);
81 event.type = CLUTTER_DELETE;
82 event.any.stage = self->stage_osx->wrapper;
83 clutter_event_put (&event);
88 - (NSRect) constrainFrameRect:(NSRect)frameRect toScreen:(NSScreen*)aScreen
90 /* in fullscreen mode we don't want to be constrained by menubar or dock
91 * FIXME: calculate proper constraints depending on fullscreen mode
97 - (void) windowDidBecomeKey:(NSNotification*)aNotification
101 CLUTTER_NOTE (BACKEND, "[%p] windowDidBecomeKey", self->stage_osx);
103 stage = self->stage_osx->wrapper;
105 if (_clutter_stage_is_fullscreen (stage))
106 [self setLevel: CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL];
108 _clutter_stage_update_state (stage, 0, CLUTTER_STAGE_STATE_ACTIVATED);
111 - (void) windowDidResignKey:(NSNotification*)aNotification
115 CLUTTER_NOTE (BACKEND, "[%p] windowDidResignKey", self->stage_osx);
117 stage = self->stage_osx->wrapper;
119 if (_clutter_stage_is_fullscreen (stage))
121 [self setLevel: NSNormalWindowLevel];
122 if (!self->stage_osx->isHiding)
123 [self orderBack: nil];
126 _clutter_stage_update_state (stage, CLUTTER_STAGE_STATE_ACTIVATED, 0);
129 - (NSSize) windowWillResize:(NSWindow *) sender toSize:(NSSize) frameSize
131 if (clutter_stage_get_user_resizable (self->stage_osx->wrapper))
133 guint min_width, min_height;
134 clutter_stage_get_minimum_size (self->stage_osx->wrapper,
137 [self setContentMinSize:NSMakeSize(min_width, min_height)];
141 return [self frame].size;
144 - (void)windowDidChangeScreen:(NSNotification *)notification
146 clutter_stage_ensure_redraw (self->stage_osx->wrapper);
150 /*************************************************************************/
151 @interface ClutterGLView : NSOpenGLView
153 ClutterStageOSX *stage_osx;
155 NSTrackingRectTag trackingRect;
157 - (void) drawRect: (NSRect) bounds;
160 @implementation ClutterGLView
161 - (id) initWithFrame: (NSRect)aFrame pixelFormat:(NSOpenGLPixelFormat*)aFormat stage:(ClutterStageOSX*)aStage
163 if ((self = [super initWithFrame:aFrame pixelFormat:aFormat]) != nil)
165 self->stage_osx = aStage;
166 trackingRect = [self addTrackingRect:[self bounds]
179 [self removeTrackingRect:trackingRect];
186 - (NSTrackingRectTag) trackingRect
191 - (ClutterActor *) clutterStage
193 return (ClutterActor *) stage_osx->wrapper;
196 - (void) drawRect: (NSRect) bounds
198 ClutterActor *stage = [self clutterStage];
200 _clutter_stage_do_paint (CLUTTER_STAGE (stage), NULL);
204 [[self openGLContext] flushBuffer];
207 /* In order to receive key events */
208 - (BOOL) acceptsFirstResponder
213 /* We want 0,0 top left */
221 ClutterActor *stage = [self clutterStage];
223 if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
226 if (clutter_stage_get_use_alpha (CLUTTER_STAGE (stage)))
236 stage_osx->requisition_width = [self bounds].size.width;
237 stage_osx->requisition_height = [self bounds].size.height;
239 stage = [self clutterStage];
240 clutter_actor_set_size (stage,
241 stage_osx->requisition_width,
242 stage_osx->requisition_height);
244 [self removeTrackingRect:trackingRect];
245 trackingRect = [self addTrackingRect:[self bounds]
251 /* Simply forward all events that reach our view to clutter. */
253 #define EVENT_HANDLER(event) \
254 -(void)event:(NSEvent *) theEvent { \
255 _clutter_event_osx_put (theEvent, stage_osx->wrapper); \
258 EVENT_HANDLER(mouseDown)
259 EVENT_HANDLER(mouseDragged)
260 EVENT_HANDLER(mouseUp)
261 EVENT_HANDLER(mouseMoved)
262 EVENT_HANDLER(mouseEntered)
263 EVENT_HANDLER(mouseExited)
264 EVENT_HANDLER(rightMouseDown)
265 EVENT_HANDLER(rightMouseDragged)
266 EVENT_HANDLER(rightMouseUp)
267 EVENT_HANDLER(otherMouseDown)
268 EVENT_HANDLER(otherMouseDragged)
269 EVENT_HANDLER(otherMouseUp)
270 EVENT_HANDLER(scrollWheel)
271 EVENT_HANDLER(keyDown)
273 EVENT_HANDLER(flagsChanged)
274 EVENT_HANDLER(helpRequested)
275 EVENT_HANDLER(tabletPoint)
276 EVENT_HANDLER(tabletProximity)
281 /*************************************************************************/
284 clutter_stage_osx_save_frame (ClutterStageOSX *self)
286 g_assert (self->window != NULL);
288 self->normalFrame = [self->window frame];
289 self->haveNormalFrame = TRUE;
293 clutter_stage_osx_set_frame (ClutterStageOSX *self)
295 g_assert (self->window != NULL);
297 if (_clutter_stage_is_fullscreen (self->wrapper))
299 /* Raise above the menubar (and dock) covering the whole screen.
301 * NOTE: This effectively breaks Option-Tabbing as our window covers
302 * all other applications completely. However we deal with the situation
303 * by lowering the window to the bottom of the normal level stack on
304 * windowDidResignKey notification.
306 [self->window setLevel: CLUTTER_OSX_FULLSCREEN_WINDOW_LEVEL];
308 [self->window setFrame: [self->window frameRectForContentRect: [[self->window screen] frame]]
313 [self->window setLevel: NSNormalWindowLevel];
315 if (self->haveNormalFrame)
316 [self->window setFrame: self->normalFrame display: NO];
319 /* looks better than positioning to 0,0 (bottom right) */
320 [self->window center];
325 /*************************************************************************/
327 clutter_stage_osx_realize (ClutterStageWindow *stage_window)
329 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
330 gfloat width, height;
333 CLUTTER_OSX_POOL_ALLOC();
335 CLUTTER_NOTE (BACKEND, "[%p] realize", self);
337 if (!self->haveRealized)
339 ClutterBackendOSX *backend_osx;
341 backend_osx = CLUTTER_BACKEND_OSX (self->backend);
343 /* Call get_size - this will either get the geometry size (which
344 * before we create the window is set to 640x480), or if a size
345 * is set, it will get that. This lets you set a size on the
346 * stage before it's realized.
348 clutter_actor_get_size (CLUTTER_ACTOR (self->wrapper), &width, &height);
349 self->requisition_width = width;
350 self->requisition_height= height;
352 rect = NSMakeRect (0, 0, self->requisition_width, self->requisition_height);
354 self->view = [[ClutterGLView alloc]
356 pixelFormat: backend_osx->pixel_format
358 [self->view setOpenGLContext:backend_osx->context];
360 self->window = [[ClutterGLWindow alloc]
361 initWithView: self->view
362 UTF8Title: clutter_stage_get_title (CLUTTER_STAGE (self->wrapper))
364 /* looks better than positioning to 0,0 (bottom right) */
365 [self->window center];
366 self->haveRealized = true;
368 CLUTTER_NOTE (BACKEND, "Stage successfully realized");
371 CLUTTER_OSX_POOL_RELEASE();
377 clutter_stage_osx_unrealize (ClutterStageWindow *stage_window)
379 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
381 CLUTTER_OSX_POOL_ALLOC();
383 CLUTTER_NOTE (BACKEND, "[%p] unrealize", self);
385 /* ensure we get realize+unrealize properly paired */
386 g_return_if_fail (self->view != NULL && self->window != NULL);
388 [self->view release];
389 [self->window close];
393 self->haveRealized = false;
395 CLUTTER_OSX_POOL_RELEASE();
399 clutter_stage_osx_show (ClutterStageWindow *stage_window,
402 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
406 CLUTTER_OSX_POOL_ALLOC();
408 CLUTTER_NOTE (BACKEND, "[%p] show", self);
410 clutter_stage_osx_realize (stage_window);
411 clutter_actor_map (CLUTTER_ACTOR (self->wrapper));
413 clutter_stage_osx_set_frame (self);
415 /* Draw view should be avoided and it is the reason why
416 * we should hide OpenGL view while we showing the stage.
418 isViewHidden = [self->view isHidden];
419 if (isViewHidden == NO)
420 [self->view setHidden:YES];
422 if (self->acceptFocus)
423 [self->window makeKeyAndOrderFront: nil];
425 [self->window orderFront: nil];
427 /* If the window is placed directly under the mouse pointer, Quartz will
428 * not send a NSMouseEntered event; we can easily synthesize one ourselves
431 nspoint = [self->window mouseLocationOutsideOfEventStream];
432 if ([self->view mouse:nspoint inRect:[self->view frame]])
436 event = [NSEvent enterExitEventWithType: NSMouseEntered
437 location: NSMakePoint(0, 0)
440 windowNumber: [self->window windowNumber]
443 trackingNumber: [self->view trackingRect]
446 [NSApp postEvent:event atStart:NO];
449 [self->view setHidden:isViewHidden];
450 [self->window setExcludedFromWindowsMenu:NO];
453 * After hiding we cease to be first responder.
455 [self->window makeFirstResponder: self->view];
457 CLUTTER_OSX_POOL_RELEASE();
461 clutter_stage_osx_hide (ClutterStageWindow *stage_window)
463 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
465 CLUTTER_OSX_POOL_ALLOC();
467 CLUTTER_NOTE (BACKEND, "[%p] hide", self);
469 self->isHiding = true;
470 [self->window orderOut: nil];
471 [self->window setExcludedFromWindowsMenu:YES];
473 clutter_actor_unmap (CLUTTER_ACTOR (self->wrapper));
475 self->isHiding = false;
477 CLUTTER_OSX_POOL_RELEASE();
481 clutter_stage_osx_get_geometry (ClutterStageWindow *stage_window,
482 cairo_rectangle_int_t *geometry)
484 ClutterBackend *backend = clutter_get_default_backend ();
485 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
487 g_return_if_fail (CLUTTER_IS_BACKEND_OSX (backend));
489 geometry->width = self->requisition_width;
490 geometry->height = self->requisition_height;
494 clutter_stage_osx_resize (ClutterStageWindow *stage_window,
498 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
499 ClutterActor *actor = clutter_stage_osx_get_wrapper (stage_window);
500 guint min_width, min_height;
503 CLUTTER_OSX_POOL_ALLOC ();
505 clutter_stage_get_minimum_size (CLUTTER_STAGE (actor),
509 [self->window setContentMinSize: NSMakeSize (min_width, min_height)];
511 width = width < min_width ? min_width : width;
512 height = height < min_height ? min_height : height;
514 self->requisition_width = width;
515 self->requisition_height = height;
517 size = NSMakeSize (self->requisition_width, self->requisition_height);
518 [self->window setContentSize: size];
520 CLUTTER_OSX_POOL_RELEASE ();
523 /*************************************************************************/
524 static ClutterActor *
525 clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window)
527 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
529 return CLUTTER_ACTOR (self->wrapper);
533 clutter_stage_osx_set_title (ClutterStageWindow *stage_window,
536 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
538 CLUTTER_OSX_POOL_ALLOC();
540 CLUTTER_NOTE (BACKEND, "[%p] set_title: %s", self, title);
542 [self->window setTitle:[NSString stringWithUTF8String: title ? title : ""]];
544 CLUTTER_OSX_POOL_RELEASE();
548 clutter_stage_osx_set_fullscreen (ClutterStageWindow *stage_window,
551 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
553 CLUTTER_OSX_POOL_ALLOC();
555 CLUTTER_NOTE (BACKEND, "[%p] set_fullscreen: %u", self, fullscreen);
557 /* Make sure to update the state before clutter_stage_osx_set_frame.
559 * Toggling fullscreen isn't atomic, there's two "events" involved:
560 * - stage state change (via state_update)
561 * - stage size change (via set_frame -> setFrameSize / set_size)
563 * We do state change first. Not sure there's any difference.
567 _clutter_stage_update_state (CLUTTER_STAGE (self->wrapper),
569 CLUTTER_STAGE_STATE_FULLSCREEN);
570 clutter_stage_osx_save_frame (self);
574 _clutter_stage_update_state (CLUTTER_STAGE (self->wrapper),
575 CLUTTER_STAGE_STATE_FULLSCREEN,
579 clutter_stage_osx_set_frame (self);
581 CLUTTER_OSX_POOL_RELEASE();
584 clutter_stage_osx_set_cursor_visible (ClutterStageWindow *stage_window,
585 gboolean cursor_visible)
587 CLUTTER_OSX_POOL_ALLOC();
594 CLUTTER_OSX_POOL_RELEASE();
598 clutter_stage_osx_set_user_resizable (ClutterStageWindow *stage_window,
599 gboolean is_resizable)
601 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
603 CLUTTER_OSX_POOL_ALLOC();
605 [self->window setShowsResizeIndicator:is_resizable];
607 CLUTTER_OSX_POOL_RELEASE();
611 clutter_stage_osx_set_accept_focus (ClutterStageWindow *stage_window,
612 gboolean accept_focus)
614 ClutterStageOSX *self = CLUTTER_STAGE_OSX (stage_window);
616 CLUTTER_OSX_POOL_ALLOC();
618 self->acceptFocus = !!accept_focus;
620 CLUTTER_OSX_POOL_RELEASE();
624 clutter_stage_osx_redraw (ClutterStageWindow *stage_window)
626 ClutterStageOSX *stage_osx = CLUTTER_STAGE_OSX (stage_window);
628 CLUTTER_OSX_POOL_ALLOC();
630 if (stage_osx->view != NULL)
631 [stage_osx->view setNeedsDisplay: YES];
633 CLUTTER_OSX_POOL_RELEASE();
637 clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
639 iface->get_wrapper = clutter_stage_osx_get_wrapper;
640 iface->set_title = clutter_stage_osx_set_title;
641 iface->set_fullscreen = clutter_stage_osx_set_fullscreen;
642 iface->show = clutter_stage_osx_show;
643 iface->hide = clutter_stage_osx_hide;
644 iface->realize = clutter_stage_osx_realize;
645 iface->unrealize = clutter_stage_osx_unrealize;
646 iface->get_geometry = clutter_stage_osx_get_geometry;
647 iface->resize = clutter_stage_osx_resize;
648 iface->set_cursor_visible = clutter_stage_osx_set_cursor_visible;
649 iface->set_user_resizable = clutter_stage_osx_set_user_resizable;
650 iface->set_accept_focus = clutter_stage_osx_set_accept_focus;
651 iface->redraw = clutter_stage_osx_redraw;
654 /*************************************************************************/
656 clutter_stage_osx_init (ClutterStageOSX *self)
658 self->requisition_width = 640;
659 self->requisition_height = 480;
660 self->acceptFocus = TRUE;
662 self->isHiding = false;
663 self->haveRealized = false;
669 clutter_stage_osx_set_property (GObject *gobject,
674 ClutterStageOSX *self = CLUTTER_STAGE_OSX (gobject);
679 self->backend = g_value_get_object (value);
683 self->wrapper = g_value_get_object (value);
687 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
693 clutter_stage_osx_finalize (GObject *gobject)
695 G_OBJECT_CLASS (clutter_stage_osx_parent_class)->finalize (gobject);
699 clutter_stage_osx_dispose (GObject *gobject)
701 G_OBJECT_CLASS (clutter_stage_osx_parent_class)->dispose (gobject);
705 clutter_stage_osx_class_init (ClutterStageOSXClass *klass)
707 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
709 gobject_class->set_property = clutter_stage_osx_set_property;
710 gobject_class->finalize = clutter_stage_osx_finalize;
711 gobject_class->dispose = clutter_stage_osx_dispose;
713 g_object_class_override_property (gobject_class, PROP_BACKEND, "backend");
714 g_object_class_override_property (gobject_class, PROP_WRAPPER, "wrapper");