[grits] Add mouse enter/leave signals to objects



commit 1d0635977583ad84faaa1978f1fd78fa3ec83052
Author: Andy Spencer <andy753421 gmail com>
Date:   Sat Oct 15 06:13:39 2011 +0000

    Add mouse enter/leave signals to objects
    
    This uses the OpenGL Selection render mode to determine which objects
    the mouse is over. It requires fairly tight integration between
    GritsOpenGL and GritsObject.
    
    The signal code is handled internally by GritsObject. However, most of
    the actual selection matching work is done by GritsOpenGL.
    
    Object types that wish to improve performance can implement the pick()
    function in addition to the draw() function. The pick function is used
    when performing selection matching. It works similar to draw, but does
    not need to do textures/lighting/opacity/etc.

 src/grits-opengl.c         |   83 +++++++++++++++++++++++++++++---
 src/objects/grits-object.c |  112 ++++++++++++++++++++++++++++++++++++++-----
 src/objects/grits-object.h |   14 ++++++
 src/objects/grits-poly.c   |   13 +++++
 4 files changed, 201 insertions(+), 21 deletions(-)
---
diff --git a/src/grits-opengl.c b/src/grits-opengl.c
index 9c3af08..423c849 100644
--- a/src/grits-opengl.c
+++ b/src/grits-opengl.c
@@ -46,6 +46,14 @@
 /* http://research.microsoft.com/pubs/70307/tr-2006-81.pdf */
 /* http://www.opengl.org/wiki/Alpha_Blending */
 
+/* The unsorted/sroted GLists are blank head nodes,
+ * This way us we can remove objects from the level just by fixing up links
+ * I.e. we don't need to do a lookup to remove an object if we have its GList */
+struct RenderLevel {
+	GList unsorted;
+	GList sorted;
+};
+
 /***********
  * Helpers *
  ***********/
@@ -116,18 +124,27 @@ static void _set_visuals(GritsOpenGL *opengl)
 	g_mutex_unlock(opengl->sphere_lock);
 }
 
+static gboolean _foreach_object_cb(gpointer key, gpointer value, gpointer pointers)
+{
+	struct RenderLevel *level = value;
+	GFunc    user_func = ((gpointer*)pointers)[0];
+	gpointer user_data = ((gpointer*)pointers)[1];
+	for (GList *cur = level->unsorted.next; cur; cur = cur->next)
+		user_func(cur->data, user_data);
+	for (GList *cur = level->sorted.next;   cur; cur = cur->next)
+		user_func(cur->data, user_data);
+	return FALSE;
+}
+
+static void _foreach_object(GritsOpenGL *opengl, GFunc func, gpointer user_data)
+{
+	gpointer pointers[2] = {func, user_data};
+	g_tree_foreach(opengl->objects, _foreach_object_cb, pointers);
+}
 
 /*************
  * Callbacks *
  *************/
