diff --git a/docs/reference/gtk/Makefile.am b/docs/reference/gtk/Makefile.am index 74520fc2bf..091f06d7b7 100644 --- a/docs/reference/gtk/Makefile.am +++ b/docs/reference/gtk/Makefile.am @@ -328,6 +328,8 @@ HTML_IMAGES = \ $(srcdir)/images/layout-tbrl.png \ $(srcdir)/images/window-default.png \ $(srcdir)/images/hello-world.png \ + $(srcdir)/images/grid-packing.png \ + $(srcdir)/images/drawing.png \ $(srcdir)/images/switch.png \ $(srcdir)/images/linear.png \ $(srcdir)/images/ease.png \ diff --git a/docs/reference/gtk/getting_started.xml b/docs/reference/gtk/getting_started.xml index aebc5ee42f..d47a09f1fd 100644 --- a/docs/reference/gtk/getting_started.xml +++ b/docs/reference/gtk/getting_started.xml @@ -147,4 +147,41 @@ +
+ Drawing + + Many widgets, like buttons, do all their drawing themselves. You + just tell them the label you want to see, and they figure out what font + to use, draw the button outline and focus rectangle, etc. Sometimes, it + is necessary to do some custom drawing. In that case, a #GtkDrawingArea + might be the right widget to use. It offers a canvas on which you can + draw by connecting to the #GtkWidget::draw signal. + + + The contents of a widget often need to be partially or fully redrawn, + e.g. when another window is moved and uncovers part of the widget, or + when tie window containing it is resized. It is also possible to explicitly + cause part or all of the widget to be redrawn, by calling + gtk_widget_queue_draw() or its variants. GTK+ takes care of most of the + details by providing a ready-to-use cairo context to the ::draw signal + handler. + + The following example shows a ::draw signal handler. It is a bit + more complicated than the previous examples, since it also demonstrates + input event handling by means of ::button-press and ::motion-notify + handlers. + + + + + + + Drawing in response to input + + + FIXME: MISSING XINCLUDE CONTENT + + + +
diff --git a/docs/reference/gtk/images/drawing.png b/docs/reference/gtk/images/drawing.png new file mode 100644 index 0000000000..a5105002ef Binary files /dev/null and b/docs/reference/gtk/images/drawing.png differ diff --git a/examples/Makefile.am b/examples/Makefile.am index f4d06bfd41..948d58a213 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -48,4 +48,9 @@ LDADD = \ $(top_builddir)/gtk/libgtk-3.0.la \ $(GTK_DEP_LIBS) -noinst_PROGRAMS = hello-world window-default bloatpad grid-packing +noinst_PROGRAMS = \ + hello-world \ + window-default \ + bloatpad \ + grid-packing \ + drawing diff --git a/examples/drawing.c b/examples/drawing.c new file mode 100644 index 0000000000..28f291b8b8 --- /dev/null +++ b/examples/drawing.c @@ -0,0 +1,200 @@ +#include + +/* Surface to store current scribbles */ +static cairo_surface_t *surface = NULL; + +static void +clear_surface (void) +{ + cairo_t *cr; + + cr = cairo_create (surface); + + cairo_set_source_rgb (cr, 1, 1, 1); + cairo_paint (cr); + + cairo_destroy (cr); +} + +/* Create a new surface of the appropriate size to store our scribbles */ +static gboolean +configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event, + gpointer data) +{ + if (surface) + cairo_surface_destroy (surface); + + surface = gdk_window_create_similar_surface (gtk_widget_get_window (widget), + CAIRO_CONTENT_COLOR, + gtk_widget_get_allocated_width (widget), + gtk_widget_get_allocated_height (widget)); + + /* Initialize the surface to white */ + clear_surface (); + + /* We've handled the configure event, no need for further processing. */ + return TRUE; +} + +/* Redraw the screen from the surface. Note that the ::draw + * signal receives a ready-to-be-used cairo_t that is already + * clipped to only draw the exposed areas of the widget + */ +static gboolean +draw_cb (GtkWidget *widget, + cairo_t *cr, + gpointer data) +{ + cairo_set_source_surface (cr, surface, 0, 0); + cairo_paint (cr); + + return FALSE; +} + +/* Draw a rectangle on the surface at the given position */ +static void +draw_brush (GtkWidget *widget, + gdouble x, + gdouble y) +{ + cairo_t *cr; + + /* Paint to the surface, where we store our state */ + cr = cairo_create (surface); + + cairo_rectangle (cr, x - 3, y - 3, 6, 6); + cairo_fill (cr); + + cairo_destroy (cr); + + /* Now invalidate the affected region of the drawing area. */ + gtk_widget_queue_draw_area (widget, x - 3, y - 3, 6, 6); +} + +/* Handle button press events by either drawing a rectangle + * or clearing the surface, depending on which button was pressed. + * The ::button-press signal handler receives a GdkEventButton + * struct which contains this information. + */ +static gboolean +button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + gpointer data) +{ + /* paranoia check, in case we haven't gotten a configure event */ + if (surface == NULL) + return FALSE; + + if (event->button == 1) + { + draw_brush (widget, event->x, event->y); + } + else if (event->button == 3) + { + clear_surface (); + gtk_widget_queue_draw (widget); + } + + /* We've handled the event, stop processing */ + return TRUE; +} + +/* Handle motion events by continuing to draw if button 1 is + * still held down. The ::motion-notify signal handler receives + * a GdkEventMotion struct which contains this information. + */ +static gboolean +motion_notify_event_cb (GtkWidget *widget, + GdkEventMotion *event, + gpointer data) +{ + int x, y; + GdkModifierType state; + + /* paranoia check, in case we haven't gotten a configure event */ + if (surface == NULL) + return FALSE; + + /* This call is very important; it requests the next motion event. + * If you don't call gdk_window_get_pointer() you'll only get + * a single motion event. The reason is that we specified + * GDK_POINTER_MOTION_HINT_MASK to gtk_widget_set_events(). + * If we hadn't specified that, we could just use event->x, event->y + * as the pointer location. But we'd also get deluged in events. + * By requesting the next event as we handle the current one, + * we avoid getting a huge number of events faster than we + * can cope. + */ + gdk_window_get_pointer (event->window, &x, &y, &state); + + if (state & GDK_BUTTON1_MASK) + draw_brush (widget, x, y); + + /* We've handled it, stop processing */ + return TRUE; +} + +static void +close_window (void) +{ + if (surface) + cairo_surface_destroy (surface); + + gtk_main_quit (); +} + +int +main (int argc, + char *argv[]) +{ + GtkWidget *window; + GtkWidget *frame; + GtkWidget *da; + + gtk_init (&argc, &argv); + + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_title (GTK_WINDOW (window), "Drawing Area"); + + g_signal_connect (window, "destroy", G_CALLBACK (close_window), NULL); + + gtk_container_set_border_width (GTK_CONTAINER (window), 8); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (window), frame); + + da = gtk_drawing_area_new (); + /* set a minimum size */ + gtk_widget_set_size_request (da, 100, 100); + + gtk_container_add (GTK_CONTAINER (frame), da); + + /* Signals used to handle the backing surface */ + g_signal_connect (da, "draw", + G_CALLBACK (draw_cb), NULL); + g_signal_connect (da,"configure-event", + G_CALLBACK (configure_event_cb), NULL); + + /* Event signals */ + g_signal_connect (da, "motion-notify-event", + G_CALLBACK (motion_notify_event_cb), NULL); + g_signal_connect (da, "button-press-event", + G_CALLBACK (button_press_event_cb), NULL); + + /* Ask to receive events the drawing area doesn't normally + * subscribe to. In particular, we need to ask for the + * button press and motion notify events that want to handle. + */ + gtk_widget_set_events (da, gtk_widget_get_events (da) + | GDK_BUTTON_PRESS_MASK + | GDK_POINTER_MOTION_MASK + | GDK_POINTER_MOTION_HINT_MASK); + + gtk_widget_show_all (window); + + gtk_main (); + + return 0; +}