1 /*
2 * Clutter.
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Authored By Matthew Allum <mallum@openedhand.com>
7 *
8 * Copyright (C) 2006 OpenedHand
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
14 *
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19 *
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
22 */
23
24 /**
25 * SECTION:clutter-stage
26 * @short_description: Top level visual element to which actors are placed.
27 *
28 * #ClutterStage is a top level 'window' on which child actors are placed
29 * and manipulated.
30 *
31 * Backends might provide support for multiple stages. The support for this
32 * feature can be checked at run-time using the clutter_feature_available()
33 * function and the %CLUTTER_FEATURE_STAGE_MULTIPLE flag. If the backend used
34 * supports multiple stages, new #ClutterStage instances can be created
35 * using clutter_stage_new(). These stages must be managed by the developer
36 * using clutter_actor_destroy(), which will take care of destroying all the
37 * actors contained inside them.
38 *
39 * #ClutterStage is a proxy actor, wrapping the backend-specific implementation
40 * (a #StageWindow) of the windowing system. It is possible to subclass
41 * #ClutterStage, as long as every overridden virtual function chains up to the
42 * parent class corresponding function.
43 */
44
45 #include "clutter-build-config.h"
46
47 #include <math.h>
48 #include <cairo-gobject.h>
49
50 #define CLUTTER_DISABLE_DEPRECATION_WARNINGS
51
52 #include "clutter-stage.h"
53 #include "deprecated/clutter-container.h"
54
55 #include "clutter-actor-private.h"
56 #include "clutter-backend-private.h"
57 #include "clutter-cairo.h"
58 #include "clutter-container.h"
59 #include "clutter-debug.h"
60 #include "clutter-enum-types.h"
61 #include "clutter-event-private.h"
62 #include "clutter-frame-clock.h"
63 #include "clutter-id-pool.h"
64 #include "clutter-input-device-private.h"
65 #include "clutter-main.h"
66 #include "clutter-marshal.h"
67 #include "clutter-mutter.h"
68 #include "clutter-paint-context-private.h"
69 #include "clutter-paint-volume-private.h"
70 #include "clutter-pick-context-private.h"
71 #include "clutter-private.h"
72 #include "clutter-stage-manager-private.h"
73 #include "clutter-stage-private.h"
74 #include "clutter-stage-view-private.h"
75 #include "clutter-private.h"
76
77 #include "cogl/cogl.h"
78
79 #define MAX_FRUSTA 64
80
81 typedef struct _QueueRedrawEntry
82 {
83 gboolean has_clip;
84 ClutterPaintVolume clip;
85 } QueueRedrawEntry;
86
87 typedef struct _PickRecord
88 {
89 graphene_point_t vertex[4];
90 ClutterActor *actor;
91 int clip_stack_top;
92 } PickRecord;
93
94 typedef struct _PickClipRecord
95 {
96 int prev;
97 graphene_point_t vertex[4];
98 } PickClipRecord;
99
100 typedef struct _PointerDeviceEntry
101 {
102 ClutterStage *stage;
103 ClutterInputDevice *device;
104 ClutterEventSequence *sequence;
105 graphene_point_t coords;
106 ClutterActor *current_actor;
107 } PointerDeviceEntry;
108
109 struct _ClutterStagePrivate
110 {
111 /* the stage implementation */
112 ClutterStageWindow *impl;
113
114 ClutterPerspective perspective;
115 graphene_matrix_t projection;
116 graphene_matrix_t inverse_projection;
117 graphene_matrix_t view;
118 float viewport[4];
119
120 gchar *title;
121 ClutterActor *key_focused_actor;
122
123 GQueue *event_queue;
124
125 GArray *paint_volume_stack;
126
127 GSList *pending_relayouts;
128 GHashTable *pending_queue_redraws;
129
130 int update_freeze_count;
131
132 gboolean needs_update_devices;
133 gboolean pending_finish_queue_redraws;
134
135 GHashTable *pointer_devices;
136 GHashTable *touch_sequences;
137
138 guint throttle_motion_events : 1;
139 guint min_size_changed : 1;
140 guint motion_events_enabled : 1;
141 guint actor_needs_immediate_relayout : 1;
142 };
143
144 enum
145 {
146 PROP_0,
147
148 PROP_PERSPECTIVE,
149 PROP_TITLE,
150 PROP_KEY_FOCUS,
151 PROP_LAST
152 };
153
154 static GParamSpec *obj_props[PROP_LAST] = { NULL, };
155
156 enum
157 {
158 ACTIVATE,
159 DEACTIVATE,
160 DELETE_EVENT,
161 BEFORE_UPDATE,
162 BEFORE_PAINT,
163 AFTER_PAINT,
164 AFTER_UPDATE,
165 PAINT_VIEW,
166 PRESENTED,
167 GL_VIDEO_MEMORY_PURGED,
168
169 LAST_SIGNAL
170 };
171
172 static guint stage_signals[LAST_SIGNAL] = { 0, };
173
174 static const ClutterColor default_stage_color = { 255, 255, 255, 255 };
175
176 static void free_queue_redraw_entry (QueueRedrawEntry *entry);
177 static void free_pointer_device_entry (PointerDeviceEntry *entry);
178 static void clutter_stage_update_view_perspective (ClutterStage *stage);
179 static void clutter_stage_set_viewport (ClutterStage *stage,
180 float width,
181 float height);
182
G_DEFINE_TYPE_WITH_PRIVATE(ClutterStage,clutter_stage,CLUTTER_TYPE_ACTOR)183 G_DEFINE_TYPE_WITH_PRIVATE (ClutterStage, clutter_stage, CLUTTER_TYPE_ACTOR)
184
185 static void
186 clutter_stage_get_preferred_width (ClutterActor *self,
187 gfloat for_height,
188 gfloat *min_width_p,
189 gfloat *natural_width_p)
190 {
191 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
192 cairo_rectangle_int_t geom;
193
194 if (priv->impl == NULL)
195 return;
196
197 _clutter_stage_window_get_geometry (priv->impl, &geom);
198
199 if (min_width_p)
200 *min_width_p = geom.width;
201
202 if (natural_width_p)
203 *natural_width_p = geom.width;
204 }
205
206 static void
clutter_stage_get_preferred_height(ClutterActor * self,gfloat for_width,gfloat * min_height_p,gfloat * natural_height_p)207 clutter_stage_get_preferred_height (ClutterActor *self,
208 gfloat for_width,
209 gfloat *min_height_p,
210 gfloat *natural_height_p)
211 {
212 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
213 cairo_rectangle_int_t geom;
214
215 if (priv->impl == NULL)
216 return;
217
218 _clutter_stage_window_get_geometry (priv->impl, &geom);
219
220 if (min_height_p)
221 *min_height_p = geom.height;
222
223 if (natural_height_p)
224 *natural_height_p = geom.height;
225 }
226
227 static void
clutter_stage_add_redraw_clip(ClutterStage * stage,cairo_rectangle_int_t * clip)228 clutter_stage_add_redraw_clip (ClutterStage *stage,
229 cairo_rectangle_int_t *clip)
230 {
231 GList *l;
232
233 for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
234 {
235 ClutterStageView *view = l->data;
236
237 if (!clip)
238 {
239 clutter_stage_view_add_redraw_clip (view, NULL);
240 }
241 else
242 {
243 cairo_rectangle_int_t view_layout;
244 cairo_rectangle_int_t intersection;
245
246 clutter_stage_view_get_layout (view, &view_layout);
247 if (_clutter_util_rectangle_intersection (&view_layout, clip,
248 &intersection))
249 clutter_stage_view_add_redraw_clip (view, &intersection);
250 }
251 }
252 }
253
254 static inline void
queue_full_redraw(ClutterStage * stage)255 queue_full_redraw (ClutterStage *stage)
256 {
257 ClutterStageWindow *stage_window;
258
259 if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
260 return;
261
262 clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
263
264 /* Just calling clutter_actor_queue_redraw will typically only
265 * redraw the bounding box of the children parented on the stage but
266 * in this case we really need to ensure that the full stage is
267 * redrawn so we add a NULL redraw clip to the stage window. */
268 stage_window = _clutter_stage_get_window (stage);
269 if (stage_window == NULL)
270 return;
271
272 clutter_stage_add_redraw_clip (stage, NULL);
273 }
274
275 static void
clutter_stage_allocate(ClutterActor * self,const ClutterActorBox * box)276 clutter_stage_allocate (ClutterActor *self,
277 const ClutterActorBox *box)
278 {
279 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
280 ClutterActorBox alloc = CLUTTER_ACTOR_BOX_INIT_ZERO;
281 float new_width, new_height;
282 float width, height;
283 cairo_rectangle_int_t window_size;
284 ClutterLayoutManager *layout_manager = clutter_actor_get_layout_manager (self);
285
286 if (priv->impl == NULL)
287 return;
288
289 /* the current allocation */
290 clutter_actor_box_get_size (box, &width, &height);
291
292 /* the current Stage implementation size */
293 _clutter_stage_window_get_geometry (priv->impl, &window_size);
294
295 /* if the stage is fixed size (for instance, it's using a EGL framebuffer)
296 * then we simply ignore any allocation request and override the
297 * allocation chain - because we cannot forcibly change the size of the
298 * stage window.
299 */
300 if (!clutter_feature_available (CLUTTER_FEATURE_STAGE_STATIC))
301 {
302 ClutterActorBox children_box;
303
304 children_box.x1 = children_box.y1 = 0.f;
305 children_box.x2 = box->x2 - box->x1;
306 children_box.y2 = box->y2 - box->y1;
307
308 CLUTTER_NOTE (LAYOUT,
309 "Following allocation to %.2fx%.2f",
310 width, height);
311
312 clutter_actor_set_allocation (self, box);
313
314 clutter_layout_manager_allocate (layout_manager,
315 CLUTTER_CONTAINER (self),
316 &children_box);
317
318 /* Ensure the window is sized correctly */
319 if (priv->min_size_changed)
320 {
321 gfloat min_width, min_height;
322 gboolean min_width_set, min_height_set;
323
324 g_object_get (G_OBJECT (self),
325 "min-width", &min_width,
326 "min-width-set", &min_width_set,
327 "min-height", &min_height,
328 "min-height-set", &min_height_set,
329 NULL);
330
331 if (!min_width_set)
332 min_width = 1;
333 if (!min_height_set)
334 min_height = 1;
335
336 if (width < min_width)
337 width = min_width;
338 if (height < min_height)
339 height = min_height;
340
341 priv->min_size_changed = FALSE;
342 }
343
344 if (window_size.width != CLUTTER_NEARBYINT (width) ||
345 window_size.height != CLUTTER_NEARBYINT (height))
346 {
347 _clutter_stage_window_resize (priv->impl,
348 CLUTTER_NEARBYINT (width),
349 CLUTTER_NEARBYINT (height));
350 }
351 }
352 else
353 {
354 ClutterActorBox override = { 0, };
355
356 /* override the passed allocation */
357 override.x1 = 0;
358 override.y1 = 0;
359 override.x2 = window_size.width;
360 override.y2 = window_size.height;
361
362 CLUTTER_NOTE (LAYOUT,
363 "Overriding original allocation of %.2fx%.2f "
364 "with %.2fx%.2f",
365 width, height,
366 override.x2, override.y2);
367
368 /* and store the overridden allocation */
369 clutter_actor_set_allocation (self, &override);
370
371 clutter_layout_manager_allocate (layout_manager,
372 CLUTTER_CONTAINER (self),
373 &override);
374 }
375
376 /* set the viewport to the new allocation */
377 clutter_actor_get_allocation_box (self, &alloc);
378 clutter_actor_box_get_size (&alloc, &new_width, &new_height);
379
380 clutter_stage_set_viewport (CLUTTER_STAGE (self), new_width, new_height);
381 }
382
383 static void
setup_clip_frustum(ClutterStage * stage,const cairo_rectangle_int_t * clip,graphene_frustum_t * frustum)384 setup_clip_frustum (ClutterStage *stage,
385 const cairo_rectangle_int_t *clip,
386 graphene_frustum_t *frustum)
387 {
388 ClutterStagePrivate *priv = stage->priv;
389 cairo_rectangle_int_t geom;
390 graphene_point3d_t camera_position;
391 graphene_point3d_t p[4];
392 graphene_plane_t planes[6];
393 graphene_vec4_t v;
394 int i;
395
396 _clutter_stage_window_get_geometry (priv->impl, &geom);
397
398 CLUTTER_NOTE (CLIPPING, "Creating stage clip frustum for "
399 "x=%d, y=%d, width=%d, height=%d",
400 clip->x, clip->y, clip->width, clip->height);
401
402 camera_position = GRAPHENE_POINT3D_INIT_ZERO;
403
404 p[0] = GRAPHENE_POINT3D_INIT (MAX (clip->x, 0), MAX (clip->y, 0), 0.f);
405 p[2] = GRAPHENE_POINT3D_INIT (MIN (clip->x + clip->width, geom.width),
406 MIN (clip->y + clip->height, geom.height),
407 0.f);
408
409 for (i = 0; i < 2; i++)
410 {
411 float w = 1.0;
412 cogl_graphene_matrix_project_point (&priv->view,
413 &p[2 * i].x,
414 &p[2 * i].y,
415 &p[2 * i].z,
416 &w);
417 }
418
419 graphene_point3d_init (&p[1], p[2].x, p[0].y, p[0].z);
420 graphene_point3d_init (&p[3], p[0].x, p[2].y, p[0].z);
421
422 for (i = 0; i < 4; i++)
423 {
424 graphene_plane_init_from_points (&planes[i],
425 &camera_position,
426 &p[i],
427 &p[(i + 1) % 4]);
428 }
429
430 graphene_vec4_init (&v, 0.f, 0.f, -1.f, priv->perspective.z_near);
431 graphene_plane_init_from_vec4 (&planes[4], &v);
432
433 graphene_vec4_init (&v, 0.f, 0.f, 1.f, priv->perspective.z_far);
434 graphene_plane_init_from_vec4 (&planes[5], &v);
435
436 graphene_frustum_init (frustum,
437 &planes[0], &planes[1],
438 &planes[2], &planes[3],
439 &planes[4], &planes[5]);
440 }
441
442 static void
clutter_stage_do_paint_view(ClutterStage * stage,ClutterStageView * view,const cairo_region_t * redraw_clip)443 clutter_stage_do_paint_view (ClutterStage *stage,
444 ClutterStageView *view,
445 const cairo_region_t *redraw_clip)
446 {
447 ClutterPaintContext *paint_context;
448 cairo_rectangle_int_t clip_rect;
449 g_autoptr (GArray) clip_frusta = NULL;
450 graphene_frustum_t clip_frustum;
451 int n_rectangles;
452
453 n_rectangles = redraw_clip ? cairo_region_num_rectangles (redraw_clip) : 0;
454 if (redraw_clip && n_rectangles < MAX_FRUSTA)
455 {
456 int i;
457
458 clip_frusta = g_array_sized_new (FALSE, FALSE,
459 sizeof (graphene_frustum_t),
460 n_rectangles);
461
462 for (i = 0; i < n_rectangles; i++)
463 {
464 cairo_region_get_rectangle (redraw_clip, i, &clip_rect);
465 setup_clip_frustum (stage, &clip_rect, &clip_frustum);
466 g_array_append_val (clip_frusta, clip_frustum);
467 }
468 }
469 else
470 {
471 clip_frusta = g_array_sized_new (FALSE, FALSE,
472 sizeof (graphene_frustum_t),
473 1);
474 if (redraw_clip)
475 cairo_region_get_extents (redraw_clip, &clip_rect);
476 else
477 clutter_stage_view_get_layout (view, &clip_rect);
478
479 setup_clip_frustum (stage, &clip_rect, &clip_frustum);
480 g_array_append_val (clip_frusta, clip_frustum);
481 }
482
483 _clutter_stage_paint_volume_stack_free_all (stage);
484
485 paint_context = clutter_paint_context_new_for_view (view,
486 redraw_clip,
487 clip_frusta,
488 CLUTTER_PAINT_FLAG_NONE);
489
490 clutter_actor_paint (CLUTTER_ACTOR (stage), paint_context);
491 clutter_paint_context_destroy (paint_context);
492 }
493
494 /* This provides a common point of entry for painting the scenegraph
495 * for picking or painting...
496 */
497 void
clutter_stage_paint_view(ClutterStage * stage,ClutterStageView * view,const cairo_region_t * redraw_clip)498 clutter_stage_paint_view (ClutterStage *stage,
499 ClutterStageView *view,
500 const cairo_region_t *redraw_clip)
501 {
502 ClutterStagePrivate *priv = stage->priv;
503
504 if (!priv->impl)
505 return;
506
507 COGL_TRACE_BEGIN_SCOPED (ClutterStagePaintView, "Paint (view)");
508
509 if (g_signal_has_handler_pending (stage, stage_signals[PAINT_VIEW],
510 0, TRUE))
511 g_signal_emit (stage, stage_signals[PAINT_VIEW], 0, view, redraw_clip);
512 else
513 CLUTTER_STAGE_GET_CLASS (stage)->paint_view (stage, view, redraw_clip);
514 }
515
516 void
clutter_stage_emit_before_update(ClutterStage * stage,ClutterStageView * view)517 clutter_stage_emit_before_update (ClutterStage *stage,
518 ClutterStageView *view)
519 {
520 g_signal_emit (stage, stage_signals[BEFORE_UPDATE], 0, view);
521 }
522
523 void
clutter_stage_emit_before_paint(ClutterStage * stage,ClutterStageView * view)524 clutter_stage_emit_before_paint (ClutterStage *stage,
525 ClutterStageView *view)
526 {
527 g_signal_emit (stage, stage_signals[BEFORE_PAINT], 0, view);
528 }
529
530 void
clutter_stage_emit_after_paint(ClutterStage * stage,ClutterStageView * view)531 clutter_stage_emit_after_paint (ClutterStage *stage,
532 ClutterStageView *view)
533 {
534 g_signal_emit (stage, stage_signals[AFTER_PAINT], 0, view);
535 }
536
537 void
clutter_stage_emit_after_update(ClutterStage * stage,ClutterStageView * view)538 clutter_stage_emit_after_update (ClutterStage *stage,
539 ClutterStageView *view)
540 {
541 g_signal_emit (stage, stage_signals[AFTER_UPDATE], 0, view);
542 }
543
544 static gboolean
clutter_stage_get_paint_volume(ClutterActor * self,ClutterPaintVolume * volume)545 clutter_stage_get_paint_volume (ClutterActor *self,
546 ClutterPaintVolume *volume)
547 {
548 /* Returning False effectively means Clutter has to assume it covers
549 * everything... */
550 return FALSE;
551 }
552
553 static void
clutter_stage_realize(ClutterActor * self)554 clutter_stage_realize (ClutterActor *self)
555 {
556 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
557 gboolean is_realized;
558
559 g_assert (priv->impl != NULL);
560 is_realized = _clutter_stage_window_realize (priv->impl);
561
562 if (!is_realized)
563 CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
564 }
565
566 static void
clutter_stage_unrealize(ClutterActor * self)567 clutter_stage_unrealize (ClutterActor *self)
568 {
569 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
570
571 /* and then unrealize the implementation */
572 g_assert (priv->impl != NULL);
573 _clutter_stage_window_unrealize (priv->impl);
574
575 CLUTTER_ACTOR_UNSET_FLAGS (self, CLUTTER_ACTOR_REALIZED);
576 }
577
578 static void
clutter_stage_show(ClutterActor * self)579 clutter_stage_show (ClutterActor *self)
580 {
581 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
582
583 CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->show (self);
584
585 /* Possibly do an allocation run so that the stage will have the
586 right size before we map it */
587 clutter_stage_maybe_relayout (self);
588
589 g_assert (priv->impl != NULL);
590 _clutter_stage_window_show (priv->impl, TRUE);
591 }
592
593 static void
clutter_stage_hide_all(ClutterActor * self)594 clutter_stage_hide_all (ClutterActor *self)
595 {
596 ClutterActorIter iter;
597 ClutterActor *child;
598
599 clutter_actor_hide (self);
600
601 /* we don't do a recursive hide_all(), to maintain the old invariants
602 * from ClutterGroup
603 */
604 clutter_actor_iter_init (&iter, self);
605 while (clutter_actor_iter_next (&iter, &child))
606 clutter_actor_hide (child);
607 }
608
609 static void
clutter_stage_hide(ClutterActor * self)610 clutter_stage_hide (ClutterActor *self)
611 {
612 ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv;
613
614 g_assert (priv->impl != NULL);
615 _clutter_stage_window_hide (priv->impl);
616
617 CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->hide (self);
618 }
619
620 static void
clutter_stage_emit_key_focus_event(ClutterStage * stage,gboolean focus_in)621 clutter_stage_emit_key_focus_event (ClutterStage *stage,
622 gboolean focus_in)
623 {
624 ClutterStagePrivate *priv = stage->priv;
625
626 if (priv->key_focused_actor == NULL)
627 return;
628
629 _clutter_actor_set_has_key_focus (CLUTTER_ACTOR (stage), focus_in);
630
631 g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_KEY_FOCUS]);
632 }
633
634 static void
clutter_stage_real_activate(ClutterStage * stage)635 clutter_stage_real_activate (ClutterStage *stage)
636 {
637 clutter_stage_emit_key_focus_event (stage, TRUE);
638 }
639
640 static void
clutter_stage_real_deactivate(ClutterStage * stage)641 clutter_stage_real_deactivate (ClutterStage *stage)
642 {
643 clutter_stage_emit_key_focus_event (stage, FALSE);
644 }
645
646 void
_clutter_stage_queue_event(ClutterStage * stage,ClutterEvent * event,gboolean copy_event)647 _clutter_stage_queue_event (ClutterStage *stage,
648 ClutterEvent *event,
649 gboolean copy_event)
650 {
651 ClutterStagePrivate *priv;
652 gboolean first_event;
653
654 g_return_if_fail (CLUTTER_IS_STAGE (stage));
655
656 priv = stage->priv;
657
658 first_event = priv->event_queue->length == 0;
659
660 if (copy_event)
661 event = clutter_event_copy (event);
662
663 if (first_event)
664 {
665 gboolean compressible = event->type == CLUTTER_MOTION ||
666 event->type == CLUTTER_TOUCH_UPDATE;
667
668 if (!compressible)
669 {
670 _clutter_process_event (event);
671 clutter_event_free (event);
672 return;
673 }
674 }
675
676 g_queue_push_tail (priv->event_queue, event);
677
678 if (first_event)
679 clutter_stage_schedule_update (stage);
680 }
681
682 gboolean
_clutter_stage_has_queued_events(ClutterStage * stage)683 _clutter_stage_has_queued_events (ClutterStage *stage)
684 {
685 ClutterStagePrivate *priv;
686
687 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
688
689 priv = stage->priv;
690
691 return priv->event_queue->length > 0;
692 }
693
694 static void
clutter_stage_compress_motion(ClutterStage * stage,ClutterEvent * event,const ClutterEvent * to_discard)695 clutter_stage_compress_motion (ClutterStage *stage,
696 ClutterEvent *event,
697 const ClutterEvent *to_discard)
698 {
699 double dx, dy;
700 double dx_unaccel, dy_unaccel;
701 double dst_dx = 0.0, dst_dy = 0.0;
702 double dst_dx_unaccel = 0.0, dst_dy_unaccel = 0.0;
703
704 if (!clutter_event_get_relative_motion (to_discard,
705 &dx, &dy,
706 &dx_unaccel, &dy_unaccel))
707 return;
708
709 clutter_event_get_relative_motion (event,
710 &dst_dx, &dst_dy,
711 &dst_dx_unaccel, &dst_dy_unaccel);
712
713 event->motion.flags |= CLUTTER_EVENT_FLAG_RELATIVE_MOTION;
714 event->motion.dx = dx + dst_dx;
715 event->motion.dy = dy + dst_dy;
716 event->motion.dx_unaccel = dx_unaccel + dst_dx_unaccel;
717 event->motion.dy_unaccel = dy_unaccel + dst_dy_unaccel;
718 }
719
720 void
_clutter_stage_process_queued_events(ClutterStage * stage)721 _clutter_stage_process_queued_events (ClutterStage *stage)
722 {
723 ClutterStagePrivate *priv;
724 GList *events, *l;
725
726 g_return_if_fail (CLUTTER_IS_STAGE (stage));
727
728 priv = stage->priv;
729
730 if (priv->event_queue->length == 0)
731 return;
732
733 /* In case the stage gets destroyed during event processing */
734 g_object_ref (stage);
735
736 /* Steal events before starting processing to avoid reentrancy
737 * issues */
738 events = priv->event_queue->head;
739 priv->event_queue->head = NULL;
740 priv->event_queue->tail = NULL;
741 priv->event_queue->length = 0;
742
743 for (l = events; l != NULL; l = l->next)
744 {
745 ClutterEvent *event;
746 ClutterEvent *next_event;
747 ClutterInputDevice *device;
748 ClutterInputDevice *next_device;
749 ClutterInputDeviceType device_type;
750 gboolean check_device = FALSE;
751
752 event = l->data;
753 next_event = l->next ? l->next->data : NULL;
754
755 device = clutter_event_get_device (event);
756
757 if (next_event != NULL)
758 next_device = clutter_event_get_device (next_event);
759 else
760 next_device = NULL;
761
762 if (device != NULL && next_device != NULL)
763 check_device = TRUE;
764
765 device_type = clutter_input_device_get_device_type (device);
766
767 /* Skip consecutive motion events coming from the same device,
768 * except those of tablet tools, since users of these events
769 * want no precision loss.
770 */
771 if (priv->throttle_motion_events && next_event != NULL &&
772 device_type != CLUTTER_TABLET_DEVICE &&
773 device_type != CLUTTER_PEN_DEVICE &&
774 device_type != CLUTTER_ERASER_DEVICE)
775 {
776 if (event->type == CLUTTER_MOTION &&
777 (next_event->type == CLUTTER_MOTION ||
778 next_event->type == CLUTTER_LEAVE) &&
779 (!check_device || (device == next_device)))
780 {
781 CLUTTER_NOTE (EVENT,
782 "Omitting motion event at %d, %d",
783 (int) event->motion.x,
784 (int) event->motion.y);
785
786 if (next_event->type == CLUTTER_MOTION)
787 clutter_stage_compress_motion (stage, next_event, event);
788
789 goto next_event;
790 }
791 else if (event->type == CLUTTER_TOUCH_UPDATE &&
792 next_event->type == CLUTTER_TOUCH_UPDATE &&
793 event->touch.sequence == next_event->touch.sequence &&
794 (!check_device || (device == next_device)))
795 {
796 CLUTTER_NOTE (EVENT,
797 "Omitting touch update event at %d, %d",
798 (int) event->touch.x,
799 (int) event->touch.y);
800 goto next_event;
801 }
802 }
803
804 _clutter_process_event (event);
805
806 next_event:
807 clutter_event_free (event);
808 }
809
810 g_list_free (events);
811
812 g_object_unref (stage);
813 }
814
815 void
clutter_stage_queue_actor_relayout(ClutterStage * stage,ClutterActor * actor)816 clutter_stage_queue_actor_relayout (ClutterStage *stage,
817 ClutterActor *actor)
818 {
819 ClutterStagePrivate *priv = stage->priv;
820
821 if (priv->pending_relayouts == NULL)
822 clutter_stage_schedule_update (stage);
823
824 priv->pending_relayouts = g_slist_prepend (priv->pending_relayouts,
825 g_object_ref (actor));
826 }
827
828 void
clutter_stage_dequeue_actor_relayout(ClutterStage * stage,ClutterActor * actor)829 clutter_stage_dequeue_actor_relayout (ClutterStage *stage,
830 ClutterActor *actor)
831 {
832 ClutterStagePrivate *priv = stage->priv;
833 GSList *l;
834
835 for (l = priv->pending_relayouts; l; l = l->next)
836 {
837 ClutterActor *relayout_actor = l->data;
838
839 if (relayout_actor == actor)
840 {
841 g_object_unref (relayout_actor);
842 priv->pending_relayouts =
843 g_slist_delete_link (priv->pending_relayouts, l);
844
845 return;
846 }
847 }
848 }
849
850 void
clutter_stage_maybe_relayout(ClutterActor * actor)851 clutter_stage_maybe_relayout (ClutterActor *actor)
852 {
853 ClutterStage *stage = CLUTTER_STAGE (actor);
854 ClutterStagePrivate *priv = stage->priv;
855 g_autoptr (GSList) stolen_list = NULL;
856 GSList *l;
857 int count = 0;
858
859 /* No work to do? Avoid the extraneous debug log messages too. */
860 if (priv->pending_relayouts == NULL)
861 return;
862
863 COGL_TRACE_BEGIN_SCOPED (ClutterStageRelayout, "Layout");
864
865 CLUTTER_NOTE (ACTOR, ">>> Recomputing layout");
866
867 stolen_list = g_steal_pointer (&priv->pending_relayouts);
868 for (l = stolen_list; l; l = l->next)
869 {
870 g_autoptr (ClutterActor) queued_actor = l->data;
871 float x = 0.f;
872 float y = 0.f;
873
874 if (CLUTTER_ACTOR_IN_RELAYOUT (queued_actor)) /* avoid reentrancy */
875 continue;
876
877 if (queued_actor == actor)
878 CLUTTER_NOTE (ACTOR, " Deep relayout of stage %s",
879 _clutter_actor_get_debug_name (queued_actor));
880 else
881 CLUTTER_NOTE (ACTOR, " Shallow relayout of actor %s",
882 _clutter_actor_get_debug_name (queued_actor));
883
884 CLUTTER_SET_PRIVATE_FLAGS (queued_actor, CLUTTER_IN_RELAYOUT);
885
886 clutter_actor_get_fixed_position (queued_actor, &x, &y);
887 clutter_actor_allocate_preferred_size (queued_actor, x, y);
888
889 CLUTTER_UNSET_PRIVATE_FLAGS (queued_actor, CLUTTER_IN_RELAYOUT);
890
891 count++;
892 }
893
894 CLUTTER_NOTE (ACTOR, "<<< Completed recomputing layout of %d subtrees", count);
895
896 if (count)
897 priv->needs_update_devices = TRUE;
898 }
899
900 GSList *
clutter_stage_find_updated_devices(ClutterStage * stage)901 clutter_stage_find_updated_devices (ClutterStage *stage)
902 {
903 ClutterStagePrivate *priv = stage->priv;
904 GSList *updating = NULL;
905 GHashTableIter iter;
906 gpointer value;
907
908 if (!priv->needs_update_devices)
909 return NULL;
910
911 priv->needs_update_devices = FALSE;
912
913 g_hash_table_iter_init (&iter, priv->pointer_devices);
914 while (g_hash_table_iter_next (&iter, NULL, &value))
915 {
916 PointerDeviceEntry *entry = value;
917 ClutterStageView *view;
918 const cairo_region_t *clip;
919
920 view = clutter_stage_get_view_at (stage, entry->coords.x, entry->coords.y);
921 if (!view)
922 continue;
923
924 clip = clutter_stage_view_peek_redraw_clip (view);
925 if (!clip || cairo_region_contains_point (clip, entry->coords.x, entry->coords.y))
926 updating = g_slist_prepend (updating, entry->device);
927 }
928
929 return updating;
930 }
931
932 void
clutter_stage_finish_layout(ClutterStage * stage)933 clutter_stage_finish_layout (ClutterStage *stage)
934 {
935 ClutterActor *actor = CLUTTER_ACTOR (stage);
936 ClutterStagePrivate *priv = stage->priv;
937 int phase;
938
939 COGL_TRACE_BEGIN_SCOPED (ClutterStageUpdateActorStageViews,
940 "Actor stage-views");
941
942 /* If an actor needs an immediate relayout because its resource scale
943 * changed, we give it another chance to allocate correctly before
944 * the paint.
945 *
946 * We're doing the whole thing twice and pass the phase to
947 * clutter_actor_finish_layout() to allow actors to detect loops:
948 * If the resource scale changes again after the relayout, the new
949 * allocation of an actor probably moved the actor onto another stage
950 * view, so if an actor sees phase == 1, it can choose a "final" scale.
951 */
952 for (phase = 0; phase < 2; phase++)
953 {
954 clutter_actor_finish_layout (actor, phase);
955
956 if (!priv->actor_needs_immediate_relayout)
957 break;
958
959 priv->actor_needs_immediate_relayout = FALSE;
960 clutter_stage_maybe_relayout (actor);
961 clutter_stage_maybe_finish_queue_redraws (stage);
962 }
963
964 g_warn_if_fail (!priv->actor_needs_immediate_relayout);
965 }
966
967 void
clutter_stage_update_devices(ClutterStage * stage,GSList * devices)968 clutter_stage_update_devices (ClutterStage *stage,
969 GSList *devices)
970 {
971 ClutterStagePrivate *priv = stage->priv;
972 GSList *l;
973
974 COGL_TRACE_BEGIN (ClutterStageUpdateDevices, "UpdateDevices");
975
976 for (l = devices; l; l = l->next)
977 {
978 ClutterInputDevice *device = l->data;
979 PointerDeviceEntry *entry = NULL;
980 ClutterActor *new_actor;
981
982 entry = g_hash_table_lookup (priv->pointer_devices, device);
983 g_assert (entry != NULL);
984
985 new_actor = _clutter_stage_do_pick (stage,
986 entry->coords.x,
987 entry->coords.y,
988 CLUTTER_PICK_REACTIVE);
989
990 clutter_stage_update_device (stage,
991 device, NULL,
992 entry->coords,
993 CLUTTER_CURRENT_TIME,
994 new_actor,
995 TRUE);
996 }
997 }
998
999 static void
clutter_stage_real_queue_relayout(ClutterActor * self)1000 clutter_stage_real_queue_relayout (ClutterActor *self)
1001 {
1002 ClutterStage *stage = CLUTTER_STAGE (self);
1003 ClutterActorClass *parent_class;
1004
1005 clutter_stage_queue_actor_relayout (stage, self);
1006
1007 /* chain up */
1008 parent_class = CLUTTER_ACTOR_CLASS (clutter_stage_parent_class);
1009 parent_class->queue_relayout (self);
1010 }
1011
1012 static gboolean
is_full_stage_redraw_queued(ClutterStage * stage)1013 is_full_stage_redraw_queued (ClutterStage *stage)
1014 {
1015 GList *l;
1016
1017 for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
1018 {
1019 ClutterStageView *view = l->data;
1020
1021 if (!clutter_stage_view_has_full_redraw_clip (view))
1022 return FALSE;
1023 }
1024
1025 return TRUE;
1026 }
1027
1028 gboolean
_clutter_stage_has_full_redraw_queued(ClutterStage * stage)1029 _clutter_stage_has_full_redraw_queued (ClutterStage *stage)
1030 {
1031 if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
1032 return FALSE;
1033
1034 return is_full_stage_redraw_queued (stage);
1035 }
1036
1037 static void
setup_ray_for_coordinates(ClutterStage * stage,float x,float y,graphene_point3d_t * point,graphene_ray_t * ray)1038 setup_ray_for_coordinates (ClutterStage *stage,
1039 float x,
1040 float y,
1041 graphene_point3d_t *point,
1042 graphene_ray_t *ray)
1043 {
1044 ClutterStagePrivate *priv = stage->priv;
1045 graphene_point3d_t camera_position;
1046 graphene_point3d_t p;
1047 graphene_vec3_t direction;
1048 graphene_vec3_t cv;
1049 graphene_vec3_t v;
1050
1051 camera_position = GRAPHENE_POINT3D_INIT_ZERO;
1052 graphene_vec3_init (&cv,
1053 camera_position.x,
1054 camera_position.y,
1055 camera_position.z);
1056
1057 p = GRAPHENE_POINT3D_INIT (x, y, 0.f);
1058 graphene_matrix_transform_point3d (&priv->view, &p, &p);
1059
1060 graphene_vec3_init (&v, p.x, p.y, p.z);
1061 graphene_vec3_subtract (&v, &cv, &direction);
1062 graphene_vec3_normalize (&direction, &direction);
1063
1064 graphene_ray_init (ray, &camera_position, &direction);
1065 graphene_point3d_init_from_point (point, &p);
1066 }
1067
1068 static ClutterActor *
_clutter_stage_do_pick_on_view(ClutterStage * stage,float x,float y,ClutterPickMode mode,ClutterStageView * view)1069 _clutter_stage_do_pick_on_view (ClutterStage *stage,
1070 float x,
1071 float y,
1072 ClutterPickMode mode,
1073 ClutterStageView *view)
1074 {
1075 g_autoptr (ClutterPickStack) pick_stack = NULL;
1076 ClutterPickContext *pick_context;
1077 graphene_point3d_t p;
1078 graphene_ray_t ray;
1079 ClutterActor *actor;
1080
1081 COGL_TRACE_BEGIN_SCOPED (ClutterStagePickView, "Pick (view)");
1082
1083 setup_ray_for_coordinates (stage, x, y, &p, &ray);
1084
1085 pick_context = clutter_pick_context_new_for_view (view, mode, &p, &ray);
1086
1087 clutter_actor_pick (CLUTTER_ACTOR (stage), pick_context);
1088 pick_stack = clutter_pick_context_steal_stack (pick_context);
1089 clutter_pick_context_destroy (pick_context);
1090
1091 actor = clutter_pick_stack_search_actor (pick_stack, &p, &ray);
1092 return actor ? actor : CLUTTER_ACTOR (stage);
1093 }
1094
1095 /**
1096 * clutter_stage_get_view_at: (skip)
1097 */
1098 ClutterStageView *
clutter_stage_get_view_at(ClutterStage * stage,float x,float y)1099 clutter_stage_get_view_at (ClutterStage *stage,
1100 float x,
1101 float y)
1102 {
1103 ClutterStagePrivate *priv = stage->priv;
1104 GList *l;
1105
1106 for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next)
1107 {
1108 ClutterStageView *view = l->data;
1109 cairo_rectangle_int_t view_layout;
1110
1111 clutter_stage_view_get_layout (view, &view_layout);
1112 if (x >= view_layout.x &&
1113 x < view_layout.x + view_layout.width &&
1114 y >= view_layout.y &&
1115 y < view_layout.y + view_layout.height)
1116 return view;
1117 }
1118
1119 return NULL;
1120 }
1121
1122 ClutterActor *
_clutter_stage_do_pick(ClutterStage * stage,float x,float y,ClutterPickMode mode)1123 _clutter_stage_do_pick (ClutterStage *stage,
1124 float x,
1125 float y,
1126 ClutterPickMode mode)
1127 {
1128 ClutterActor *actor = CLUTTER_ACTOR (stage);
1129 ClutterStagePrivate *priv = stage->priv;
1130 float stage_width, stage_height;
1131 ClutterStageView *view = NULL;
1132
1133 priv = stage->priv;
1134
1135 if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
1136 return actor;
1137
1138 if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_NOP_PICKING))
1139 return actor;
1140
1141 if (G_UNLIKELY (priv->impl == NULL))
1142 return actor;
1143
1144 clutter_actor_get_size (CLUTTER_ACTOR (stage), &stage_width, &stage_height);
1145 if (x < 0 || x >= stage_width || y < 0 || y >= stage_height)
1146 return actor;
1147
1148 view = clutter_stage_get_view_at (stage, x, y);
1149 if (view)
1150 return _clutter_stage_do_pick_on_view (stage, x, y, mode, view);
1151
1152 return actor;
1153 }
1154
1155 static void
clutter_stage_real_apply_transform(ClutterActor * stage,graphene_matrix_t * matrix)1156 clutter_stage_real_apply_transform (ClutterActor *stage,
1157 graphene_matrix_t *matrix)
1158 {
1159 ClutterStagePrivate *priv = CLUTTER_STAGE (stage)->priv;
1160
1161 /* FIXME: we probably shouldn't be explicitly resetting the matrix
1162 * here... */
1163 graphene_matrix_init_from_matrix (matrix, &priv->view);
1164 }
1165
1166 static void
clutter_stage_constructed(GObject * gobject)1167 clutter_stage_constructed (GObject *gobject)
1168 {
1169 ClutterStage *self = CLUTTER_STAGE (gobject);
1170 ClutterStageManager *stage_manager;
1171
1172 stage_manager = clutter_stage_manager_get_default ();
1173
1174 /* this will take care to sinking the floating reference */
1175 _clutter_stage_manager_add_stage (stage_manager, self);
1176
1177 /* if this stage has been created on a backend that does not
1178 * support multiple stages then it becomes the default stage
1179 * as well; any other attempt at creating a ClutterStage will
1180 * fail.
1181 */
1182 if (!clutter_feature_available (CLUTTER_FEATURE_STAGE_MULTIPLE))
1183 {
1184 if (G_UNLIKELY (clutter_stage_manager_get_default_stage (stage_manager) != NULL))
1185 {
1186 g_error ("Unable to create another stage: the backend of "
1187 "type '%s' does not support multiple stages. Use "
1188 "clutter_stage_manager_get_default_stage() instead "
1189 "to access the stage singleton.",
1190 G_OBJECT_TYPE_NAME (clutter_get_default_backend ()));
1191 }
1192
1193 _clutter_stage_manager_set_default_stage (stage_manager, self);
1194 }
1195
1196 G_OBJECT_CLASS (clutter_stage_parent_class)->constructed (gobject);
1197 }
1198
1199 static void
clutter_stage_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1200 clutter_stage_set_property (GObject *object,
1201 guint prop_id,
1202 const GValue *value,
1203 GParamSpec *pspec)
1204 {
1205 ClutterStage *stage = CLUTTER_STAGE (object);
1206
1207 switch (prop_id)
1208 {
1209 case PROP_TITLE:
1210 clutter_stage_set_title (stage, g_value_get_string (value));
1211 break;
1212
1213 case PROP_KEY_FOCUS:
1214 clutter_stage_set_key_focus (stage, g_value_get_object (value));
1215 break;
1216
1217 default:
1218 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1219 break;
1220 }
1221 }
1222
1223 static void
clutter_stage_get_property(GObject * gobject,guint prop_id,GValue * value,GParamSpec * pspec)1224 clutter_stage_get_property (GObject *gobject,
1225 guint prop_id,
1226 GValue *value,
1227 GParamSpec *pspec)
1228 {
1229 ClutterStagePrivate *priv = CLUTTER_STAGE (gobject)->priv;
1230
1231 switch (prop_id)
1232 {
1233 case PROP_PERSPECTIVE:
1234 g_value_set_boxed (value, &priv->perspective);
1235 break;
1236
1237 case PROP_TITLE:
1238 g_value_set_string (value, priv->title);
1239 break;
1240
1241 case PROP_KEY_FOCUS:
1242 g_value_set_object (value, priv->key_focused_actor);
1243 break;
1244
1245 default:
1246 G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1247 break;
1248 }
1249 }
1250
1251 static void
clutter_stage_dispose(GObject * object)1252 clutter_stage_dispose (GObject *object)
1253 {
1254 ClutterStage *stage = CLUTTER_STAGE (object);
1255 ClutterStagePrivate *priv = stage->priv;
1256 ClutterStageManager *stage_manager;
1257
1258 clutter_actor_hide (CLUTTER_ACTOR (object));
1259
1260 _clutter_clear_events_queue ();
1261
1262 if (priv->impl != NULL)
1263 {
1264 CLUTTER_NOTE (BACKEND, "Disposing of the stage implementation");
1265
1266 if (CLUTTER_ACTOR_IS_REALIZED (object))
1267 _clutter_stage_window_unrealize (priv->impl);
1268
1269 g_object_unref (priv->impl);
1270 priv->impl = NULL;
1271 }
1272
1273 clutter_actor_destroy_all_children (CLUTTER_ACTOR (object));
1274
1275 g_hash_table_remove_all (priv->pending_queue_redraws);
1276
1277 g_slist_free_full (priv->pending_relayouts,
1278 (GDestroyNotify) g_object_unref);
1279 priv->pending_relayouts = NULL;
1280
1281 /* this will release the reference on the stage */
1282 stage_manager = clutter_stage_manager_get_default ();
1283 _clutter_stage_manager_remove_stage (stage_manager, stage);
1284
1285 G_OBJECT_CLASS (clutter_stage_parent_class)->dispose (object);
1286 }
1287
1288 static void
clutter_stage_finalize(GObject * object)1289 clutter_stage_finalize (GObject *object)
1290 {
1291 ClutterStage *stage = CLUTTER_STAGE (object);
1292 ClutterStagePrivate *priv = stage->priv;
1293
1294 g_queue_foreach (priv->event_queue, (GFunc) clutter_event_free, NULL);
1295 g_queue_free (priv->event_queue);
1296
1297 g_hash_table_destroy (priv->pointer_devices);
1298 g_hash_table_destroy (priv->touch_sequences);
1299
1300 g_free (priv->title);
1301
1302 g_array_free (priv->paint_volume_stack, TRUE);
1303
1304 G_OBJECT_CLASS (clutter_stage_parent_class)->finalize (object);
1305 }
1306
1307 static void
clutter_stage_real_paint_view(ClutterStage * stage,ClutterStageView * view,const cairo_region_t * redraw_clip)1308 clutter_stage_real_paint_view (ClutterStage *stage,
1309 ClutterStageView *view,
1310 const cairo_region_t *redraw_clip)
1311 {
1312 clutter_stage_do_paint_view (stage, view, redraw_clip);
1313 }
1314
1315 static void
clutter_stage_paint(ClutterActor * actor,ClutterPaintContext * paint_context)1316 clutter_stage_paint (ClutterActor *actor,
1317 ClutterPaintContext *paint_context)
1318 {
1319 ClutterStageView *view;
1320
1321 CLUTTER_ACTOR_CLASS (clutter_stage_parent_class)->paint (actor, paint_context);
1322
1323 view = clutter_paint_context_get_stage_view (paint_context);
1324 if (view &&
1325 G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_PAINT_MAX_RENDER_TIME))
1326 {
1327 cairo_rectangle_int_t view_layout;
1328 ClutterFrameClock *frame_clock;
1329 g_autoptr (GString) string = NULL;
1330 PangoLayout *layout;
1331 PangoRectangle logical;
1332 ClutterColor color;
1333 g_autoptr (ClutterPaintNode) node = NULL;
1334 ClutterActorBox box;
1335
1336 clutter_stage_view_get_layout (view, &view_layout);
1337 frame_clock = clutter_stage_view_get_frame_clock (view);
1338
1339 string = clutter_frame_clock_get_max_render_time_debug_info (frame_clock);
1340
1341 layout = clutter_actor_create_pango_layout (actor, string->str);
1342 pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
1343 pango_layout_get_pixel_extents (layout, NULL, &logical);
1344
1345 clutter_color_init (&color, 255, 255, 255, 255);
1346 node = clutter_text_node_new (layout, &color);
1347
1348 box.x1 = view_layout.x;
1349 box.y1 = view_layout.y + 30;
1350 box.x2 = box.x1 + logical.width;
1351 box.y2 = box.y1 + logical.height;
1352 clutter_paint_node_add_rectangle (node, &box);
1353
1354 clutter_paint_node_paint (node, paint_context);
1355
1356 g_object_unref (layout);
1357 }
1358 }
1359
1360 static void
clutter_stage_class_init(ClutterStageClass * klass)1361 clutter_stage_class_init (ClutterStageClass *klass)
1362 {
1363 GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1364 ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
1365
1366 gobject_class->constructed = clutter_stage_constructed;
1367 gobject_class->set_property = clutter_stage_set_property;
1368 gobject_class->get_property = clutter_stage_get_property;
1369 gobject_class->dispose = clutter_stage_dispose;
1370 gobject_class->finalize = clutter_stage_finalize;
1371
1372 actor_class->allocate = clutter_stage_allocate;
1373 actor_class->get_preferred_width = clutter_stage_get_preferred_width;
1374 actor_class->get_preferred_height = clutter_stage_get_preferred_height;
1375 actor_class->get_paint_volume = clutter_stage_get_paint_volume;
1376 actor_class->realize = clutter_stage_realize;
1377 actor_class->unrealize = clutter_stage_unrealize;
1378 actor_class->show = clutter_stage_show;
1379 actor_class->hide = clutter_stage_hide;
1380 actor_class->hide_all = clutter_stage_hide_all;
1381 actor_class->queue_relayout = clutter_stage_real_queue_relayout;
1382 actor_class->apply_transform = clutter_stage_real_apply_transform;
1383 actor_class->paint = clutter_stage_paint;
1384
1385 klass->paint_view = clutter_stage_real_paint_view;
1386
1387 /**
1388 * ClutterStage:perspective:
1389 *
1390 * The parameters used for the perspective projection from 3D
1391 * coordinates to 2D
1392 *
1393 * Since: 0.8
1394 */
1395 obj_props[PROP_PERSPECTIVE] =
1396 g_param_spec_boxed ("perspective",
1397 P_("Perspective"),
1398 P_("Perspective projection parameters"),
1399 CLUTTER_TYPE_PERSPECTIVE,
1400 CLUTTER_PARAM_READABLE |
1401 G_PARAM_EXPLICIT_NOTIFY);
1402
1403 /**
1404 * ClutterStage:title:
1405 *
1406 * The stage's title - usually displayed in stage windows title decorations.
1407 *
1408 * Since: 0.4
1409 */
1410 obj_props[PROP_TITLE] =
1411 g_param_spec_string ("title",
1412 P_("Title"),
1413 P_("Stage Title"),
1414 NULL,
1415 CLUTTER_PARAM_READWRITE |
1416 G_PARAM_EXPLICIT_NOTIFY);
1417
1418 /**
1419 * ClutterStage:key-focus:
1420 *
1421 * The #ClutterActor that will receive key events from the underlying
1422 * windowing system.
1423 *
1424 * If %NULL, the #ClutterStage will receive the events.
1425 *
1426 * Since: 1.2
1427 */
1428 obj_props[PROP_KEY_FOCUS] =
1429 g_param_spec_object ("key-focus",
1430 P_("Key Focus"),
1431 P_("The currently key focused actor"),
1432 CLUTTER_TYPE_ACTOR,
1433 CLUTTER_PARAM_READWRITE |
1434 G_PARAM_EXPLICIT_NOTIFY);
1435
1436 g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
1437
1438 /**
1439 * ClutterStage::activate:
1440 * @stage: the stage which was activated
1441 *
1442 * The ::activate signal is emitted when the stage receives key focus
1443 * from the underlying window system.
1444 *
1445 * Since: 0.6
1446 */
1447 stage_signals[ACTIVATE] =
1448 g_signal_new (I_("activate"),
1449 G_TYPE_FROM_CLASS (gobject_class),
1450 G_SIGNAL_RUN_LAST,
1451 G_STRUCT_OFFSET (ClutterStageClass, activate),
1452 NULL, NULL, NULL,
1453 G_TYPE_NONE, 0);
1454 /**
1455 * ClutterStage::deactivate:
1456 * @stage: the stage which was deactivated
1457 *
1458 * The ::deactivate signal is emitted when the stage loses key focus
1459 * from the underlying window system.
1460 *
1461 * Since: 0.6
1462 */
1463 stage_signals[DEACTIVATE] =
1464 g_signal_new (I_("deactivate"),
1465 G_TYPE_FROM_CLASS (gobject_class),
1466 G_SIGNAL_RUN_LAST,
1467 G_STRUCT_OFFSET (ClutterStageClass, deactivate),
1468 NULL, NULL, NULL,
1469 G_TYPE_NONE, 0);
1470
1471 /**
1472 * ClutterStage::before-update:
1473 * @stage: the #ClutterStage
1474 * @view: a #ClutterStageView
1475 */
1476 stage_signals[BEFORE_UPDATE] =
1477 g_signal_new (I_("before-update"),
1478 G_TYPE_FROM_CLASS (gobject_class),
1479 G_SIGNAL_RUN_LAST,
1480 0,
1481 NULL, NULL, NULL,
1482 G_TYPE_NONE, 1,
1483 CLUTTER_TYPE_STAGE_VIEW);
1484
1485 /**
1486 * ClutterStage::before-paint:
1487 * @stage: the stage that received the event
1488 * @view: a #ClutterStageView
1489 *
1490 * The ::before-paint signal is emitted before the stage is painted.
1491 */
1492 stage_signals[BEFORE_PAINT] =
1493 g_signal_new (I_("before-paint"),
1494 G_TYPE_FROM_CLASS (gobject_class),
1495 G_SIGNAL_RUN_LAST,
1496 G_STRUCT_OFFSET (ClutterStageClass, before_paint),
1497 NULL, NULL, NULL,
1498 G_TYPE_NONE, 1,
1499 CLUTTER_TYPE_STAGE_VIEW);
1500 /**
1501 * ClutterStage::after-paint:
1502 * @stage: the stage that received the event
1503 * @view: a #ClutterStageView
1504 *
1505 * The ::after-paint signal is emitted after the stage is painted,
1506 * but before the results are displayed on the screen.
1507 *
1508 * Since: 1.20
1509 */
1510 stage_signals[AFTER_PAINT] =
1511 g_signal_new (I_("after-paint"),
1512 G_TYPE_FROM_CLASS (gobject_class),
1513 G_SIGNAL_RUN_LAST,
1514 0, /* no corresponding vfunc */
1515 NULL, NULL, NULL,
1516 G_TYPE_NONE, 1,
1517 CLUTTER_TYPE_STAGE_VIEW);
1518
1519 /**
1520 * ClutterStage::after-update:
1521 * @stage: the #ClutterStage
1522 * @view: a #ClutterStageView
1523 */
1524 stage_signals[AFTER_UPDATE] =
1525 g_signal_new (I_("after-update"),
1526 G_TYPE_FROM_CLASS (gobject_class),
1527 G_SIGNAL_RUN_LAST,
1528 0,
1529 NULL, NULL, NULL,
1530 G_TYPE_NONE, 1,
1531 CLUTTER_TYPE_STAGE_VIEW);
1532
1533 /**
1534 * ClutterStage::paint-view:
1535 * @stage: the stage that received the event
1536 * @view: a #ClutterStageView
1537 * @redraw_clip: a #cairo_region_t with the redraw clip
1538 *
1539 * The ::paint-view signal is emitted before a #ClutterStageView is being
1540 * painted.
1541 *
1542 * The view is painted in the default handler. Hence, if you want to perform
1543 * some action after the view is painted, like reading the contents of the
1544 * framebuffer, use g_signal_connect_after() or pass %G_CONNECT_AFTER.
1545 */
1546 stage_signals[PAINT_VIEW] =
1547 g_signal_new (I_("paint-view"),
1548 G_TYPE_FROM_CLASS (gobject_class),
1549 G_SIGNAL_RUN_LAST,
1550 G_STRUCT_OFFSET (ClutterStageClass, paint_view),
1551 NULL, NULL, NULL,
1552 G_TYPE_NONE, 2,
1553 CLUTTER_TYPE_STAGE_VIEW,
1554 CAIRO_GOBJECT_TYPE_REGION);
1555
1556 /**
1557 * ClutterStage::presented: (skip)
1558 * @stage: the stage that received the event
1559 * @view: the #ClutterStageView presented
1560 * @frame_info: a #ClutterFrameInfo
1561 *
1562 * Signals that the #ClutterStage was presented on the screen to the user.
1563 */
1564 stage_signals[PRESENTED] =
1565 g_signal_new (I_("presented"),
1566 G_TYPE_FROM_CLASS (gobject_class),
1567 G_SIGNAL_RUN_LAST,
1568 0,
1569 NULL, NULL, NULL,
1570 G_TYPE_NONE, 2,
1571 CLUTTER_TYPE_STAGE_VIEW,
1572 G_TYPE_POINTER);
1573
1574 /**
1575 * ClutterStage::gl-video-memory-purged: (skip)
1576 * @stage: the stage that received the event
1577 *
1578 * Signals that the underlying GL driver has had its texture memory purged
1579 * so anything presently held in texture memory is now invalidated, and
1580 * likely corrupt. It needs redrawing.
1581 */
1582 stage_signals[GL_VIDEO_MEMORY_PURGED] =
1583 g_signal_new (I_("gl-video-memory-purged"),
1584 G_TYPE_FROM_CLASS (gobject_class),
1585 G_SIGNAL_RUN_LAST,
1586 0,
1587 NULL, NULL, NULL,
1588 G_TYPE_NONE, 0);
1589
1590 klass->activate = clutter_stage_real_activate;
1591 klass->deactivate = clutter_stage_real_deactivate;
1592 }
1593
1594 static void
clutter_stage_notify_min_size(ClutterStage * self)1595 clutter_stage_notify_min_size (ClutterStage *self)
1596 {
1597 self->priv->min_size_changed = TRUE;
1598 }
1599
1600 static void
clutter_stage_init(ClutterStage * self)1601 clutter_stage_init (ClutterStage *self)
1602 {
1603 cairo_rectangle_int_t geom = { 0, };
1604 ClutterStagePrivate *priv;
1605 ClutterStageWindow *impl;
1606 ClutterBackend *backend;
1607 GError *error;
1608
1609 /* a stage is a top-level object */
1610 CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_IS_TOPLEVEL);
1611
1612 self->priv = priv = clutter_stage_get_instance_private (self);
1613
1614 CLUTTER_NOTE (BACKEND, "Creating stage from the default backend");
1615 backend = clutter_get_default_backend ();
1616
1617 error = NULL;
1618 impl = _clutter_backend_create_stage (backend, self, &error);
1619
1620 if (G_LIKELY (impl != NULL))
1621 {
1622 _clutter_stage_set_window (self, impl);
1623 _clutter_stage_window_get_geometry (priv->impl, &geom);
1624 }
1625 else
1626 {
1627 if (error != NULL)
1628 {
1629 g_critical ("Unable to create a new stage implementation: %s",
1630 error->message);
1631 g_error_free (error);
1632 }
1633 else
1634 g_critical ("Unable to create a new stage implementation.");
1635 }
1636
1637 priv->event_queue = g_queue_new ();
1638
1639 priv->throttle_motion_events = TRUE;
1640 priv->min_size_changed = FALSE;
1641 priv->motion_events_enabled = TRUE;
1642
1643 priv->pointer_devices =
1644 g_hash_table_new_full (NULL, NULL,
1645 NULL, (GDestroyNotify) free_pointer_device_entry);
1646 priv->touch_sequences =
1647 g_hash_table_new_full (NULL, NULL,
1648 NULL, (GDestroyNotify) free_pointer_device_entry);
1649
1650 clutter_actor_set_background_color (CLUTTER_ACTOR (self),
1651 &default_stage_color);
1652
1653 clutter_stage_queue_actor_relayout (self, CLUTTER_ACTOR (self));
1654
1655 clutter_actor_set_reactive (CLUTTER_ACTOR (self), TRUE);
1656 clutter_stage_set_title (self, g_get_prgname ());
1657 clutter_stage_set_key_focus (self, NULL);
1658
1659 g_signal_connect (self, "notify::min-width",
1660 G_CALLBACK (clutter_stage_notify_min_size), NULL);
1661 g_signal_connect (self, "notify::min-height",
1662 G_CALLBACK (clutter_stage_notify_min_size), NULL);
1663
1664 clutter_stage_set_viewport (self, geom.width, geom.height);
1665
1666 priv->pending_queue_redraws =
1667 g_hash_table_new_full (NULL, NULL,
1668 g_object_unref,
1669 (GDestroyNotify) free_queue_redraw_entry);
1670
1671 priv->paint_volume_stack =
1672 g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume));
1673 }
1674
1675 static void
clutter_stage_set_perspective(ClutterStage * stage,ClutterPerspective * perspective)1676 clutter_stage_set_perspective (ClutterStage *stage,
1677 ClutterPerspective *perspective)
1678 {
1679 ClutterStagePrivate *priv = stage->priv;
1680
1681 if (priv->perspective.fovy == perspective->fovy &&
1682 priv->perspective.aspect == perspective->aspect &&
1683 priv->perspective.z_near == perspective->z_near &&
1684 priv->perspective.z_far == perspective->z_far)
1685 return;
1686
1687 priv->perspective = *perspective;
1688
1689 graphene_matrix_init_perspective (&priv->projection,
1690 priv->perspective.fovy,
1691 priv->perspective.aspect,
1692 priv->perspective.z_near,
1693 priv->perspective.z_far);
1694 graphene_matrix_inverse (&priv->projection,
1695 &priv->inverse_projection);
1696
1697 _clutter_stage_dirty_projection (stage);
1698 clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
1699 }
1700
1701 /**
1702 * clutter_stage_get_perspective:
1703 * @stage: A #ClutterStage
1704 * @perspective: (out caller-allocates) (allow-none): return location for a
1705 * #ClutterPerspective
1706 *
1707 * Retrieves the stage perspective.
1708 */
1709 void
clutter_stage_get_perspective(ClutterStage * stage,ClutterPerspective * perspective)1710 clutter_stage_get_perspective (ClutterStage *stage,
1711 ClutterPerspective *perspective)
1712 {
1713 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1714 g_return_if_fail (perspective != NULL);
1715
1716 *perspective = stage->priv->perspective;
1717 }
1718
1719 /*
1720 * clutter_stage_get_projection_matrix:
1721 * @stage: A #ClutterStage
1722 * @projection: return location for a #graphene_matrix_t representing the
1723 * perspective projection applied to actors on the given
1724 * @stage.
1725 *
1726 * Retrieves the @stage's projection matrix. This is derived from the
1727 * current perspective.
1728 *
1729 * Since: 1.6
1730 */
1731 void
_clutter_stage_get_projection_matrix(ClutterStage * stage,graphene_matrix_t * projection)1732 _clutter_stage_get_projection_matrix (ClutterStage *stage,
1733 graphene_matrix_t *projection)
1734 {
1735 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1736 g_return_if_fail (projection != NULL);
1737
1738 *projection = stage->priv->projection;
1739 }
1740
1741 /* This simply provides a simple mechanism for us to ensure that
1742 * the projection matrix gets re-asserted before painting.
1743 *
1744 * This is used when switching between multiple stages */
1745 void
_clutter_stage_dirty_projection(ClutterStage * stage)1746 _clutter_stage_dirty_projection (ClutterStage *stage)
1747 {
1748 ClutterStagePrivate *priv;
1749 GList *l;
1750
1751 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1752
1753 priv = stage->priv;
1754
1755 for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next)
1756 {
1757 ClutterStageView *view = l->data;
1758
1759 clutter_stage_view_invalidate_projection (view);
1760 }
1761 }
1762
1763 /*
1764 * clutter_stage_set_viewport:
1765 * @stage: A #ClutterStage
1766 * @width: The width to render the stage at, in window coordinates
1767 * @height: The height to render the stage at, in window coordinates
1768 *
1769 * Sets the stage viewport. The viewport defines a final scale and
1770 * translation of your rendered stage and actors. This lets you render
1771 * your stage into a subregion of the stage window or you could use it to
1772 * pan a subregion of the stage if your stage window is smaller then
1773 * the stage. (XXX: currently this isn't possible)
1774 *
1775 * Unlike a scale and translation done using the modelview matrix this
1776 * is done after everything has had perspective projection applied, so
1777 * for example if you were to pan across a subregion of the stage using
1778 * the viewport then you would not see a change in perspective for the
1779 * actors on the stage.
1780 *
1781 * Normally the stage viewport will automatically track the size of the
1782 * stage window with no offset so the stage will fill your window. This
1783 * behaviour can be changed with the "viewport-mimics-window" property
1784 * which will automatically be set to FALSE if you use this API. If
1785 * you want to revert to the original behaviour then you should set
1786 * this property back to %TRUE using
1787 * clutter_stage_set_viewport_mimics_window().
1788 * (XXX: If we were to make this API public then we might want to do
1789 * add that property.)
1790 *
1791 * Note: currently this interface only support integer precision
1792 * offsets and sizes for viewports but the interface takes floats because
1793 * OpenGL 4.0 has introduced floating point viewports which we might
1794 * want to expose via this API eventually.
1795 *
1796 * Since: 1.6
1797 */
1798 static void
clutter_stage_set_viewport(ClutterStage * stage,float width,float height)1799 clutter_stage_set_viewport (ClutterStage *stage,
1800 float width,
1801 float height)
1802 {
1803 ClutterStagePrivate *priv;
1804 float x, y;
1805
1806 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1807
1808 priv = stage->priv;
1809
1810 x = 0.f;
1811 y = 0.f;
1812 width = roundf (width);
1813 height = roundf (height);
1814
1815 if (x == priv->viewport[0] &&
1816 y == priv->viewport[1] &&
1817 width == priv->viewport[2] &&
1818 height == priv->viewport[3])
1819 return;
1820
1821 priv->viewport[0] = x;
1822 priv->viewport[1] = y;
1823 priv->viewport[2] = width;
1824 priv->viewport[3] = height;
1825
1826 clutter_stage_update_view_perspective (stage);
1827 _clutter_stage_dirty_viewport (stage);
1828
1829 queue_full_redraw (stage);
1830 }
1831
1832 /* This simply provides a simple mechanism for us to ensure that
1833 * the viewport gets re-asserted before next painting.
1834 *
1835 * This is used when switching between multiple stages */
1836 void
_clutter_stage_dirty_viewport(ClutterStage * stage)1837 _clutter_stage_dirty_viewport (ClutterStage *stage)
1838 {
1839 ClutterStagePrivate *priv;
1840 GList *l;
1841
1842 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1843
1844 priv = stage->priv;
1845
1846 for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next)
1847 {
1848 ClutterStageView *view = l->data;
1849
1850 clutter_stage_view_invalidate_viewport (view);
1851 }
1852 }
1853
1854 /*
1855 * clutter_stage_get_viewport:
1856 * @stage: A #ClutterStage
1857 * @x: A location for the X position where the stage is rendered,
1858 * in window coordinates.
1859 * @y: A location for the Y position where the stage is rendered,
1860 * in window coordinates.
1861 * @width: A location for the width the stage is rendered at,
1862 * in window coordinates.
1863 * @height: A location for the height the stage is rendered at,
1864 * in window coordinates.
1865 *
1866 * Returns the viewport offset and size set using
1867 * clutter_stage_set_viewport() or if the "viewport-mimics-window" property
1868 * is TRUE then @x and @y will be set to 0 and @width and @height will equal
1869 * the width if the stage window.
1870 *
1871 * Since: 1.6
1872 */
1873 void
_clutter_stage_get_viewport(ClutterStage * stage,float * x,float * y,float * width,float * height)1874 _clutter_stage_get_viewport (ClutterStage *stage,
1875 float *x,
1876 float *y,
1877 float *width,
1878 float *height)
1879 {
1880 ClutterStagePrivate *priv;
1881
1882 g_return_if_fail (CLUTTER_IS_STAGE (stage));
1883
1884 priv = stage->priv;
1885
1886 *x = priv->viewport[0];
1887 *y = priv->viewport[1];
1888 *width = priv->viewport[2];
1889 *height = priv->viewport[3];
1890 }
1891
1892 /**
1893 * clutter_stage_read_pixels:
1894 * @stage: A #ClutterStage
1895 * @x: x coordinate of the first pixel that is read from stage
1896 * @y: y coordinate of the first pixel that is read from stage
1897 * @width: Width dimension of pixels to be read, or -1 for the
1898 * entire stage width
1899 * @height: Height dimension of pixels to be read, or -1 for the
1900 * entire stage height
1901 *
1902 * Makes a screenshot of the stage in RGBA 8bit data, returns a
1903 * linear buffer with @width * 4 as rowstride.
1904 *
1905 * The alpha data contained in the returned buffer is driver-dependent,
1906 * and not guaranteed to hold any sensible value.
1907 *
1908 * Return value: (transfer full) (array): a pointer to newly allocated memory with the buffer
1909 * or %NULL if the read failed. Use g_free() on the returned data
1910 * to release the resources it has allocated.
1911 */
1912 guchar *
clutter_stage_read_pixels(ClutterStage * stage,gint x,gint y,gint width,gint height)1913 clutter_stage_read_pixels (ClutterStage *stage,
1914 gint x,
1915 gint y,
1916 gint width,
1917 gint height)
1918 {
1919 ClutterStagePrivate *priv;
1920 ClutterActorBox box;
1921 GList *l;
1922 ClutterStageView *view;
1923 cairo_region_t *clip;
1924 cairo_rectangle_int_t clip_rect;
1925 CoglFramebuffer *framebuffer;
1926 float view_scale;
1927 float pixel_width;
1928 float pixel_height;
1929 uint8_t *pixels;
1930
1931 COGL_TRACE_BEGIN_SCOPED (ClutterStageReadPixels, "Read Pixels");
1932
1933 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
1934
1935 priv = stage->priv;
1936
1937 clutter_actor_get_allocation_box (CLUTTER_ACTOR (stage), &box);
1938
1939 if (width < 0)
1940 width = ceilf (box.x2 - box.x1);
1941
1942 if (height < 0)
1943 height = ceilf (box.y2 - box.y1);
1944
1945 l = _clutter_stage_window_get_views (priv->impl);
1946
1947 if (!l)
1948 return NULL;
1949
1950 /* XXX: We only read the first view. Needs different API for multi view screen
1951 * capture. */
1952 view = l->data;
1953
1954 clutter_stage_view_get_layout (view, &clip_rect);
1955 clip = cairo_region_create_rectangle (&clip_rect);
1956 cairo_region_intersect_rectangle (clip,
1957 &(cairo_rectangle_int_t) {
1958 .x = x,
1959 .y = y,
1960 .width = width,
1961 .height = height,
1962 });
1963 cairo_region_get_extents (clip, &clip_rect);
1964
1965 if (clip_rect.width == 0 || clip_rect.height == 0)
1966 {
1967 cairo_region_destroy (clip);
1968 return NULL;
1969 }
1970
1971 framebuffer = clutter_stage_view_get_framebuffer (view);
1972 clutter_stage_do_paint_view (stage, view, clip);
1973
1974 cairo_region_destroy (clip);
1975
1976 view_scale = clutter_stage_view_get_scale (view);
1977 pixel_width = roundf (clip_rect.width * view_scale);
1978 pixel_height = roundf (clip_rect.height * view_scale);
1979
1980 pixels = g_malloc0 (pixel_width * pixel_height * 4);
1981 cogl_framebuffer_read_pixels (framebuffer,
1982 clip_rect.x * view_scale,
1983 clip_rect.y * view_scale,
1984 pixel_width, pixel_height,
1985 COGL_PIXEL_FORMAT_RGBA_8888,
1986 pixels);
1987
1988 return pixels;
1989 }
1990
1991 /**
1992 * clutter_stage_get_actor_at_pos:
1993 * @stage: a #ClutterStage
1994 * @pick_mode: how the scene graph should be painted
1995 * @x: X coordinate to check
1996 * @y: Y coordinate to check
1997 *
1998 * Checks the scene at the coordinates @x and @y and returns a pointer
1999 * to the #ClutterActor at those coordinates. The result is the actor which
2000 * would be at the specified location on the next redraw, and is not
2001 * necessarily that which was there on the previous redraw. This allows the
2002 * function to perform chronologically correctly after any queued changes to
2003 * the scene, and even if nothing has been drawn.
2004 *
2005 * By using @pick_mode it is possible to control which actors will be
2006 * painted and thus available.
2007 *
2008 * Return value: (transfer none): the actor at the specified coordinates,
2009 * if any
2010 */
2011 ClutterActor *
clutter_stage_get_actor_at_pos(ClutterStage * stage,ClutterPickMode pick_mode,float x,float y)2012 clutter_stage_get_actor_at_pos (ClutterStage *stage,
2013 ClutterPickMode pick_mode,
2014 float x,
2015 float y)
2016 {
2017 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
2018
2019 return _clutter_stage_do_pick (stage, x, y, pick_mode);
2020 }
2021
2022 /**
2023 * clutter_stage_set_title:
2024 * @stage: A #ClutterStage
2025 * @title: A utf8 string for the stage windows title.
2026 *
2027 * Sets the stage title.
2028 *
2029 * Since: 0.4
2030 **/
2031 void
clutter_stage_set_title(ClutterStage * stage,const gchar * title)2032 clutter_stage_set_title (ClutterStage *stage,
2033 const gchar *title)
2034 {
2035 ClutterStagePrivate *priv;
2036 ClutterStageWindow *impl;
2037
2038 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2039
2040 priv = stage->priv;
2041
2042 g_free (priv->title);
2043 priv->title = g_strdup (title);
2044
2045 impl = CLUTTER_STAGE_WINDOW (priv->impl);
2046 if (CLUTTER_STAGE_WINDOW_GET_IFACE(impl)->set_title != NULL)
2047 CLUTTER_STAGE_WINDOW_GET_IFACE (impl)->set_title (impl, priv->title);
2048
2049 g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_TITLE]);
2050 }
2051
2052 /**
2053 * clutter_stage_get_title:
2054 * @stage: A #ClutterStage
2055 *
2056 * Gets the stage title.
2057 *
2058 * Return value: pointer to the title string for the stage. The
2059 * returned string is owned by the actor and should not
2060 * be modified or freed.
2061 *
2062 * Since: 0.4
2063 **/
2064 const gchar *
clutter_stage_get_title(ClutterStage * stage)2065 clutter_stage_get_title (ClutterStage *stage)
2066 {
2067 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
2068
2069 return stage->priv->title;
2070 }
2071
2072 /**
2073 * clutter_stage_set_key_focus:
2074 * @stage: the #ClutterStage
2075 * @actor: (allow-none): the actor to set key focus to, or %NULL
2076 *
2077 * Sets the key focus on @actor. An actor with key focus will receive
2078 * all the key events. If @actor is %NULL, the stage will receive
2079 * focus.
2080 *
2081 * Since: 0.6
2082 */
2083 void
clutter_stage_set_key_focus(ClutterStage * stage,ClutterActor * actor)2084 clutter_stage_set_key_focus (ClutterStage *stage,
2085 ClutterActor *actor)
2086 {
2087 ClutterStagePrivate *priv;
2088
2089 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2090 g_return_if_fail (actor == NULL || CLUTTER_IS_ACTOR (actor));
2091
2092 priv = stage->priv;
2093
2094 /* normalize the key focus. NULL == stage */
2095 if (actor == CLUTTER_ACTOR (stage))
2096 actor = NULL;
2097
2098 /* avoid emitting signals and notifications if we're setting the same
2099 * actor as the key focus
2100 */
2101 if (priv->key_focused_actor == actor)
2102 return;
2103
2104 if (priv->key_focused_actor != NULL)
2105 {
2106 ClutterActor *old_focused_actor;
2107
2108 old_focused_actor = priv->key_focused_actor;
2109
2110 /* set key_focused_actor to NULL before emitting the signal or someone
2111 * might hide the previously focused actor in the signal handler
2112 */
2113 priv->key_focused_actor = NULL;
2114
2115 _clutter_actor_set_has_key_focus (old_focused_actor, FALSE);
2116 }
2117 else
2118 _clutter_actor_set_has_key_focus (CLUTTER_ACTOR (stage), FALSE);
2119
2120 /* Note, if someone changes key focus in focus-out signal handler we'd be
2121 * overriding the latter call below moving the focus where it was originally
2122 * intended. The order of events would be:
2123 * 1st focus-out, 2nd focus-out (on stage), 2nd focus-in, 1st focus-in
2124 */
2125 if (actor != NULL)
2126 {
2127 priv->key_focused_actor = actor;
2128 _clutter_actor_set_has_key_focus (actor, TRUE);
2129 }
2130 else
2131 _clutter_actor_set_has_key_focus (CLUTTER_ACTOR (stage), TRUE);
2132
2133 g_object_notify_by_pspec (G_OBJECT (stage), obj_props[PROP_KEY_FOCUS]);
2134 }
2135
2136 /**
2137 * clutter_stage_get_key_focus:
2138 * @stage: the #ClutterStage
2139 *
2140 * Retrieves the actor that is currently under key focus.
2141 *
2142 * Return value: (transfer none): the actor with key focus, or the stage
2143 *
2144 * Since: 0.6
2145 */
2146 ClutterActor *
clutter_stage_get_key_focus(ClutterStage * stage)2147 clutter_stage_get_key_focus (ClutterStage *stage)
2148 {
2149 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
2150
2151 if (stage->priv->key_focused_actor)
2152 return stage->priv->key_focused_actor;
2153
2154 return CLUTTER_ACTOR (stage);
2155 }
2156
2157 /*** Perspective boxed type ******/
2158
2159 static gpointer
clutter_perspective_copy(gpointer data)2160 clutter_perspective_copy (gpointer data)
2161 {
2162 if (G_LIKELY (data))
2163 return g_memdup2 (data, sizeof (ClutterPerspective));
2164
2165 return NULL;
2166 }
2167
2168 static void
clutter_perspective_free(gpointer data)2169 clutter_perspective_free (gpointer data)
2170 {
2171 if (G_LIKELY (data))
2172 g_free (data);
2173 }
2174
2175 G_DEFINE_BOXED_TYPE (ClutterPerspective, clutter_perspective,
2176 clutter_perspective_copy,
2177 clutter_perspective_free);
2178
2179 /**
2180 * clutter_stage_ensure_viewport:
2181 * @stage: a #ClutterStage
2182 *
2183 * Ensures that the GL viewport is updated with the current
2184 * stage window size.
2185 *
2186 * This function will queue a redraw of @stage.
2187 *
2188 * This function should not be called by applications; it is used
2189 * when embedding a #ClutterStage into a toolkit with another
2190 * windowing system, like GTK+.
2191 *
2192 * Since: 1.0
2193 */
2194 void
clutter_stage_ensure_viewport(ClutterStage * stage)2195 clutter_stage_ensure_viewport (ClutterStage *stage)
2196 {
2197 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2198
2199 _clutter_stage_dirty_viewport (stage);
2200
2201 clutter_actor_queue_redraw (CLUTTER_ACTOR (stage));
2202 }
2203
2204 # define _DEG_TO_RAD(d) ((d) * ((float) G_PI / 180.0f))
2205
2206 /* This calculates a distance into the view frustum to position the
2207 * stage so there is a decent amount of space to position geometry
2208 * between the stage and the near clipping plane.
2209 *
2210 * Some awkward issues with this problem are:
2211 * - It's not possible to have a gap as large as the stage size with
2212 * a fov > 53° which is basically always the case since the default
2213 * fov is 60°.
2214 * - This can be deduced if you consider that this requires a
2215 * triangle as wide as it is deep to fit in the frustum in front
2216 * of the z_near plane. That triangle will always have an angle
2217 * of 53.13° at the point sitting on the z_near plane, but if the
2218 * frustum has a wider fov angle the left/right clipping planes
2219 * can never converge with the two corners of our triangle no
2220 * matter what size the triangle has.
2221 * - With a fov > 53° there is a trade off between maximizing the gap
2222 * size relative to the stage size but not losing depth precision.
2223 * - Perhaps ideally we wouldn't just consider the fov on the y-axis
2224 * that is usually used to define a perspective, we would consider
2225 * the fov of the axis with the largest stage size so the gap would
2226 * accommodate that size best.
2227 *
2228 * After going around in circles a few times with how to handle these
2229 * issues, we decided in the end to go for the simplest solution to
2230 * start with instead of an elaborate function that handles arbitrary
2231 * fov angles that we currently have no use-case for.
2232 *
2233 * The solution assumes a fovy of 60° and for that case gives a gap
2234 * that's 85% of the stage height. We can consider more elaborate
2235 * functions if necessary later.
2236 *
2237 * One guide we had to steer the gap size we support is the
2238 * interactive test, test-texture-quality which expects to animate an
2239 * actor to +400 on the z axis with a stage size of 640x480. A gap
2240 * that's 85% of the stage height gives a gap of 408 in that case.
2241 */
2242 static float
calculate_z_translation(float z_near)2243 calculate_z_translation (float z_near)
2244 {
2245 /* This solution uses fairly basic trigonometry, but is seems worth
2246 * clarifying the particular geometry we are looking at in-case
2247 * anyone wants to develop this further later. Not sure how well an
2248 * ascii diagram is going to work :-)
2249 *
2250 * |--- stage_height ---|
2251 * | stage line |
2252 * ╲━━━━━━━━━━━━━━━━━━━━━╱------------
2253 * ╲. (2) │ .╱ | |
2254 * C ╲ . │ . ╱ gap| |
2255 * =0.5°╲ . a │ . ╱ | |
2256 * b╲(1). D│ . ╱ | |
2257 * ╲ B.│. ╱near plane | |
2258 * A= ╲━━━━━━━━━╱------------- |
2259 * 120° ╲ c │ ╱ | z_2d
2260 * ╲ │ ╱ z_near |
2261 * left ╲ │ ╱ | |
2262 * clip 60°fovy | |
2263 * plane ╳----------------------
2264 * |
2265 * |
2266 * origin line
2267 *
2268 * The area of interest is the triangle labeled (1) at the top left
2269 * marked with the ... line (a) from where the origin line crosses
2270 * the near plane to the top left where the stage line cross the
2271 * left clip plane.
2272 *
2273 * The sides of the triangle are a, b and c and the corresponding
2274 * angles opposite those sides are A, B and C.
2275 *
2276 * The angle of C is what trades off the gap size we have relative
2277 * to the stage size vs the depth precision we have.
2278 *
2279 * As mentioned above we arove at the angle for C is by working
2280 * backwards from how much space we want for test-texture-quality.
2281 * With a stage_height of 480 we want a gap > 400, ideally we also
2282 * wanted a somewhat round number as a percentage of the height for
2283 * documentation purposes. ~87% or a gap of ~416 is the limit
2284 * because that's where we approach a C angle of 0° and effectively
2285 * loose all depth precision.
2286 *
2287 * So for our test app with a stage_height of 480 if we aim for a
2288 * gap of 408 (85% of 480) we can get the angle D as
2289 * atan (stage_height/2/408) = 30.5°.
2290 *
2291 * That gives us the angle for B as 90° - 30.5° = 59.5°
2292 *
2293 * We can already determine that A has an angle of (fovy/2 + 90°) =
2294 * 120°
2295 *
2296 * Therefore C = 180 - A - B = 0.5°
2297 *
2298 * The length of c = z_near * tan (30°)
2299 *
2300 * Now we can use the rule a/SinA = c/SinC to calculate the
2301 * length of a. After some rearranging that gives us:
2302 *
2303 * a c
2304 * ---------- = ----------
2305 * sin (120°) sin (0.5°)
2306 *
2307 * c * sin (120°)
2308 * a = --------------
2309 * sin (0.5°)
2310 *
2311 * And with that we can determine z_2d = cos (D) * a =
2312 * cos (30.5°) * a + z_near:
2313 *
2314 * c * sin (120°) * cos (30.5°)
2315 * z_2d = --------------------------- + z_near
2316 * sin (0.5°)
2317 */
2318
2319 /* We expect the compiler should boil this down to z_near * CONSTANT
2320 * already, but just in case we use precomputed constants
2321 */
2322 #if 0
2323 # define A tanf (_DEG_TO_RAD (30.f))
2324 # define B sinf (_DEG_TO_RAD (120.f))
2325 # define C cosf (_DEG_TO_RAD (30.5f))
2326 # define D sinf (_DEG_TO_RAD (.5f))
2327 #else
2328 # define A 0.57735025882720947265625f
2329 # define B 0.866025388240814208984375f
2330 # define C 0.86162912845611572265625f
2331 # define D 0.00872653536498546600341796875f
2332 #endif
2333
2334 return z_near
2335 * A * B * C
2336 / D
2337 + z_near;
2338 }
2339
2340 static void
view_2d_in_perspective(graphene_matrix_t * matrix,float fov_y,float aspect,float z_near,float z_2d,float width_2d,float height_2d)2341 view_2d_in_perspective (graphene_matrix_t *matrix,
2342 float fov_y,
2343 float aspect,
2344 float z_near,
2345 float z_2d,
2346 float width_2d,
2347 float height_2d)
2348 {
2349 float top = z_near * tan (fov_y * G_PI / 360.0);
2350 float left = -top * aspect;
2351 float right = top * aspect;
2352 float bottom = -top;
2353
2354 float left_2d_plane = left / z_near * z_2d;
2355 float right_2d_plane = right / z_near * z_2d;
2356 float bottom_2d_plane = bottom / z_near * z_2d;
2357 float top_2d_plane = top / z_near * z_2d;
2358
2359 float width_2d_start = right_2d_plane - left_2d_plane;
2360 float height_2d_start = top_2d_plane - bottom_2d_plane;
2361
2362 /* Factors to scale from framebuffer geometry to frustum
2363 * cross-section geometry. */
2364 float width_scale = width_2d_start / width_2d;
2365 float height_scale = height_2d_start / height_2d;
2366
2367 graphene_matrix_init_scale (matrix, width_scale, -height_scale, width_scale);
2368 graphene_matrix_translate (matrix,
2369 &GRAPHENE_POINT3D_INIT (left_2d_plane,
2370 top_2d_plane,
2371 -z_2d));
2372 }
2373
2374 static void
clutter_stage_update_view_perspective(ClutterStage * stage)2375 clutter_stage_update_view_perspective (ClutterStage *stage)
2376 {
2377 ClutterStagePrivate *priv = stage->priv;
2378 ClutterPerspective perspective;
2379 float z_2d;
2380
2381 perspective = priv->perspective;
2382
2383 perspective.fovy = 60.0; /* 60 Degrees */
2384 perspective.z_near = 1.0;
2385 perspective.aspect = priv->viewport[2] / priv->viewport[3];
2386 z_2d = calculate_z_translation (perspective.z_near);
2387
2388 /* NB: z_2d is only enough room for 85% of the stage_height between
2389 * the stage and the z_near plane. For behind the stage plane we
2390 * want a more consistent gap of 10 times the stage_height before
2391 * hitting the far plane so we calculate that relative to the final
2392 * height of the stage plane at the z_2d_distance we got... */
2393 perspective.z_far = z_2d +
2394 tanf (_DEG_TO_RAD (perspective.fovy / 2.0f)) * z_2d * 20.0f;
2395
2396 clutter_stage_set_perspective (stage, &perspective);
2397
2398 view_2d_in_perspective (&priv->view,
2399 perspective.fovy,
2400 perspective.aspect,
2401 perspective.z_near,
2402 z_2d,
2403 priv->viewport[2],
2404 priv->viewport[3]);
2405
2406 clutter_actor_invalidate_transform (CLUTTER_ACTOR (stage));
2407 }
2408
2409 void
_clutter_stage_maybe_setup_viewport(ClutterStage * stage,ClutterStageView * view)2410 _clutter_stage_maybe_setup_viewport (ClutterStage *stage,
2411 ClutterStageView *view)
2412 {
2413 ClutterStagePrivate *priv = stage->priv;
2414
2415 if (clutter_stage_view_is_dirty_viewport (view))
2416 {
2417 cairo_rectangle_int_t view_layout;
2418 float fb_scale;
2419 float viewport_offset_x;
2420 float viewport_offset_y;
2421 float viewport_x;
2422 float viewport_y;
2423 float viewport_width;
2424 float viewport_height;
2425
2426 CLUTTER_NOTE (PAINT,
2427 "Setting up the viewport { w:%f, h:%f }",
2428 priv->viewport[2],
2429 priv->viewport[3]);
2430
2431 fb_scale = clutter_stage_view_get_scale (view);
2432 clutter_stage_view_get_layout (view, &view_layout);
2433
2434 viewport_offset_x = view_layout.x * fb_scale;
2435 viewport_offset_y = view_layout.y * fb_scale;
2436 viewport_x = roundf (priv->viewport[0] * fb_scale - viewport_offset_x);
2437 viewport_y = roundf (priv->viewport[1] * fb_scale - viewport_offset_y);
2438 viewport_width = roundf (priv->viewport[2] * fb_scale);
2439 viewport_height = roundf (priv->viewport[3] * fb_scale);
2440
2441 clutter_stage_view_set_viewport (view,
2442 viewport_x, viewport_y,
2443 viewport_width, viewport_height);
2444 }
2445
2446 if (clutter_stage_view_is_dirty_projection (view))
2447 clutter_stage_view_set_projection (view, &priv->projection);
2448 }
2449
2450 #undef _DEG_TO_RAD
2451
2452 /**
2453 * clutter_stage_is_redraw_queued_on_view: (skip)
2454 */
2455 gboolean
clutter_stage_is_redraw_queued_on_view(ClutterStage * stage,ClutterStageView * view)2456 clutter_stage_is_redraw_queued_on_view (ClutterStage *stage,
2457 ClutterStageView *view)
2458 {
2459 clutter_stage_maybe_finish_queue_redraws (stage);
2460
2461 return clutter_stage_view_has_redraw_clip (view);
2462 }
2463
2464 void
_clutter_stage_set_window(ClutterStage * stage,ClutterStageWindow * stage_window)2465 _clutter_stage_set_window (ClutterStage *stage,
2466 ClutterStageWindow *stage_window)
2467 {
2468 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2469 g_return_if_fail (CLUTTER_IS_STAGE_WINDOW (stage_window));
2470
2471 if (stage->priv->impl != NULL)
2472 g_object_unref (stage->priv->impl);
2473
2474 stage->priv->impl = stage_window;
2475 }
2476
2477 ClutterStageWindow *
_clutter_stage_get_window(ClutterStage * stage)2478 _clutter_stage_get_window (ClutterStage *stage)
2479 {
2480 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
2481
2482 return CLUTTER_STAGE_WINDOW (stage->priv->impl);
2483 }
2484
2485 ClutterStageWindow *
_clutter_stage_get_default_window(void)2486 _clutter_stage_get_default_window (void)
2487 {
2488 ClutterStageManager *manager = clutter_stage_manager_get_default ();
2489 ClutterStage *stage;
2490
2491 stage = clutter_stage_manager_get_default_stage (manager);
2492 if (stage == NULL)
2493 return NULL;
2494
2495 return _clutter_stage_get_window (stage);
2496 }
2497
2498 /**
2499 * clutter_stage_set_throttle_motion_events:
2500 * @stage: a #ClutterStage
2501 * @throttle: %TRUE to throttle motion events
2502 *
2503 * Sets whether motion events received between redraws should
2504 * be throttled or not. If motion events are throttled, those
2505 * events received by the windowing system between redraws will
2506 * be compressed so that only the last event will be propagated
2507 * to the @stage and its actors.
2508 *
2509 * This function should only be used if you want to have all
2510 * the motion events delivered to your application code.
2511 *
2512 * Since: 1.0
2513 */
2514 void
clutter_stage_set_throttle_motion_events(ClutterStage * stage,gboolean throttle)2515 clutter_stage_set_throttle_motion_events (ClutterStage *stage,
2516 gboolean throttle)
2517 {
2518 ClutterStagePrivate *priv;
2519
2520 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2521
2522 priv = stage->priv;
2523
2524 if (priv->throttle_motion_events != throttle)
2525 priv->throttle_motion_events = throttle;
2526 }
2527
2528 /**
2529 * clutter_stage_get_throttle_motion_events:
2530 * @stage: a #ClutterStage
2531 *
2532 * Retrieves the value set with clutter_stage_set_throttle_motion_events()
2533 *
2534 * Return value: %TRUE if the motion events are being throttled,
2535 * and %FALSE otherwise
2536 *
2537 * Since: 1.0
2538 */
2539 gboolean
clutter_stage_get_throttle_motion_events(ClutterStage * stage)2540 clutter_stage_get_throttle_motion_events (ClutterStage *stage)
2541 {
2542 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
2543
2544 return stage->priv->throttle_motion_events;
2545 }
2546
2547 /**
2548 * clutter_stage_set_minimum_size:
2549 * @stage: a #ClutterStage
2550 * @width: width, in pixels
2551 * @height: height, in pixels
2552 *
2553 * Sets the minimum size for a stage window, if the default backend
2554 * uses #ClutterStage inside a window
2555 *
2556 * This is a convenience function, and it is equivalent to setting the
2557 * #ClutterActor:min-width and #ClutterActor:min-height on @stage
2558 *
2559 * If the current size of @stage is smaller than the minimum size, the
2560 * @stage will be resized to the new @width and @height
2561 *
2562 * Since: 1.2
2563 */
2564 void
clutter_stage_set_minimum_size(ClutterStage * stage,guint width,guint height)2565 clutter_stage_set_minimum_size (ClutterStage *stage,
2566 guint width,
2567 guint height)
2568 {
2569 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2570 g_return_if_fail ((width > 0) && (height > 0));
2571
2572 g_object_set (G_OBJECT (stage),
2573 "min-width", (gfloat) width,
2574 "min-height", (gfloat )height,
2575 NULL);
2576 }
2577
2578 /**
2579 * clutter_stage_get_minimum_size:
2580 * @stage: a #ClutterStage
2581 * @width: (out): return location for the minimum width, in pixels,
2582 * or %NULL
2583 * @height: (out): return location for the minimum height, in pixels,
2584 * or %NULL
2585 *
2586 * Retrieves the minimum size for a stage window as set using
2587 * clutter_stage_set_minimum_size().
2588 *
2589 * The returned size may not correspond to the actual minimum size and
2590 * it is specific to the #ClutterStage implementation inside the
2591 * Clutter backend
2592 *
2593 * Since: 1.2
2594 */
2595 void
clutter_stage_get_minimum_size(ClutterStage * stage,guint * width_p,guint * height_p)2596 clutter_stage_get_minimum_size (ClutterStage *stage,
2597 guint *width_p,
2598 guint *height_p)
2599 {
2600 gfloat width, height;
2601 gboolean width_set, height_set;
2602
2603 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2604
2605 g_object_get (G_OBJECT (stage),
2606 "min-width", &width,
2607 "min-width-set", &width_set,
2608 "min-height", &height,
2609 "min-height-set", &height_set,
2610 NULL);
2611
2612 /* if not width or height have been set, then the Stage
2613 * minimum size is defined to be 1x1
2614 */
2615 if (!width_set)
2616 width = 1;
2617
2618 if (!height_set)
2619 height = 1;
2620
2621 if (width_p)
2622 *width_p = (guint) width;
2623
2624 if (height_p)
2625 *height_p = (guint) height;
2626 }
2627
2628 /**
2629 * clutter_stage_schedule_update:
2630 * @stage: a #ClutterStage actor
2631 *
2632 * Schedules a redraw of the #ClutterStage at the next optimal timestamp.
2633 */
2634 void
clutter_stage_schedule_update(ClutterStage * stage)2635 clutter_stage_schedule_update (ClutterStage *stage)
2636 {
2637 ClutterStageWindow *stage_window;
2638 GList *l;
2639
2640 if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
2641 return;
2642
2643 stage_window = _clutter_stage_get_window (stage);
2644 if (stage_window == NULL)
2645 return;
2646
2647 for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
2648 {
2649 ClutterStageView *view = l->data;
2650
2651 clutter_stage_view_schedule_update (view);
2652 }
2653 }
2654
2655 ClutterPaintVolume *
_clutter_stage_paint_volume_stack_allocate(ClutterStage * stage)2656 _clutter_stage_paint_volume_stack_allocate (ClutterStage *stage)
2657 {
2658 GArray *paint_volume_stack = stage->priv->paint_volume_stack;
2659
2660 g_array_set_size (paint_volume_stack,
2661 paint_volume_stack->len+1);
2662
2663 return &g_array_index (paint_volume_stack,
2664 ClutterPaintVolume,
2665 paint_volume_stack->len - 1);
2666 }
2667
2668 void
_clutter_stage_paint_volume_stack_free_all(ClutterStage * stage)2669 _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage)
2670 {
2671 GArray *paint_volume_stack = stage->priv->paint_volume_stack;
2672 int i;
2673
2674 for (i = 0; i < paint_volume_stack->len; i++)
2675 {
2676 ClutterPaintVolume *pv =
2677 &g_array_index (paint_volume_stack, ClutterPaintVolume, i);
2678 clutter_paint_volume_free (pv);
2679 }
2680
2681 g_array_set_size (paint_volume_stack, 0);
2682 }
2683
2684 /* When an actor queues a redraw we add it to a list on the stage that
2685 * gets processed once all updates to the stage have been finished.
2686 *
2687 * This deferred approach to processing queue_redraw requests means
2688 * that we can avoid redundant transformations of clip volumes if
2689 * something later triggers a full stage redraw anyway. It also means
2690 * we can be more sure that all the referenced actors will have valid
2691 * allocations improving the chance that we can determine the actors
2692 * paint volume so we can clip the redraw request even if the user
2693 * didn't explicitly do so.
2694 */
2695 void
clutter_stage_queue_actor_redraw(ClutterStage * stage,ClutterActor * actor,const ClutterPaintVolume * clip)2696 clutter_stage_queue_actor_redraw (ClutterStage *stage,
2697 ClutterActor *actor,
2698 const ClutterPaintVolume *clip)
2699 {
2700 ClutterStagePrivate *priv = stage->priv;
2701 QueueRedrawEntry *entry = NULL;
2702
2703 CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ",
2704 _clutter_actor_get_debug_name (actor), clip);
2705
2706 if (!priv->pending_finish_queue_redraws)
2707 {
2708 GList *l;
2709
2710 for (l = clutter_stage_peek_stage_views (stage); l; l = l->next)
2711 {
2712 ClutterStageView *view = l->data;
2713
2714 clutter_stage_view_schedule_update (view);
2715 }
2716
2717 priv->pending_finish_queue_redraws = TRUE;
2718 }
2719
2720 entry = g_hash_table_lookup (priv->pending_queue_redraws, actor);
2721
2722 if (entry)
2723 {
2724 /* Ignore all requests to queue a redraw for an actor if a full
2725 * (non-clipped) redraw of the actor has already been queued. */
2726 if (!entry->has_clip)
2727 {
2728 CLUTTER_NOTE (CLIPPING, "Bail from stage_queue_actor_redraw (%s): "
2729 "Unclipped redraw of actor already queued",
2730 _clutter_actor_get_debug_name (actor));
2731 return;
2732 }
2733
2734 /* If queuing a clipped redraw and a clipped redraw has
2735 * previously been queued for this actor then combine the latest
2736 * clip together with the existing clip */
2737 if (clip)
2738 clutter_paint_volume_union (&entry->clip, clip);
2739 else
2740 {
2741 clutter_paint_volume_free (&entry->clip);
2742 entry->has_clip = FALSE;
2743 }
2744 }
2745 else
2746 {
2747 entry = g_new0 (QueueRedrawEntry, 1);
2748
2749 if (clip)
2750 {
2751 entry->has_clip = TRUE;
2752 _clutter_paint_volume_init_static (&entry->clip, actor);
2753 _clutter_paint_volume_set_from_volume (&entry->clip, clip);
2754 }
2755 else
2756 entry->has_clip = FALSE;
2757
2758 g_hash_table_insert (priv->pending_queue_redraws,
2759 g_object_ref (actor), entry);
2760 }
2761 }
2762
2763 static void
free_queue_redraw_entry(QueueRedrawEntry * entry)2764 free_queue_redraw_entry (QueueRedrawEntry *entry)
2765 {
2766 if (entry->has_clip)
2767 clutter_paint_volume_free (&entry->clip);
2768 g_free (entry);
2769 }
2770
2771 void
clutter_stage_dequeue_actor_redraw(ClutterStage * self,ClutterActor * actor)2772 clutter_stage_dequeue_actor_redraw (ClutterStage *self,
2773 ClutterActor *actor)
2774 {
2775 g_hash_table_remove (self->priv->pending_queue_redraws, actor);
2776 }
2777
2778 static void
add_to_stage_clip(ClutterStage * stage,ClutterPaintVolume * redraw_clip)2779 add_to_stage_clip (ClutterStage *stage,
2780 ClutterPaintVolume *redraw_clip)
2781 {
2782 ClutterStageWindow *stage_window;
2783 ClutterActorBox bounding_box;
2784 ClutterActorBox intersection_box;
2785 cairo_rectangle_int_t geom, stage_clip;
2786
2787 if (CLUTTER_ACTOR_IN_DESTRUCTION (CLUTTER_ACTOR (stage)))
2788 return;
2789
2790 stage_window = _clutter_stage_get_window (stage);
2791 if (stage_window == NULL)
2792 return;
2793
2794 if (is_full_stage_redraw_queued (stage))
2795 return;
2796
2797 if (redraw_clip == NULL)
2798 {
2799 clutter_stage_add_redraw_clip (stage, NULL);
2800 return;
2801 }
2802
2803 if (redraw_clip->is_empty)
2804 return;
2805
2806 /* Now transform and project the clip volume to view coordinates and get
2807 * the axis aligned bounding box that's aligned to the pixel grid.
2808 */
2809 _clutter_paint_volume_get_stage_paint_box (redraw_clip,
2810 stage,
2811 &bounding_box);
2812
2813 _clutter_stage_window_get_geometry (stage_window, &geom);
2814
2815 intersection_box.x1 = MAX (bounding_box.x1, 0);
2816 intersection_box.y1 = MAX (bounding_box.y1, 0);
2817 intersection_box.x2 = MIN (bounding_box.x2, geom.width);
2818 intersection_box.y2 = MIN (bounding_box.y2, geom.height);
2819
2820 /* There is no need to track degenerate/empty redraw clips */
2821 if (intersection_box.x2 <= intersection_box.x1 ||
2822 intersection_box.y2 <= intersection_box.y1)
2823 return;
2824
2825 stage_clip.x = intersection_box.x1;
2826 stage_clip.y = intersection_box.y1;
2827 stage_clip.width = intersection_box.x2 - stage_clip.x;
2828 stage_clip.height = intersection_box.y2 - stage_clip.y;
2829
2830 clutter_stage_add_redraw_clip (stage, &stage_clip);
2831 }
2832
2833 void
clutter_stage_maybe_finish_queue_redraws(ClutterStage * stage)2834 clutter_stage_maybe_finish_queue_redraws (ClutterStage *stage)
2835 {
2836 ClutterStagePrivate *priv = stage->priv;
2837 GHashTableIter iter;
2838 gpointer key, value;
2839
2840 COGL_TRACE_BEGIN_SCOPED (ClutterStageFinishQueueRedraws, "FinishQueueRedraws");
2841
2842 if (!priv->pending_finish_queue_redraws)
2843 return;
2844
2845 priv->pending_finish_queue_redraws = FALSE;
2846
2847 g_hash_table_iter_init (&iter, priv->pending_queue_redraws);
2848 while (g_hash_table_iter_next (&iter, &key, &value))
2849 {
2850 ClutterActor *redraw_actor = key;
2851 QueueRedrawEntry *entry = value;
2852
2853 g_hash_table_iter_steal (&iter);
2854
2855 if (clutter_actor_is_mapped (redraw_actor))
2856 {
2857 ClutterPaintVolume old_actor_pv, new_actor_pv;
2858
2859 _clutter_paint_volume_init_static (&old_actor_pv, NULL);
2860 _clutter_paint_volume_init_static (&new_actor_pv, NULL);
2861
2862 if (entry->has_clip)
2863 {
2864 add_to_stage_clip (stage, &entry->clip);
2865 }
2866 else if (clutter_actor_get_redraw_clip (redraw_actor,
2867 &old_actor_pv,
2868 &new_actor_pv))
2869 {
2870 /* Add both the old paint volume of the actor (which is
2871 * currently visible on the screen) and the new paint volume
2872 * (which will be visible on the screen after this redraw)
2873 * to the redraw clip.
2874 * The former we do to ensure the old texture on the screen
2875 * will be fully painted over in case the actor was moved.
2876 */
2877 add_to_stage_clip (stage, &old_actor_pv);
2878 add_to_stage_clip (stage, &new_actor_pv);
2879 }
2880 else
2881 {
2882 /* If there's no clip we can use, we have to trigger an
2883 * unclipped full stage redraw.
2884 */
2885 add_to_stage_clip (stage, NULL);
2886 }
2887 }
2888
2889 g_object_unref (redraw_actor);
2890 free_queue_redraw_entry (entry);
2891
2892 /* get_paint_volume() vfuncs might queue redraws and can cause our
2893 * iterator to now be invalidated. So start over. This isn't wasting
2894 * any time since we already stole (removed) the elements previously
2895 * visited.
2896 */
2897 g_hash_table_iter_init (&iter, priv->pending_queue_redraws);
2898 }
2899 }
2900
2901 /**
2902 * clutter_stage_set_motion_events_enabled:
2903 * @stage: a #ClutterStage
2904 * @enabled: %TRUE to enable the motion events delivery, and %FALSE
2905 * otherwise
2906 *
2907 * Sets whether per-actor motion events (and relative crossing
2908 * events) should be disabled or not.
2909 *
2910 * The default is %TRUE.
2911 *
2912 * If @enable is %FALSE the following signals will not be emitted
2913 * by the actors children of @stage:
2914 *
2915 * - #ClutterActor::motion-event
2916 * - #ClutterActor::enter-event
2917 * - #ClutterActor::leave-event
2918 *
2919 * The events will still be delivered to the #ClutterStage.
2920 *
2921 * The main side effect of this function is that disabling the motion
2922 * events will disable picking to detect the #ClutterActor underneath
2923 * the pointer for each motion event. This is useful, for instance,
2924 * when dragging a #ClutterActor across the @stage: the actor underneath
2925 * the pointer is not going to change, so it's meaningless to perform
2926 * a pick.
2927 *
2928 * Since: 1.8
2929 */
2930 void
clutter_stage_set_motion_events_enabled(ClutterStage * stage,gboolean enabled)2931 clutter_stage_set_motion_events_enabled (ClutterStage *stage,
2932 gboolean enabled)
2933 {
2934 ClutterStagePrivate *priv;
2935
2936 g_return_if_fail (CLUTTER_IS_STAGE (stage));
2937
2938 priv = stage->priv;
2939
2940 enabled = !!enabled;
2941
2942 if (priv->motion_events_enabled != enabled)
2943 priv->motion_events_enabled = enabled;
2944 }
2945
2946 /**
2947 * clutter_stage_get_motion_events_enabled:
2948 * @stage: a #ClutterStage
2949 *
2950 * Retrieves the value set using clutter_stage_set_motion_events_enabled().
2951 *
2952 * Return value: %TRUE if the per-actor motion event delivery is enabled
2953 * and %FALSE otherwise
2954 *
2955 * Since: 1.8
2956 */
2957 gboolean
clutter_stage_get_motion_events_enabled(ClutterStage * stage)2958 clutter_stage_get_motion_events_enabled (ClutterStage *stage)
2959 {
2960 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
2961
2962 return stage->priv->motion_events_enabled;
2963 }
2964
2965 void
_clutter_stage_add_pointer_drag_actor(ClutterStage * stage,ClutterInputDevice * device,ClutterActor * actor)2966 _clutter_stage_add_pointer_drag_actor (ClutterStage *stage,
2967 ClutterInputDevice *device,
2968 ClutterActor *actor)
2969 {
2970 GHashTable *drag_actors;
2971
2972 drag_actors = g_object_get_data (G_OBJECT (stage),
2973 "__clutter_stage_pointer_drag_actors");
2974 if (drag_actors == NULL)
2975 {
2976 drag_actors = g_hash_table_new (NULL, NULL);
2977 g_object_set_data_full (G_OBJECT (stage),
2978 "__clutter_stage_pointer_drag_actors",
2979 drag_actors,
2980 (GDestroyNotify) g_hash_table_destroy);
2981 }
2982
2983 g_hash_table_replace (drag_actors, device, actor);
2984 }
2985
2986 ClutterActor *
_clutter_stage_get_pointer_drag_actor(ClutterStage * stage,ClutterInputDevice * device)2987 _clutter_stage_get_pointer_drag_actor (ClutterStage *stage,
2988 ClutterInputDevice *device)
2989 {
2990 GHashTable *drag_actors;
2991
2992 drag_actors = g_object_get_data (G_OBJECT (stage),
2993 "__clutter_stage_pointer_drag_actors");
2994 if (drag_actors == NULL)
2995 return NULL;
2996
2997 return g_hash_table_lookup (drag_actors, device);
2998 }
2999
3000 void
_clutter_stage_remove_pointer_drag_actor(ClutterStage * stage,ClutterInputDevice * device)3001 _clutter_stage_remove_pointer_drag_actor (ClutterStage *stage,
3002 ClutterInputDevice *device)
3003 {
3004 GHashTable *drag_actors;
3005
3006 drag_actors = g_object_get_data (G_OBJECT (stage),
3007 "__clutter_stage_pointer_drag_actors");
3008 if (drag_actors == NULL)
3009 return;
3010
3011 g_hash_table_remove (drag_actors, device);
3012
3013 if (g_hash_table_size (drag_actors) == 0)
3014 g_object_set_data (G_OBJECT (stage),
3015 "__clutter_stage_pointer_drag_actors",
3016 NULL);
3017 }
3018
3019 void
_clutter_stage_add_touch_drag_actor(ClutterStage * stage,ClutterEventSequence * sequence,ClutterActor * actor)3020 _clutter_stage_add_touch_drag_actor (ClutterStage *stage,
3021 ClutterEventSequence *sequence,
3022 ClutterActor *actor)
3023 {
3024 GHashTable *drag_actors;
3025
3026 drag_actors = g_object_get_data (G_OBJECT (stage),
3027 "__clutter_stage_touch_drag_actors");
3028 if (drag_actors == NULL)
3029 {
3030 drag_actors = g_hash_table_new (NULL, NULL);
3031 g_object_set_data_full (G_OBJECT (stage),
3032 "__clutter_stage_touch_drag_actors",
3033 drag_actors,
3034 (GDestroyNotify) g_hash_table_destroy);
3035 }
3036
3037 g_hash_table_replace (drag_actors, sequence, actor);
3038 }
3039
3040 ClutterActor *
_clutter_stage_get_touch_drag_actor(ClutterStage * stage,ClutterEventSequence * sequence)3041 _clutter_stage_get_touch_drag_actor (ClutterStage *stage,
3042 ClutterEventSequence *sequence)
3043 {
3044 GHashTable *drag_actors;
3045
3046 drag_actors = g_object_get_data (G_OBJECT (stage),
3047 "__clutter_stage_touch_drag_actors");
3048 if (drag_actors == NULL)
3049 return NULL;
3050
3051 return g_hash_table_lookup (drag_actors, sequence);
3052 }
3053
3054 void
_clutter_stage_remove_touch_drag_actor(ClutterStage * stage,ClutterEventSequence * sequence)3055 _clutter_stage_remove_touch_drag_actor (ClutterStage *stage,
3056 ClutterEventSequence *sequence)
3057 {
3058 GHashTable *drag_actors;
3059
3060 drag_actors = g_object_get_data (G_OBJECT (stage),
3061 "__clutter_stage_touch_drag_actors");
3062 if (drag_actors == NULL)
3063 return;
3064
3065 g_hash_table_remove (drag_actors, sequence);
3066
3067 if (g_hash_table_size (drag_actors) == 0)
3068 g_object_set_data (G_OBJECT (stage),
3069 "__clutter_stage_touch_drag_actors",
3070 NULL);
3071 }
3072
3073 int64_t
clutter_stage_get_frame_counter(ClutterStage * stage)3074 clutter_stage_get_frame_counter (ClutterStage *stage)
3075 {
3076 ClutterStageWindow *stage_window;
3077
3078 stage_window = _clutter_stage_get_window (stage);
3079 return _clutter_stage_window_get_frame_counter (stage_window);
3080 }
3081
3082 void
clutter_stage_presented(ClutterStage * stage,ClutterStageView * view,ClutterFrameInfo * frame_info)3083 clutter_stage_presented (ClutterStage *stage,
3084 ClutterStageView *view,
3085 ClutterFrameInfo *frame_info)
3086 {
3087 g_signal_emit (stage, stage_signals[PRESENTED], 0, view, frame_info);
3088 }
3089
3090 /**
3091 * clutter_stage_get_capture_final_size:
3092 * @stage: a #ClutterStage actor
3093 * @rect: a #cairo_rectangle_int_t
3094 * @out_width: (out) (optional): the final width
3095 * @out_height: (out) (optional): the final height
3096 * @out_scale: (out) (optional): the final scale factor
3097 *
3098 * Get the size of the framebuffer one must pass to
3099 * clutter_stage_paint_to_buffer() or clutter_stage_paint_to_framebuffer()
3100 * would be used with the same @rect.
3101 *
3102 * Returns: %TRUE if the size has been retrieved, %FALSE otherwise.
3103 */
3104 gboolean
clutter_stage_get_capture_final_size(ClutterStage * stage,cairo_rectangle_int_t * rect,int * out_width,int * out_height,float * out_scale)3105 clutter_stage_get_capture_final_size (ClutterStage *stage,
3106 cairo_rectangle_int_t *rect,
3107 int *out_width,
3108 int *out_height,
3109 float *out_scale)
3110 {
3111 float max_scale = 1.0;
3112
3113 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE);
3114
3115 if (rect)
3116 {
3117 graphene_rect_t capture_rect;
3118 g_autoptr (GList) views = NULL;
3119 GList *l;
3120
3121 _clutter_util_rect_from_rectangle (rect, &capture_rect);
3122 views = clutter_stage_get_views_for_rect (stage, &capture_rect);
3123
3124 if (!views)
3125 return FALSE;
3126
3127 for (l = views; l; l = l->next)
3128 {
3129 ClutterStageView *view = l->data;
3130
3131 max_scale = MAX (clutter_stage_view_get_scale (view), max_scale);
3132 }
3133
3134 if (out_width)
3135 *out_width = (gint) roundf (rect->width * max_scale);
3136
3137 if (out_height)
3138 *out_height = (gint) roundf (rect->height * max_scale);
3139 }
3140 else
3141 {
3142 ClutterActorBox alloc;
3143 float stage_width, stage_height;
3144
3145 clutter_actor_get_allocation_box (CLUTTER_ACTOR (stage), &alloc);
3146 clutter_actor_box_get_size (&alloc, &stage_width, &stage_height);
3147 max_scale = clutter_actor_get_real_resource_scale (CLUTTER_ACTOR (stage));
3148
3149 if (out_width)
3150 *out_width = (gint) roundf (stage_width * max_scale);
3151
3152 if (out_height)
3153 *out_height = (gint) roundf (stage_height * max_scale);
3154 }
3155
3156 if (out_scale)
3157 *out_scale = max_scale;
3158
3159 return TRUE;
3160 }
3161
3162 void
clutter_stage_paint_to_framebuffer(ClutterStage * stage,CoglFramebuffer * framebuffer,const cairo_rectangle_int_t * rect,float scale,ClutterPaintFlag paint_flags)3163 clutter_stage_paint_to_framebuffer (ClutterStage *stage,
3164 CoglFramebuffer *framebuffer,
3165 const cairo_rectangle_int_t *rect,
3166 float scale,
3167 ClutterPaintFlag paint_flags)
3168 {
3169 ClutterStagePrivate *priv = stage->priv;
3170 ClutterPaintContext *paint_context;
3171 cairo_region_t *redraw_clip;
3172
3173 if (paint_flags & CLUTTER_PAINT_FLAG_CLEAR)
3174 {
3175 CoglColor clear_color;
3176
3177 cogl_color_init_from_4ub (&clear_color, 0, 0, 0, 0);
3178 cogl_framebuffer_clear (framebuffer, COGL_BUFFER_BIT_COLOR, &clear_color);
3179 }
3180
3181 redraw_clip = cairo_region_create_rectangle (rect);
3182 paint_context =
3183 clutter_paint_context_new_for_framebuffer (framebuffer,
3184 redraw_clip,
3185 paint_flags);
3186 cairo_region_destroy (redraw_clip);
3187
3188 cogl_framebuffer_push_matrix (framebuffer);
3189 cogl_framebuffer_set_projection_matrix (framebuffer, &priv->projection);
3190 cogl_framebuffer_set_viewport (framebuffer,
3191 -(rect->x * scale),
3192 -(rect->y * scale),
3193 priv->viewport[2] * scale,
3194 priv->viewport[3] * scale);
3195 clutter_actor_paint (CLUTTER_ACTOR (stage), paint_context);
3196 cogl_framebuffer_pop_matrix (framebuffer);
3197
3198 clutter_paint_context_destroy (paint_context);
3199 }
3200
3201 /**
3202 * clutter_stage_paint_to_buffer:
3203 * @stage: a #ClutterStage actor
3204 * @rect: a #cairo_rectangle_int_t
3205 * @scale: the scale
3206 * @data: (array) (element-type guint8): a pointer to the data
3207 * @stride: stride of the image surface
3208 * @format: the pixel format
3209 * @paint_flags: the #ClutterPaintFlag
3210 * @error: the error
3211 *
3212 * Take a snapshot of the stage to a provided buffer.
3213 *
3214 * Returns: %TRUE is the buffer has been paint successfully, %FALSE otherwise.
3215 */
3216 gboolean
clutter_stage_paint_to_buffer(ClutterStage * stage,const cairo_rectangle_int_t * rect,float scale,uint8_t * data,int stride,CoglPixelFormat format,ClutterPaintFlag paint_flags,GError ** error)3217 clutter_stage_paint_to_buffer (ClutterStage *stage,
3218 const cairo_rectangle_int_t *rect,
3219 float scale,
3220 uint8_t *data,
3221 int stride,
3222 CoglPixelFormat format,
3223 ClutterPaintFlag paint_flags,
3224 GError **error)
3225 {
3226 ClutterBackend *clutter_backend = clutter_get_default_backend ();
3227 CoglContext *cogl_context =
3228 clutter_backend_get_cogl_context (clutter_backend);
3229 int texture_width, texture_height;
3230 CoglTexture2D *texture;
3231 CoglOffscreen *offscreen;
3232 CoglFramebuffer *framebuffer;
3233 CoglBitmap *bitmap;
3234
3235 texture_width = (int) roundf (rect->width * scale);
3236 texture_height = (int) roundf (rect->height * scale);
3237 texture = cogl_texture_2d_new_with_size (cogl_context,
3238 texture_width,
3239 texture_height);
3240 if (!texture)
3241 {
3242 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3243 "Failed to create %dx%d texture",
3244 texture_width, texture_height);
3245 return FALSE;
3246 }
3247
3248 offscreen = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
3249 framebuffer = COGL_FRAMEBUFFER (offscreen);
3250
3251 cogl_object_unref (texture);
3252
3253 if (!cogl_framebuffer_allocate (framebuffer, error))
3254 return FALSE;
3255
3256 clutter_stage_paint_to_framebuffer (stage, framebuffer,
3257 rect, scale, paint_flags);
3258
3259 bitmap = cogl_bitmap_new_for_data (cogl_context,
3260 texture_width, texture_height,
3261 format,
3262 stride,
3263 data);
3264
3265 cogl_framebuffer_read_pixels_into_bitmap (framebuffer,
3266 0, 0,
3267 COGL_READ_PIXELS_COLOR_BUFFER,
3268 bitmap);
3269
3270 cogl_object_unref (bitmap);
3271 g_object_unref (framebuffer);
3272
3273 return TRUE;
3274 }
3275
3276 /**
3277 * clutter_stage_paint_to_content:
3278 * @stage: a #ClutterStage actor
3279 * @rect: a #cairo_rectangle_int_t
3280 * @scale: the scale
3281 * @paint_flags: the #ClutterPaintFlag
3282 * @error: the error
3283 *
3284 * Take a snapshot of the stage to a #ClutterContent.
3285 *
3286 * Returns: (transfer full): the #ClutterContent or %NULL on error.
3287 */
3288 ClutterContent *
clutter_stage_paint_to_content(ClutterStage * stage,const cairo_rectangle_int_t * rect,float scale,ClutterPaintFlag paint_flags,GError ** error)3289 clutter_stage_paint_to_content (ClutterStage *stage,
3290 const cairo_rectangle_int_t *rect,
3291 float scale,
3292 ClutterPaintFlag paint_flags,
3293 GError **error)
3294 {
3295 ClutterBackend *clutter_backend = clutter_get_default_backend ();
3296 CoglContext *cogl_context =
3297 clutter_backend_get_cogl_context (clutter_backend);
3298 int texture_width, texture_height;
3299 CoglTexture2D *texture;
3300 CoglOffscreen *offscreen;
3301 g_autoptr (CoglFramebuffer) framebuffer = NULL;
3302
3303 texture_width = (int) roundf (rect->width * scale);
3304 texture_height = (int) roundf (rect->height * scale);
3305 texture = cogl_texture_2d_new_with_size (cogl_context,
3306 texture_width,
3307 texture_height);
3308 if (!texture)
3309 {
3310 g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
3311 "Failed to create %dx%d texture",
3312 texture_width, texture_height);
3313 return NULL;
3314 }
3315
3316 offscreen = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
3317 framebuffer = COGL_FRAMEBUFFER (offscreen);
3318
3319 cogl_object_unref (texture);
3320
3321 if (!cogl_framebuffer_allocate (framebuffer, error))
3322 return NULL;
3323
3324 clutter_stage_paint_to_framebuffer (stage, framebuffer,
3325 rect, scale, paint_flags);
3326
3327 return clutter_texture_content_new_from_texture (cogl_offscreen_get_texture (offscreen),
3328 NULL);
3329 }
3330
3331 void
clutter_stage_capture_view_into(ClutterStage * stage,ClutterStageView * view,cairo_rectangle_int_t * rect,uint8_t * data,int stride)3332 clutter_stage_capture_view_into (ClutterStage *stage,
3333 ClutterStageView *view,
3334 cairo_rectangle_int_t *rect,
3335 uint8_t *data,
3336 int stride)
3337 {
3338 CoglFramebuffer *framebuffer;
3339 ClutterBackend *backend;
3340 CoglContext *context;
3341 CoglBitmap *bitmap;
3342 cairo_rectangle_int_t view_layout;
3343 float view_scale;
3344 float texture_width;
3345 float texture_height;
3346
3347 g_return_if_fail (CLUTTER_IS_STAGE (stage));
3348
3349 framebuffer = clutter_stage_view_get_framebuffer (view);
3350
3351 clutter_stage_view_get_layout (view, &view_layout);
3352
3353 if (!rect)
3354 rect = &view_layout;
3355
3356 view_scale = clutter_stage_view_get_scale (view);
3357 texture_width = roundf (rect->width * view_scale);
3358 texture_height = roundf (rect->height * view_scale);
3359
3360 backend = clutter_get_default_backend ();
3361 context = clutter_backend_get_cogl_context (backend);
3362 bitmap = cogl_bitmap_new_for_data (context,
3363 texture_width, texture_height,
3364 CLUTTER_CAIRO_FORMAT_ARGB32,
3365 stride,
3366 data);
3367
3368 cogl_framebuffer_read_pixels_into_bitmap (framebuffer,
3369 roundf ((rect->x - view_layout.x) * view_scale),
3370 roundf ((rect->y - view_layout.y) * view_scale),
3371 COGL_READ_PIXELS_COLOR_BUFFER,
3372 bitmap);
3373
3374 cogl_object_unref (bitmap);
3375 }
3376
3377 /**
3378 * clutter_stage_peek_stage_views: (skip)
3379 */
3380 GList *
clutter_stage_peek_stage_views(ClutterStage * stage)3381 clutter_stage_peek_stage_views (ClutterStage *stage)
3382 {
3383 ClutterStagePrivate *priv = stage->priv;
3384
3385 return _clutter_stage_window_get_views (priv->impl);
3386 }
3387
3388 void
clutter_stage_clear_stage_views(ClutterStage * stage)3389 clutter_stage_clear_stage_views (ClutterStage *stage)
3390 {
3391 clutter_actor_clear_stage_views_recursive (CLUTTER_ACTOR (stage));
3392 }
3393
3394 GList *
clutter_stage_get_views_for_rect(ClutterStage * stage,const graphene_rect_t * rect)3395 clutter_stage_get_views_for_rect (ClutterStage *stage,
3396 const graphene_rect_t *rect)
3397 {
3398 ClutterStagePrivate *priv = stage->priv;
3399 GList *views_for_rect = NULL;
3400 GList *l;
3401
3402 for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next)
3403 {
3404 ClutterStageView *view = l->data;
3405 cairo_rectangle_int_t view_layout;
3406 graphene_rect_t view_rect;
3407
3408 clutter_stage_view_get_layout (view, &view_layout);
3409 _clutter_util_rect_from_rectangle (&view_layout, &view_rect);
3410
3411 if (graphene_rect_intersection (&view_rect, rect, NULL))
3412 views_for_rect = g_list_prepend (views_for_rect, view);
3413 }
3414
3415 return views_for_rect;
3416 }
3417
3418 void
clutter_stage_set_actor_needs_immediate_relayout(ClutterStage * stage)3419 clutter_stage_set_actor_needs_immediate_relayout (ClutterStage *stage)
3420 {
3421 ClutterStagePrivate *priv = stage->priv;
3422
3423 priv->actor_needs_immediate_relayout = TRUE;
3424 }
3425
3426 static void
on_device_actor_reactive_changed(ClutterActor * actor,GParamSpec * pspec,PointerDeviceEntry * entry)3427 on_device_actor_reactive_changed (ClutterActor *actor,
3428 GParamSpec *pspec,
3429 PointerDeviceEntry *entry)
3430 {
3431 ClutterStage *self = entry->stage;
3432 ClutterActor *new_device_actor;
3433
3434 g_assert (!clutter_actor_get_reactive (actor));
3435
3436 new_device_actor =
3437 _clutter_stage_do_pick (self,
3438 entry->coords.x,
3439 entry->coords.y,
3440 CLUTTER_PICK_REACTIVE);
3441
3442 clutter_stage_update_device (self,
3443 entry->device, entry->sequence,
3444 entry->coords,
3445 CLUTTER_CURRENT_TIME,
3446 new_device_actor,
3447 TRUE);
3448 }
3449
3450 static void
on_device_actor_destroyed(ClutterActor * actor,PointerDeviceEntry * entry)3451 on_device_actor_destroyed (ClutterActor *actor,
3452 PointerDeviceEntry *entry)
3453 {
3454 /* Simply unset the current_actor pointer here, there's no need to
3455 * unset has_pointer or to disconnect any signals because the actor
3456 * is gone anyway.
3457 * Also, as soon as the next repaint happens, a repick should be triggered
3458 * and the PointerDeviceEntry will get updated again, so no need to
3459 * trigger a repick here.
3460 */
3461 entry->current_actor = NULL;
3462 }
3463
3464 static void
free_pointer_device_entry(PointerDeviceEntry * entry)3465 free_pointer_device_entry (PointerDeviceEntry *entry)
3466 {
3467 if (entry->current_actor)
3468 {
3469 ClutterActor *actor = entry->current_actor;
3470
3471 g_signal_handlers_disconnect_by_func (actor,
3472 G_CALLBACK (on_device_actor_reactive_changed),
3473 entry);
3474 g_signal_handlers_disconnect_by_func (actor,
3475 G_CALLBACK (on_device_actor_destroyed),
3476 entry);
3477
3478 _clutter_actor_set_has_pointer (actor, FALSE);
3479 }
3480
3481 g_free (entry);
3482 }
3483
3484 void
clutter_stage_update_device_entry(ClutterStage * self,ClutterInputDevice * device,ClutterEventSequence * sequence,graphene_point_t coords,ClutterActor * actor)3485 clutter_stage_update_device_entry (ClutterStage *self,
3486 ClutterInputDevice *device,
3487 ClutterEventSequence *sequence,
3488 graphene_point_t coords,
3489 ClutterActor *actor)
3490 {
3491 ClutterStagePrivate *priv = self->priv;
3492 PointerDeviceEntry *entry = NULL;
3493
3494 g_assert (device != NULL);
3495
3496 if (sequence != NULL)
3497 entry = g_hash_table_lookup (priv->touch_sequences, sequence);
3498 else
3499 entry = g_hash_table_lookup (priv->pointer_devices, device);
3500
3501 if (!entry)
3502 {
3503 entry = g_new0 (PointerDeviceEntry, 1);
3504
3505 if (sequence != NULL)
3506 g_hash_table_insert (priv->touch_sequences, sequence, entry);
3507 else
3508 g_hash_table_insert (priv->pointer_devices, device, entry);
3509
3510 entry->stage = self;
3511 entry->device = device;
3512 entry->sequence = sequence;
3513 }
3514
3515 entry->coords = coords;
3516
3517 if (entry->current_actor != actor)
3518 {
3519 if (entry->current_actor)
3520 {
3521 ClutterActor *old_actor = entry->current_actor;
3522
3523 g_signal_handlers_disconnect_by_func (old_actor,
3524 G_CALLBACK (on_device_actor_reactive_changed),
3525 entry);
3526 g_signal_handlers_disconnect_by_func (old_actor,
3527 G_CALLBACK (on_device_actor_destroyed),
3528 entry);
3529
3530 _clutter_actor_set_has_pointer (old_actor, FALSE);
3531 }
3532
3533 entry->current_actor = actor;
3534
3535 if (actor)
3536 {
3537 g_signal_connect (actor, "notify::reactive",
3538 G_CALLBACK (on_device_actor_reactive_changed), entry);
3539 g_signal_connect (actor, "destroy",
3540 G_CALLBACK (on_device_actor_destroyed), entry);
3541
3542 _clutter_actor_set_has_pointer (actor, TRUE);
3543 }
3544 }
3545 }
3546
3547 void
clutter_stage_remove_device_entry(ClutterStage * self,ClutterInputDevice * device,ClutterEventSequence * sequence)3548 clutter_stage_remove_device_entry (ClutterStage *self,
3549 ClutterInputDevice *device,
3550 ClutterEventSequence *sequence)
3551 {
3552 ClutterStagePrivate *priv = self->priv;
3553 gboolean removed;
3554
3555 g_assert (device != NULL);
3556
3557 if (sequence != NULL)
3558 removed = g_hash_table_remove (priv->touch_sequences, sequence);
3559 else
3560 removed = g_hash_table_remove (priv->pointer_devices, device);
3561
3562 g_assert (removed);
3563 }
3564
3565 /**
3566 * clutter_stage_get_device_actor:
3567 * @stage: a #ClutterStage
3568 * @device: a #ClutterInputDevice
3569 * @sequence: (allow-none): an optional #ClutterEventSequence
3570 *
3571 * Retrieves the #ClutterActor underneath the pointer or touch point
3572 * of @device and @sequence.
3573 *
3574 * Return value: (transfer none): a pointer to the #ClutterActor or %NULL
3575 */
3576 ClutterActor *
clutter_stage_get_device_actor(ClutterStage * stage,ClutterInputDevice * device,ClutterEventSequence * sequence)3577 clutter_stage_get_device_actor (ClutterStage *stage,
3578 ClutterInputDevice *device,
3579 ClutterEventSequence *sequence)
3580 {
3581 ClutterStagePrivate *priv = stage->priv;
3582 PointerDeviceEntry *entry = NULL;
3583
3584 g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);
3585 g_return_val_if_fail (device != NULL, NULL);
3586
3587 if (sequence != NULL)
3588 entry = g_hash_table_lookup (priv->touch_sequences, sequence);
3589 else
3590 entry = g_hash_table_lookup (priv->pointer_devices, device);
3591
3592 if (entry)
3593 return entry->current_actor;
3594
3595 return NULL;
3596 }
3597
3598 /**
3599 * clutter_stage_get_device_coords: (skip):
3600 */
3601 void
clutter_stage_get_device_coords(ClutterStage * stage,ClutterInputDevice * device,ClutterEventSequence * sequence,graphene_point_t * coords)3602 clutter_stage_get_device_coords (ClutterStage *stage,
3603 ClutterInputDevice *device,
3604 ClutterEventSequence *sequence,
3605 graphene_point_t *coords)
3606 {
3607 ClutterStagePrivate *priv = stage->priv;
3608 PointerDeviceEntry *entry = NULL;
3609
3610 g_return_if_fail (CLUTTER_IS_STAGE (stage));
3611 g_return_if_fail (device != NULL);
3612
3613 if (sequence != NULL)
3614 entry = g_hash_table_lookup (priv->touch_sequences, sequence);
3615 else
3616 entry = g_hash_table_lookup (priv->pointer_devices, device);
3617
3618 if (entry && coords)
3619 *coords = entry->coords;
3620 }
3621