-/* The unsorted/sroted GLists are blank head nodes,
- * This way us we can remove objects from the level just by fixing up links
- * I.e. we don't need to do a lookup to remove an object if we have its GList */
-struct RenderLevel {
-	GList unsorted;
-	GList sorted;
-};
-
 static gboolean on_configure(GritsOpenGL *opengl, GdkEventConfigure *event, gpointer _)
 {
 	g_debug("GritsOpenGL: on_configure");
@@ -212,6 +229,55 @@ static gboolean on_expose(GritsOpenGL *opengl, GdkEventExpose *event, gpointer _
 	return FALSE;
 }
 
+static gboolean on_motion_notify(GritsOpenGL *opengl, GdkEventMotion *event, gpointer _)
+{
+	gdouble height = GTK_WIDGET(opengl)->allocation.height;
+	gdouble gl_x   = event->x;
+	gdouble gl_y   = height - event->y;
+
+	/* Configure view */
+	gint viewport[4];
+	gdouble projection[16];
+	glGetIntegerv(GL_VIEWPORT, viewport);
+	glGetDoublev(GL_PROJECTION_MATRIX, projection);
+
+	glMatrixMode(GL_PROJECTION);
+	glPushMatrix();
+	glLoadIdentity();
+	gluPickMatrix(gl_x, gl_y, 2, 2, viewport);
+	glMultMatrixd(projection);
+
+	/* Prepare for picking */
+	guint buffer[100][4] = {};
+	glSelectBuffer(G_N_ELEMENTS(buffer), (guint*)buffer);
+	glRenderMode(GL_SELECT);
+	glInitNames();
+
+	/* Run picking */
+	g_mutex_lock(opengl->objects_lock);
+	_foreach_object(opengl, (GFunc)grits_object_pick_begin, opengl);
+	int hits = glRenderMode(GL_RENDER);
+	g_debug("GritsOpenGL: on_motion_notify - hits=%d ev=%.0lf,%.0lf",
+			hits, gl_x, gl_y);
+	for (int i = 0; i < hits; i++) {
+		//g_debug("\tHit: %d",     i);
+		//g_debug("\t\tcount: %d", buffer[i][0]);
+		//g_debug("\t\tz1:    %f", (float)buffer[i][1]/0x7fffffff);
+		//g_debug("\t\tz2:    %f", (float)buffer[i][2]/0x7fffffff);
+		//g_debug("\t\tname:  %p", (gpointer)buffer[i][3]);
+		GritsObject *object = GRITS_OBJECT(buffer[i][3]);
+		grits_object_pick_pointer(object, gl_x, gl_y);
+	}
+	_foreach_object(opengl, (GFunc)grits_object_pick_end, NULL);
+	g_mutex_unlock(opengl->objects_lock);
+
+	/* Cleanup */
+	glMatrixMode(GL_PROJECTION);
+	glPopMatrix();
+
+	return FALSE;
+}
+
 static gboolean on_key_press(GritsOpenGL *opengl, GdkEventKey *event, gpointer _)
 {
 	g_debug("GritsOpenGL: on_key_press - key=%x, state=%x, plus=%x",
@@ -282,6 +348,7 @@ static void on_realize(GritsOpenGL *opengl, gpointer _)
 	g_signal_connect(opengl, "location-changed", G_CALLBACK(on_view_changed), NULL);
 	g_signal_connect(opengl, "rotation-changed", G_CALLBACK(on_view_changed), NULL);
 
+	g_signal_connect(opengl, "motion-notify-event", G_CALLBACK(on_motion_notify), NULL);
 #ifndef ROAM_DEBUG
 	opengl->sm_source[0] = g_timeout_add_full(G_PRIORITY_HIGH_IDLE+30, 33,  (GSourceFunc)on_idle, opengl, NULL);
 	opengl->sm_source[1] = g_timeout_add_full(G_PRIORITY_HIGH_IDLE+10, 500, (GSourceFunc)on_idle, opengl, NULL);
diff --git a/src/objects/grits-object.c b/src/objects/grits-object.c
index 3cfd802..6abc4eb 100644
--- a/src/objects/grits-object.c
+++ b/src/objects/grits-object.c
@@ -34,18 +34,15 @@
 
 #include "grits-object.h"
 
-
-/**
- * grits_object_draw:
- * @object: the object
- * @opengl: the viewer the object is being displayed in
- *
- * Perform any OpenGL commands necessasairy to draw the object.
- *
- * The GL_PROJECTION and GL_MODELVIEW matricies and GL_ALL_ATTRIB_BITS will be
- * restored to the default state after the call to draw.
- */
-void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
+/* Constants */
+enum {
+	SIG_ENTER,
+	SIG_LEAVE,
+	NUM_SIGNALS,
+};
+static guint signals[NUM_SIGNALS];
+
+void grits_object_pickdraw(GritsObject *object, GritsOpenGL *opengl, gboolean pick)
 {
 	GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
 	if (!klass->draw) {
@@ -111,7 +108,10 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
 				object->center.lon,
 				object->center.elev);
 
-	klass->draw(object, opengl);
+	if (pick && klass->pick)
+		klass->pick(object, opengl);
+	else
+		klass->draw(object, opengl);
 
 	if (!(object->skip & GRITS_SKIP_STATE)) {
 		glPopAttrib();
@@ -121,6 +121,21 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
 	g_mutex_unlock(opengl->sphere_lock);
 }
 
+/**
+ * grits_object_draw:
+ * @object: the object
+ * @opengl: the viewer the object is being displayed in
+ *
+ * Perform any OpenGL commands necessasairy to draw the object.
+ *
+ * The GL_PROJECTION and GL_MODELVIEW matricies and GL_ALL_ATTRIB_BITS will be
+ * restored to the default state after the call to draw.
+ */
+void grits_object_draw(GritsObject *object, GritsOpenGL *opengl)
+{
+	grits_object_pickdraw(object, opengl, FALSE);
+}
+
 void grits_object_hide(GritsObject *object, gboolean hidden)
 {
 	GritsObjectClass *klass = GRITS_OBJECT_GET_CLASS(object);
@@ -135,6 +150,41 @@ void grits_object_queue_draw(GritsObject *object)
 		gtk_widget_queue_draw(GTK_WIDGET(object->viewer));
 }
 
+/* Event handling */
+void grits_object_pick_begin(GritsObject *object, GritsOpenGL *opengl)
+{
+	object->state.picked = FALSE;
+
+	/* Check for connected signals */
+	for (int i = 0; i < NUM_SIGNALS; i++) {
+		if (g_signal_has_handler_pending(object, signals[i], 0, FALSE)) {
+			/* Someone is watching, render the object _once_ */
+			glPushName((guint)object);
+			grits_object_pickdraw(object, opengl, TRUE);
+			glPopName();
+			return;
+		}
+	}
+}
+
+void grits_object_pick_pointer(GritsObject *object, double x, double y)
+{
+	object->state.picked = TRUE;
+}
+
+void grits_object_pick_end(GritsObject *object)
+{
+	if (object->state.picked) {
+		if (!object->state.selected)
+			g_signal_emit(object, signals[SIG_ENTER], 0);
+		object->state.selected = TRUE;
+	} else {
+		if (object->state.selected)
+			g_signal_emit(object, signals[SIG_LEAVE], 0);
+		object->state.selected = FALSE;
+	}
+}
+
 /* GObject stuff */
 G_DEFINE_ABSTRACT_TYPE(GritsObject, grits_object, G_TYPE_OBJECT);
 static void grits_object_init(GritsObject *object)
@@ -146,4 +196,40 @@ static void grits_object_init(GritsObject *object)
 
 static void grits_object_class_init(GritsObjectClass *klass)
 {
+	GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+
+	/**
+	 * GritsObject::enter:
+	 * @object: the object.
+	 *
+	 * The ::enter signal is emitted when the pointer moves over the object
+	 */
+	signals[SIG_ENTER] = g_signal_new(
+			"enter",
+			G_TYPE_FROM_CLASS(gobject_class),
+			G_SIGNAL_RUN_LAST,
+			0,
+			NULL,
+			NULL,
+			g_cclosure_marshal_VOID__VOID,
+			G_TYPE_NONE,
+			0);
+
+	/**
+	 * GritsViewer::leave:
+	 * @object: the object.
+	 *
+	 * The ::leave signal is emitted when the pointer moves away from the
+	 * object
+	 */
+	signals[SIG_LEAVE] = g_signal_new(
+			"leave",
+			G_TYPE_FROM_CLASS(gobject_class),
+			G_SIGNAL_RUN_LAST,
+			0,
+			NULL,
+			NULL,
+			g_cclosure_marshal_VOID__VOID,
+			G_TYPE_NONE,
+			0);
 }
diff --git a/src/objects/grits-object.h b/src/objects/grits-object.h
index de77ac5..d7cd2b0 100644
--- a/src/objects/grits-object.h
+++ b/src/objects/grits-object.h
@@ -36,6 +36,12 @@
 #define GRITS_SKIP_CENTER  (1<<2)
 #define GRITS_SKIP_STATE   (1<<3)
 
+/* Picking states */
+typedef struct {
+	guint picked   : 1;
+	guint selected : 1;
+} GritsState;
+
 typedef struct _GritsObject      GritsObject;
 typedef struct _GritsObjectClass GritsObjectClass;
 
@@ -48,6 +54,8 @@ struct _GritsObject {
 	gboolean     hidden; // If true, the object will not be drawn
 	gdouble      lod;    // Level of detail, used to hide small objects
 	guint32      skip;   // Bit mask of safe operations
+
+	GritsState   state;  // Internal, used for picking
 };
 
 struct _GritsObjectClass {
@@ -55,6 +63,7 @@ struct _GritsObjectClass {
 
 	/* Move some of these to GObject? */
 	void (*draw) (GritsObject *object, GritsOpenGL *opengl);
+	void (*pick) (GritsObject *object, GritsOpenGL *opengl);
 	void (*hide) (GritsObject *object, gboolean hidden);
 };
 
@@ -65,6 +74,11 @@ void grits_object_draw(GritsObject *object, GritsOpenGL *opengl);
 
 void grits_object_hide(GritsObject *object, gboolean hidden);
 
+/* Interal, used by grits_opengl */
+void grits_object_pick_begin(GritsObject *object, GritsOpenGL *opengl);
+void grits_object_pick_pointer(GritsObject *object, double x, double y);
+void grits_object_pick_end(GritsObject *object);
+
 /**
  * grits_object_queue_draw:
  * @object: The #GritsObject that needs drawing
diff --git a/src/objects/grits-poly.c b/src/objects/grits-poly.c
index 55d492a..da35b36 100644
--- a/src/objects/grits-poly.c
+++ b/src/objects/grits-poly.c
@@ -110,6 +110,18 @@ static void grits_poly_draw(GritsObject *_poly, GritsOpenGL *opengl)
 	glPopAttrib();
 }
 
+static void grits_poly_pick(GritsObject *_poly, GritsOpenGL *opengl)
+{
+	//g_debug("GritsPoly: pick");
+	GritsPoly *poly = GRITS_POLY(_poly);
+	if (!poly->list)
+		return;
+	glPushAttrib(GL_ENABLE_BIT);
+	glDisable(GL_CULL_FACE);
+	glCallList(poly->list+0);
+	glPopAttrib();
+}
+
 /**
  * grits_poly_new:
  * @points:  TODO
@@ -198,4 +210,5 @@ static void grits_poly_class_init(GritsPolyClass *klass)
 
 	GritsObjectClass *object_class = GRITS_OBJECT_CLASS(klass);
 	object_class->draw = grits_poly_draw;
+	object_class->pick = grits_poly_pick;
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]