gtk: Add a GL drawing widget

https://bugzilla.gnome.org/show_bug.cgi?id=119189
This commit is contained in:
Emmanuele Bassi
2014-08-11 11:34:05 +01:00
parent a5da2eff7d
commit ffcd7983aa
7 changed files with 625 additions and 0 deletions

View File

@@ -365,6 +365,7 @@ gtk_public_h_sources = \
gtkgesturesingle.h \
gtkgestureswipe.h \
gtkgesturezoom.h \
gtkglarea.h \
gtkgrid.h \
gtkheaderbar.h \
gtkicontheme.h \
@@ -937,6 +938,7 @@ gtk_base_c_sources = \
gtkgesturesingle.c \
gtkgestureswipe.c \
gtkgesturezoom.c \
gtkglarea.c \
gtkgrid.c \
gtkheaderbar.c \
gtkhsla.c \

View File

@@ -114,6 +114,7 @@
#include <gtk/gtkgesturesingle.h>
#include <gtk/gtkgestureswipe.h>
#include <gtk/gtkgesturezoom.h>
#include <gtk/gtkglarea.h>
#include <gtk/gtkgrid.h>
#include <gtk/gtkheaderbar.h>
#include <gtk/gtkicontheme.h>

483
gtk/gtkglarea.c Normal file
View File

@@ -0,0 +1,483 @@
#include "config.h"
#include "config.h"
#include "gtkglarea.h"
#include "gtkintl.h"
#include "gtkstylecontext.h"
#include "gtkmarshalers.h"
#include "gtkprivate.h"
/**
* SECTION:gtkglarea
* @Title: GtkGLArea
* @Short_description: A widget for custom drawing with OpenGL
*
* #GtkGLArea is a widget that allows drawing with OpenGL.
*
* #GtkGLArea can set up its own #GdkGLContext using a provided
* #GdkGLPixelFormat, or can use a given #GdkGLContext.
*
* In order to draw, you have to connect to the #GtkGLArea::draw-with-context
* signal, or subclass #GtkGLArea and override the @GtkGLAreaClass.draw_with_context
* virtual function.
*
* The #GtkGLArea widget ensures that the #GdkGLContext is associated with the
* widget's drawing area, and it is kept updated when the size and position of
* the drawing area changes.
*/
typedef struct {
GdkGLPixelFormat *pixel_format;
GdkGLContext *context;
} GtkGLAreaPrivate;
enum {
PROP_0,
PROP_PIXEL_FORMAT,
PROP_CONTEXT,
LAST_PROP
};
static GParamSpec *obj_props[LAST_PROP] = { NULL, };
enum {
RENDER,
CREATE_CONTEXT,
LAST_SIGNAL
};
static guint area_signals[LAST_SIGNAL] = { 0, };
G_DEFINE_TYPE_WITH_PRIVATE (GtkGLArea, gtk_gl_area, GTK_TYPE_WIDGET)
static void
gtk_gl_area_dispose (GObject *gobject)
{
GtkGLArea *self = GTK_GL_AREA (gobject);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
g_clear_object (&priv->pixel_format);
g_clear_object (&priv->context);
G_OBJECT_CLASS (gtk_gl_area_parent_class)->dispose (gobject);
}
static void
gtk_gl_area_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
GtkGLArea *self = GTK_GL_AREA (gobject);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
switch (prop_id)
{
case PROP_PIXEL_FORMAT:
priv->pixel_format = g_value_dup_object (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static void
gtk_gl_area_get_property (GObject *gobject,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (GTK_GL_AREA (gobject));
switch (prop_id)
{
case PROP_PIXEL_FORMAT:
g_value_set_object (value, priv->pixel_format);
break;
case PROP_CONTEXT:
g_value_set_object (value, priv->context);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
}
}
static GdkGLContext *
gtk_gl_area_create_context (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
GdkGLContext *context;
if (priv->context != NULL)
return priv->context;
context = NULL;
g_signal_emit (area, area_signals[CREATE_CONTEXT], 0,
priv->pixel_format,
&context);
if (context == NULL)
{
g_critical ("No GL context was created for the widget");
return NULL;
}
gtk_widget_set_visual (GTK_WIDGET (area), gdk_gl_context_get_visual (context));
priv->context = context;
return priv->context;
}
static void
gtk_gl_area_realize (GtkWidget *widget)
{
GdkGLContext *context;
GtkAllocation allocation;
GdkWindow *window;
GdkWindowAttr attributes;
gint attributes_mask;
gtk_widget_set_realized (widget, TRUE);
gtk_widget_get_allocation (widget, &allocation);
context = gtk_gl_area_create_context (GTK_GL_AREA (widget));
attributes.window_type = GDK_WINDOW_CHILD;
attributes.x = allocation.x;
attributes.y = allocation.y;
attributes.width = allocation.width;
attributes.height = allocation.height;
attributes.wclass = GDK_INPUT_OUTPUT;
attributes.visual = gtk_widget_get_visual (widget);
attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL;
window = gdk_window_new (gtk_widget_get_parent_window (widget),
&attributes,
attributes_mask);
gtk_widget_register_window (widget, window);
gtk_widget_set_window (widget, window);
if (context != NULL)
{
gdk_gl_context_set_window (context, gtk_widget_get_window (widget));
gdk_gl_context_update (context);
}
}
static void
gtk_gl_area_unrealize (GtkWidget *widget)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private ((GtkGLArea *) widget);
if (priv->context != NULL)
gdk_gl_context_set_window (priv->context, NULL);
GTK_WIDGET_CLASS (gtk_gl_area_parent_class)->unrealize (widget);
}
static void
gtk_gl_area_size_allocate (GtkWidget *widget,
GtkAllocation *allocation)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private ((GtkGLArea *) widget);
GtkAllocation old_alloc;
gboolean same_size;
gtk_widget_get_allocation (widget, &old_alloc);
gtk_widget_set_allocation (widget, allocation);
if (!gtk_widget_get_realized (widget))
return;
same_size = old_alloc.width == allocation->width &&
old_alloc.height == allocation->height;
gdk_window_move_resize (gtk_widget_get_window (widget),
allocation->x,
allocation->y,
allocation->width,
allocation->height);
/* no need to reset the viewport if the size of the window did not change */
if (priv->context != NULL && !same_size)
gdk_gl_context_update (priv->context);
}
static gboolean
gtk_gl_area_draw (GtkWidget *widget,
cairo_t *cr)
{
GtkGLArea *self = GTK_GL_AREA (widget);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
gboolean unused;
if (priv->context == NULL)
return FALSE;
if (!gtk_gl_area_make_current (self))
return FALSE;
g_signal_emit (self, area_signals[RENDER], 0, priv->context, &unused);
gtk_gl_area_flush_buffer (self);
return TRUE;
}
static void
gtk_gl_area_screen_changed (GtkWidget *widget,
GdkScreen *old_screen)
{
GtkGLArea *self = GTK_GL_AREA (widget);
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (self);
/* this will cause the context to be recreated on realize */
g_clear_object (&priv->context);
}
static GdkGLContext *
gtk_gl_area_real_create_context (GtkGLArea *area,
GdkGLPixelFormat *format)
{
GdkDisplay *display = gtk_widget_get_display (GTK_WIDGET (area));
GdkGLContext *retval;
GError *error = NULL;
if (display == NULL)
display = gdk_display_get_default ();
retval = gdk_display_get_gl_context (display, format, NULL, &error);
if (error != NULL)
{
g_critical ("Unable to create a GdkGLContext: %s", error->message);
g_error_free (error);
return NULL;
}
return retval;
}
static gboolean
create_context_accumulator (GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer data)
{
g_value_copy (handler_return, return_accu);
/* stop after the first handler returning a valid object */
return g_value_get_object (handler_return) == NULL;
}
static void
gtk_gl_area_class_init (GtkGLAreaClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
klass->create_context = gtk_gl_area_real_create_context;
widget_class->screen_changed = gtk_gl_area_screen_changed;
widget_class->realize = gtk_gl_area_realize;
widget_class->unrealize = gtk_gl_area_unrealize;
widget_class->size_allocate = gtk_gl_area_size_allocate;
widget_class->draw = gtk_gl_area_draw;
gtk_widget_class_set_accessible_role (widget_class, ATK_ROLE_DRAWING_AREA);
obj_props[PROP_PIXEL_FORMAT] =
g_param_spec_object ("pixel-format",
P_("Pixel Format"),
P_("The GDK pixel format for creating the GL context"),
GDK_TYPE_GL_PIXEL_FORMAT,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
obj_props[PROP_CONTEXT] =
g_param_spec_object ("context",
P_("Context"),
P_("The GL context"),
GDK_TYPE_GL_CONTEXT,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS);
gobject_class->set_property = gtk_gl_area_set_property;
gobject_class->get_property = gtk_gl_area_get_property;
gobject_class->dispose = gtk_gl_area_dispose;
g_object_class_install_properties (gobject_class, LAST_PROP, obj_props);
area_signals[CREATE_CONTEXT] =
g_signal_new (I_("create-context"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkGLAreaClass, create_context),
create_context_accumulator, NULL,
_gtk_marshal_OBJECT__OBJECT,
GDK_TYPE_GL_CONTEXT, 1,
GDK_TYPE_GL_PIXEL_FORMAT);
/**
* GtkGLArea::render:
* @area: the #GtkGLArea that emitted the signal
* @context: the #GdkGLContext used by @area
*
* The ::render signal is emitted every time the contents
* of the #GtkGLArea should be redrawn.
*
* The @context is bound to the @area prior to emitting this function,
* and the buffers are flushed once the emission terminates.
*
* Returns: %TRUE to stop other handlers from being invoked for the event.
* %FALSE to propagate the event further.
*
* Since: 3.14
*/
area_signals[RENDER] =
g_signal_new (I_("render"),
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (GtkGLAreaClass, render),
_gtk_boolean_handled_accumulator, NULL,
NULL,
G_TYPE_BOOLEAN, 1,
GDK_TYPE_GL_CONTEXT);
}
static void
gtk_gl_area_init (GtkGLArea *self)
{
gtk_widget_set_has_window (GTK_WIDGET (self), TRUE);
}
/**
* gtk_gl_area_new:
* @pixel_format: a #GdkGLPixelFormat
*
* Creates a new #GtkGLArea widget using the given @pixel_format to
* configure a #GdkGLContext.
*
* Returns: (transfer full): the newly created #GtkGLArea
*
* Since: 3.14
*/
GtkWidget *
gtk_gl_area_new (GdkGLPixelFormat *pixel_format)
{
g_return_val_if_fail (GDK_IS_GL_PIXEL_FORMAT (pixel_format), NULL);
return g_object_new (GTK_TYPE_GL_AREA,
"pixel-format", pixel_format,
NULL);
}
/**
* gtk_gl_area_get_pixel_format:
* @area: a #GtkGLArea
*
* Retrieves the #GdkGLPixelFormat used by @area.
*
* Returns: (transfer none): the #GdkGLPixelFormat
*
* Since: 3.14
*/
GdkGLPixelFormat *
gtk_gl_area_get_pixel_format (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);
return priv->pixel_format;
}
/**
* gtk_gl_area_get_context:
* @area: a #GtkGLArea
*
* Retrieves the #GdkGLContext used by @area.
*
* Returns: (transfer none): the #GdkGLContext
*
* Since: 3.14
*/
GdkGLContext *
gtk_gl_area_get_context (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
g_return_val_if_fail (GTK_IS_GL_AREA (area), NULL);
return priv->context;
}
/**
* gtk_gl_area_make_current:
* @area: a #GtkGLArea
*
* Ensures that the #GdkGLContext used by @area is associated with
* the #GtkGLArea.
*
* This function is automatically called before emitting the
* #GtkGLArea::draw-with-context signal, and should not be called
* by application code.
*
* Returns: %TRUE if the context was associated successfully with
* the widget
*
* Since: 3.14
*/
gboolean
gtk_gl_area_make_current (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
GtkWidget *widget;
g_return_val_if_fail (GTK_IS_GL_AREA (area), FALSE);
widget = GTK_WIDGET (area);
g_return_val_if_fail (gtk_widget_get_realized (widget), FALSE);
if (priv->context == NULL)
return FALSE;
gdk_gl_context_set_window (priv->context, gtk_widget_get_window (widget));
return gdk_gl_context_make_current (priv->context);
}
/**
* gtk_gl_area_flush_buffer:
* @area: a #GtkGLArea
*
* Flushes the buffer associated with @area.
*
* This function is automatically called after emitting
* the #GtkGLArea::draw-with-context signal, and should not
* be called by application code.
*
* Since: 3.14
*/
void
gtk_gl_area_flush_buffer (GtkGLArea *area)
{
GtkGLAreaPrivate *priv = gtk_gl_area_get_instance_private (area);
g_return_if_fail (GTK_IS_GL_AREA (area));
g_return_if_fail (gtk_widget_get_realized (GTK_WIDGET (area)));
if (priv->context == NULL)
return;
gdk_gl_context_flush_buffer (priv->context);
}

80
gtk/gtkglarea.h Normal file
View File

@@ -0,0 +1,80 @@
/* GTK - The GIMP Toolkit
*
* gtkglarea.h: A GL drawing area
*
* Copyright © 2014 Emmanuele Bassi
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __GTK_GL_AREA_H__
#define __GTK_GL_AREA_H__
#if !defined (__GTK_H_INSIDE__) && !defined (GTK_COMPILATION)
#error "Only <gtk/gtk.h> can be included directly."
#endif
#include <gtk/gtkwidget.h>
G_BEGIN_DECLS
#define GTK_TYPE_GL_AREA (gtk_gl_area_get_type ())
#define GTK_GL_AREA(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_TYPE_GL_AREA, GtkGLArea))
#define GTK_IS_GL_AREA(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GTK_TYPE_GL_AREA))
#define GTK_GL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_GL_AREA, GtkGLAreaClass))
#define GTK_IS_GL_AREA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_GL_AREA))
#define GTK_GL_AREA_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_GL_AREA, GtkGLAreaClass))
typedef struct _GtkGLArea GtkGLArea;
typedef struct _GtkGLAreaClass GtkGLAreaClass;
struct _GtkGLArea
{
/*< private >*/
GtkWidget parent_instance;
};
struct _GtkGLAreaClass
{
/*< private >*/
GtkWidgetClass parent_class;
/*< public >*/
GdkGLContext * (* create_context) (GtkGLArea *area,
GdkGLPixelFormat *format);
gboolean (* render) (GtkGLArea *area,
GdkGLContext *context);
/*< private >*/
gpointer _padding[6];
};
GDK_AVAILABLE_IN_3_14
GType gtk_gl_area_get_type (void) G_GNUC_CONST;
GDK_AVAILABLE_IN_3_14
GtkWidget * gtk_gl_area_new (GdkGLPixelFormat *format);
GDK_AVAILABLE_IN_3_14
GdkGLContext * gtk_gl_area_get_context (GtkGLArea *area);
GDK_AVAILABLE_IN_3_14
gboolean gtk_gl_area_make_current (GtkGLArea *area);
GDK_AVAILABLE_IN_3_14
void gtk_gl_area_flush_buffer (GtkGLArea *area);
G_END_DECLS
#endif /* __GTK_GL_AREA_H__ */

View File

@@ -124,6 +124,7 @@ VOID:UINT,STRING,UINT
VOID:UINT,UINT
VOID:VOID
OBJECT:OBJECT,INT,INT
OBJECT:OBJECT
VOID:POINTER,POINTER,POINTER,POINTER,STRING
VOID:OBJECT,STRING,POINTER,POINTER
INT:INT

View File

@@ -66,6 +66,7 @@ noinst_PROGRAMS = $(TEST_PROGS) \
testfullscreen \
testgeometry \
testgiconpixbuf \
testglarea \
testgrid \
testgtk \
testheaderbar \

57
tests/testglarea.c Normal file
View File

@@ -0,0 +1,57 @@
#include <stdlib.h>
#include <gtk/gtk.h>
#include <epoxy/gl.h>
static gboolean
render (GtkGLArea *area,
GdkGLContext *context)
{
glClearColor (0, 0, 0, 0);
glClear (GL_COLOR_BUFFER_BIT);
glColor3f (1.0f, 0.85f, 0.35f);
glBegin (GL_TRIANGLES);
{
glVertex3f ( 0.0, 0.6, 0.0);
glVertex3f (-0.2, -0.3, 0.0);
glVertex3f ( 0.2, -0.3, 0.0);
}
glEnd ();
glFlush ();
return TRUE;
}
int
main (int argc, char *argv[])
{
GtkWidget *window, *gl_area;
GdkGLPixelFormat *pixel_format;
gtk_init (&argc, &argv);
/* create a new pixel format; we use this to configure the
* GL context, and to check for features
*/
pixel_format = gdk_gl_pixel_format_new ("double-buffer", TRUE, NULL);
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
gtk_window_set_title (GTK_WINDOW (window), "GtkGLArea - Golden Triangle");
gtk_window_set_default_size (GTK_WINDOW (window), 400, 400);
gtk_container_set_border_width (GTK_CONTAINER (window), 12);
g_signal_connect (window, "destroy", G_CALLBACK (gtk_main_quit), NULL);
gtk_widget_show (window);
gl_area = gtk_gl_area_new (pixel_format);
gtk_container_add (GTK_CONTAINER (window), gl_area);
gtk_widget_show (gl_area);
g_object_unref (pixel_format);
g_signal_connect (gl_area, "render", G_CALLBACK (render), NULL);
gtk_main ();
return EXIT_SUCCESS;
}