From 251f60910f19a71919696d5a36ae8b2938e1b3ab Mon Sep 17 00:00:00 2001
From: Toshio Sekiya `z*Dg%2jTsv6Q(^BGD`*gp`j|fG7tb`fB
z&gNpCRy_%hoQ*Uy9HG8mTtieLNd*}*AAIg=zSoYX2{^di_T%Q<2a-hjGX4W`X?b}D
zmpG4;zAHdIq*xOgcJ!`G3A%FS3NpKSTOg5W0X{N~Wnqz`IW09!>oj}T>Gb?$>X7&l
z`0#p7=(hFi*M~2USBprzWiM*{JZHap@-w%}zc8lvGE$Y}N9=AyHHyJfS0|O_Mz>WB
zJ9siKFTZxVcwymOZS;6cnlmaZx|VX*JKoeWV;@DbV-5btVwb&u3$*A4z>V42*(aQw
z!O|Xw*A^hsS4HFz(kVpK$W}GmMr7gC This section and following sections are not updated yet and
-the programs were checked on the older GTK version than 4.10. They will
-be updated in the near future. If you want to draw dynamically on the screen, like an image window
-of gimp graphics editor, the GtkDrawingArea widget is the most suitable
-widget. You can freely draw or redraw an image in this widget. This is
-called custom drawing. GtkDrawingArea provides a cairo drawing context so users can draw
-images by using cairo functions. In this section, I will explain: If you want to draw shapes or paint images dynamically on the screen,
+use the GtkDrawingArea widget. GtkDrawingArea provides a cairo drawing context. You can draw images
+with cairo library functions. This section describes: Cairo is a set of two dimensional graphical drawing functions (or
-graphics library). There are a lot of documents on Cairo’s website. If you aren’t
-familiar with Cairo, it is worth reading the tutorial. The following is an introduction to the Cairo library and how to use
-it. First, you need to know about surfaces, sources, masks,
-destinations, cairo context and transformations. Cairo is a drawing library for two dimensional graphics. There are a
+lot of documents on Cairo’s
+website. If you aren’t familiar with Cairo, it is worth reading the
+tutorial. The following is an introduction to the Cairo library. First, you
+need to know surfaces, sources, masks, destinations, cairo context and
+transformations. To compile this, change your current directory to
s See the Cairo’s website
for further information.r6*bA||CjM{cuOiAmVh&-`TecVV)T%6PH
zz)M#?yN$qBl+>E{su)Yi>wk4xz9CYz`=Zl)pN!v7LHivKej(VU`FVj>W7^_Grh9i8
zAzE|R#XiTnVsSDrGcOM@-4hZ5YQW#$ABD$X+?Q8|I#T3mTzs_QU0!8s+I+tf6SKvx
zgv%6q94?>EaLi)>YC7^6ri-)fmGr78xsD>16V9Xaf{jxUwl)dT*xP$7xt_lGu}X^$
z!~)9Dq`dA4RRC3?KM68{t*{f_b`9o}MZ285Wzrk6QhB`4fq?%D{T4Ad*jOYf17gLC85WLrR8ABfQwB
zJr{jLLyf9=3ClUMcAZIKCzngH&od5ebtSh8542uVCLoX-N;z5%prZn`OdKXBFJ4lP
z+m6ePpMSw4i!Gl1_?Vnv#p)9u?WSNaq`twVPgr;Kf%PUwnCygzDjHf^sE^75_YnGS
zFbWAZb<3IGn=sxh48y+k(Ac)^NMfS=Sxz9xA3%afVaI$BdtIKrFlnyGL{C^p>!}?n
z*J}<H5t;CL5$$T1HK=BoYmH
zpYLszf=z%45s#0Xrnss%?O>sCxb;at0yTz%269~hD$WsSXJ?phJSm0O+&?&9^`#3C
z*rlZ<*kP-ol|ec_EiJ`&3u>9hwd<6jV<$m801RflMo0*M?#_3l0lOQ(3W|)1$_Gtd
zCZx!e01uoKYYPOu0VxGnO0cs~+qTVuZ@(VYjr#g}FfU3^)~4Z1^;clW&VC0{HgxDU
z*Z^RGF9TH`h|F?Ev?;kA+z)RHii!d|{EY7mcY-NEZ6`z-i}cv*cft@T<;`Fh+wv!H
z3iN~PWpL`S$8i!|@ZF#VK|kxmeLpZA4ldM=9eKn-fS+GJBZp1qB(6$nmSA>Ct~j8#
z(24Yq-~{A%JK0SHt;^xTzcz**e2vCG66{}yMvagJA*euZcy`c7gA#5CYvB)&F82C=
zN#Ni5AM18`pcw&W21l<~NIB&9m5=|B;{O5yUsL%vb^P}W-REp@-d*<)oLxz^zG<_P
zdo&INR`<`?Ufq?h_V=psPjM-C)Oblw;u0+WbgV*yoS1N7{=;18eAdv{%JEf-{#7aJ
zG3Bj2AMzU$jW(}R6q4u{e!m7pU;V<>b?4P}`;yxkt&ov<+kZAZ|3uy2b4Onr|2L)F
zr8iC)N_x`e-v&za&pOE8ahSdtW=ZBJYPnR&4(O%(Prs)k|4S&o4DM%_*Oe*Rf#~WR
zpN~^s1D$uu{5`d)tvH#@ZQ6o0w}w6zGdF#;s$bmy|2My3CufwQp3H<1N{aa(ES&uY
z@T8~V2H1y?@BJUp
GtkDrawingArea and Cairo
-
-
Cairo
-
cairo_set_source_rgb
creates a source pattern,
-which in this case is a solid white paint. The second to fourth
-arguments are red, green and blue color values respectively, and they
-are of type float. The values are between zero (0.0) and one (1.0), with
-black being given by (0.0,0.0,0.0) and white by (1.0,1.0,1.0).cairo_paint
copies everywhere in the source to
destination. The destination is filled with white pixels with this
command.cairo_set_line_width
set the width of lines. In
+cairo_set_line_width
sets the width of lines. In
this case, the line width is set to be two pixels and will end up that
same size. (It is because the transformation is identity. If the
transformation isn’t identity, for example scaling with the factor
three, the actual width in destination will be six (2x3=6) pixels.)cairo_stroke
transfer the source to destination
+cairo_stroke
transfers the source to destination
through the rectangle in the mask.rectangle.png
.rectangle.png
.src/misc
and type the following.
-
+$ gcc `pkg-config --cflags cairo` cairo.c `pkg-config --libs cairo`
GtkDrawingArea
@@ -308,10 +299,10 @@ important in this example.
draw_function
to draw the contents of
+itself whenever its necessary. For example, when a user drag a mouse
+pointer and resize a top-level window, GtkDrawingArea also changes the
+size. Then, the whole window needs to be redrawn. For the information of
gtk_drawing_area_set_draw_func
, see Gtk
API Reference – gtk_drawing_area_set_draw_func.
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL);
g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock);
- gtk_widget_show(win);
+ gtk_window_present(GTK_WINDOW (win));
}
Our Now, we will make a new application which has GtkDrawingArea and
-TfeTextView in it. Its name is “color”. If you write a name of a color
-in TfeTextView and click on the Custom drawing is to draw shapes dynamically. This section shows an
+example of custom drawing. You can draw rectangles by dragging the
+mouse. Down the button. The following colors are available. (without new line charactor) Move the mouse Up the button. The programs are at In addition the following two options are also available. This application can only do very simple things. However, it tells us
-that if we add powerful parser to it, we will be able to make it more
-efficient. I want to show it to you in the later section by making a
-turtle graphics language like Logo program language. In this section, we focus on how to bind the two objects. First, We need to make the ui file of the widgets. Title bar, four
-buttons in the tool bar, textview and drawing area. The ui file is as
-follows. This file describes a ui file to compile. The compiler
+glib-compile-resources uses it. The xml file for the resource compiler is almost same as before. Just
-substitute “color” for “tfe”.time_handler()
function is very simple, as it just
@@ -448,7 +448,7 @@ class="sourceCode numberSource C numberLines">
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA (clock), draw_clock, NULL, NULL);
g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) clock);
- gtk_widget_show(win);
+ gtk_window_present(GTK_WINDOW (win));
}
diff --git a/docs/sec26.html b/docs/sec26.html
index 18ab413..36a923c 100644
--- a/docs/sec26.html
+++ b/docs/sec26.html
@@ -111,423 +111,354 @@
-
+Combine GtkDrawingArea
-and TfeTextView
-run
button, then the color
-of GtkDrawingArea changes to the color given by you.Custom drawing
+src/custom_drawing
directory.
+Download the repository and see
+the directory. There are four files.
-
-
-
-Color.ui and
-color.gresource.xml
-rect.gresource.xml
+<?xml version="1.0" encoding="UTF-8"?>
-<interface>
- <object class="GtkApplicationWindow" id="win">
- <property name="title">color changer</property>
- <property name="default-width">600</property>
- <property name="default-height">400</property>
- <child>
- <object class="GtkBox" id="boxv">
- <property name="orientation">GTK_ORIENTATION_VERTICAL</property>
- <child>
- <object class="GtkBox" id="boxh1">
- <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
- <child>
- <object class="GtkLabel" id="dmy1">
- <property name="width-chars">10</property>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="btnr">
- <property name="label">Run</property>
- <signal name="clicked" handler="run_cb"></signal>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="btno">
- <property name="label">Open</property>
- <signal name="clicked" handler="open_cb"></signal>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="dmy2">
- <property name="hexpand">TRUE</property>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="btns">
- <property name="label">Save</property>
- <signal name="clicked" handler="save_cb"></signal>
- </object>
- </child>
- <child>
- <object class="GtkButton" id="btnc">
- <property name="label">Close</property>
- <signal name="clicked" handler="close_cb"></signal>
- </object>
- </child>
- <child>
- <object class="GtkLabel" id="dmy3">
- <property name="width-chars">10</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkBox" id="boxh2">
- <property name="orientation">GTK_ORIENTATION_HORIZONTAL</property>
- <property name="homogeneous">TRUE</property>
- <child>
- <object class="GtkScrolledWindow" id="scr">
- <property name="hexpand">TRUE</property>
- <property name="vexpand">TRUE</property>
- <child>
- <object class="TfeTextView" id="tv">
- <property name="wrap-mode">GTK_WRAP_WORD_CHAR</property>
- </object>
- </child>
- </object>
- </child>
- <child>
- <object class="GtkDrawingArea" id="da">
- <property name="hexpand">TRUE</property>
- <property name="vexpand">TRUE</property>
- </object>
- </child>
- </object>
- </child>
- </object>
- </child>
- </object>
-</interface>
-
-boxh1
makes a tool bar which
-has four buttons, Run
, Open
, Save
-and Close
. This is similar to the tfe
text
-editor in Section 9. There are two differences.
-Run
button replaces New
button. A signal
-element is added to each button object. It has “name” attribute which is
-a signal name and “handler” attribute which is the name of its signal
-handler. Options “-WI, –export-dynamic” CFLAG is necessary when you
-compile the application. You can achieve this by adding “export_dynamic:
-true” argument to the executable function in meson.build
.
-And be careful that the handler must be defined without ‘static’
-class.boxh2
includes
-GtkScrolledWindow and GtkDrawingArea. GtkBox has “homogeneous property”
-with TRUE value, so the two children have the same width in the box.
-TfeTextView is a child of GtkScrolledWindow.
The prefix is /com/github/ToshioCP/rect
and the file is
+rect.ui
. Therefore, GtkBuilder reads the resource from
+/com/github/ToshioCP/rect/rect.ui
.
The following is the ui file that defines the widgets. There are two
+widgets which are GtkApplicationWindow and GtkDrawingArea. The ids are
+win
and da
respectively.
<?xml version="1.0" encoding="UTF-8"?>
-<gresources>
- <gresource prefix="/com/github/ToshioCP/color">
- <file>color.ui</file>
- </gresource>
-</gresources>
The main point of this program is a drawing function.
-static void
-draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
- if (surface) {
- cairo_set_source_surface (cr, surface, 0, 0);
- cairo_paint (cr);
- }
-}
The surface
variable in line 3 is a static variable.
static cairo_surface_t *surface = NULL;
The drawing function just copies the surface
to its own
-surface with the cairo_paint
function. The surface (pointed
-by the static variable surface
) is built by the
-run
function.
This program uses GtkApplication. The application ID is
+com.github.ToshioCP.rect
.
#define APPLICATION_ID "com.github.ToshioCP.rect"
See GNOME +Developer Documentation for further information.
+The function main
is called at the beginning of the
+application.
int
+main (int argc, char **argv) {
+ GtkApplication *app;
+ int stat;
+
+ app = gtk_application_new (APPLICATION_ID, G_APPLICATION_HANDLES_OPEN);
+ g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
+ g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
+ g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
+ stat =g_application_run (G_APPLICATION (app), argc, argv);
+ g_object_unref (app);
+ return stat;
+}
It connects three signals and handlers.
+static void
-run (void) {
- GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
- GtkTextIter start_iter;
- GtkTextIter end_iter;
- char *contents;
- cairo_t *cr;
+app_startup (GApplication *application) {
+ GtkApplication *app = GTK_APPLICATION (application);
+ GtkBuilder *build;
+ GtkWindow *win;
+ GtkDrawingArea *da;
+ GtkGesture *drag;
- gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
- contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
- if (surface) {
- cr = cairo_create (surface);
- if (g_strcmp0 ("red", contents) == 0)
- cairo_set_source_rgb (cr, 1, 0, 0);
- else if (g_strcmp0 ("green", contents) == 0)
- cairo_set_source_rgb (cr, 0, 1, 0);
- else if (g_strcmp0 ("blue", contents) == 0)
- cairo_set_source_rgb (cr, 0, 0, 1);
- else if (g_strcmp0 ("white", contents) == 0)
- cairo_set_source_rgb (cr, 1, 1, 1);
- else if (g_strcmp0 ("black", contents) == 0)
- cairo_set_source_rgb (cr, 0, 0, 0);
- else if (g_strcmp0 ("light", contents) == 0)
- cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
- else if (g_strcmp0 ("dark", contents) == 0)
- cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
- else
- cairo_set_source_surface (cr, surface, 0, 0);
- cairo_paint (cr);
- cairo_destroy (cr);
- }
- g_free (contents);
-}
The startup handler does three things.
contents
.surface
points a surface instance,
-it is painted as follows.contents
-and copied to the surface with cairo_paint
.The drawing area just reflects the surface
. But one
-problem is resizing. If a user resizes the main window, the drawing area
-is also resized. It makes size difference between the surface and the
-drawing area. So, the surface needs to be resized to fit the drawing
-area.
It is accomplished by connecting the “resize” signal on the drawing -area to a handler.
-(GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL); g_signal_connect
The handler is as follows.
-static void
-resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
- if (surface)
- cairo_surface_destroy (surface);
- surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
- run ();
-}
If the variable surface
sets a surface instance, it is
-destroyed. A new surface is created and its size fits the drawing area.
-The surface is assigned to the variable surface
. The
-function run
is called and the surface is colored.
The signal is emitted when:
+So, the first surface is created when it is realized.
-This is the main file.
+static void
+app_activate (GApplication *application) {
+ GtkApplication *app = GTK_APPLICATION (application);
+ GtkWindow *win;
+
+ win = gtk_application_get_active_window (app);
+ gtk_window_present (win);
+}
The activate handler just shows the window.
+The program has two cairo surfaces and they are pointed by the global +variables.
+static cairo_surface_t *surface = NULL;
+static cairo_surface_t *surface_save = NULL;
The drawing process is as follows.
Run
-signal handler is the point in this program.surface
.surface
to the cairo surface of the
+GtkDrawingArea.gtk_widget_queue_draw (da)
to draw it if
+necessary.The following is colorapplication.c
.
They are created in the “resize” signal handler.
#include <gtk/gtk.h>
-#include "../tfetextview/tfetextview.h"
-
-static GtkWidget *win;
-static GtkWidget *tv;
-static GtkWidget *da;
-
-static cairo_surface_t *surface = NULL;
-
-static void
-run (void) {
- GtkTextBuffer *tb = gtk_text_view_get_buffer (GTK_TEXT_VIEW (tv));
- GtkTextIter start_iter;
- GtkTextIter end_iter;
- char *contents;
- cairo_t *cr;
-
- gtk_text_buffer_get_bounds (tb, &start_iter, &end_iter);
- contents = gtk_text_buffer_get_text (tb, &start_iter, &end_iter, FALSE);
- if (surface) {
- cr = cairo_create (surface);
- if (g_strcmp0 ("red", contents) == 0)
- cairo_set_source_rgb (cr, 1, 0, 0);
- else if (g_strcmp0 ("green", contents) == 0)
- cairo_set_source_rgb (cr, 0, 1, 0);
- else if (g_strcmp0 ("blue", contents) == 0)
- cairo_set_source_rgb (cr, 0, 0, 1);
- else if (g_strcmp0 ("white", contents) == 0)
- cairo_set_source_rgb (cr, 1, 1, 1);
- else if (g_strcmp0 ("black", contents) == 0)
- cairo_set_source_rgb (cr, 0, 0, 0);
- else if (g_strcmp0 ("light", contents) == 0)
- cairo_set_source_rgba (cr, 1, 1, 1, 0.5);
- else if (g_strcmp0 ("dark", contents) == 0)
- cairo_set_source_rgba (cr, 0, 0, 0, 0.5);
- else
- cairo_set_source_surface (cr, surface, 0, 0);
- cairo_paint (cr);
- cairo_destroy (cr);
- }
- g_free (contents);
-}
-
-void
-run_cb (GtkWidget *btnr) {
- run ();
- gtk_widget_queue_draw (GTK_WIDGET (da));
-}
-
-void
-open_cb (GtkWidget *btno) {
- tfe_text_view_open (TFE_TEXT_VIEW (tv), GTK_WINDOW (win));
-}
-
-void
-save_cb (GtkWidget *btns) {
- tfe_text_view_save (TFE_TEXT_VIEW (tv));
-}
-
-void
-close_cb (GtkWidget *btnc) {
- gtk_window_destroy (GTK_WINDOW (win));
-}
-
-static void
-resize_cb (GtkDrawingArea *drawing_area, int width, int height, gpointer user_data) {
- if (surface)
- cairo_surface_destroy (surface);
- surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
- run ();
-}
-
-static void
-draw_func (GtkDrawingArea *drawing_area, cairo_t *cr, int width, int height, gpointer user_data) {
- if (surface) {
- cairo_set_source_surface (cr, surface, 0, 0);
- cairo_paint (cr);
- }
-}
-
-static void
-app_activate (GApplication *application) {
- gtk_window_present (GTK_WINDOW (win));
-}
-
-static void
-app_startup (GApplication *application) {
- GtkApplication *app = GTK_APPLICATION (application);
- GtkBuilder *build;
- GdkDisplay *display;
-
- build = gtk_builder_new_from_resource ("/com/github/ToshioCP/color/color.ui");
- win = GTK_WIDGET (gtk_builder_get_object (build, "win"));
- gtk_window_set_application (GTK_WINDOW (win), app);
- tv = GTK_WIDGET (gtk_builder_get_object (build, "tv"));
- da = GTK_WIDGET (gtk_builder_get_object (build, "da"));
- g_object_unref(build);
- g_signal_connect (GTK_DRAWING_AREA (da), "resize", G_CALLBACK (resize_cb), NULL);
- gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA (da), draw_func, NULL, NULL);
-
- display = gdk_display_get_default ();
- GtkCssProvider *provider = gtk_css_provider_new ();
- gtk_css_provider_load_from_data (provider, "textview {padding: 10px; font-family: monospace; font-size: 12pt;}", -1);
- gtk_style_context_add_provider_for_display (display, GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_USER);
-}
-
-static void
-app_shutdown (GApplication *application) {
- if (surface)
- cairo_surface_destroy (surface);
-}
-
-#define APPLICATION_ID "com.github.ToshioCP.color"
-
-int
-main (int argc, char **argv) {
- GtkApplication *app;
- int stat;
-
- app = gtk_application_new (APPLICATION_ID, G_APPLICATION_DEFAULT_FLAGS);
-
- g_signal_connect (app, "startup", G_CALLBACK (app_startup), NULL);
- g_signal_connect (app, "shutdown", G_CALLBACK (app_shutdown), NULL);
- g_signal_connect (app, "activate", G_CALLBACK (app_activate), NULL);
-
- stat =g_application_run (G_APPLICATION (app), argc, argv);
- g_object_unref (app);
- return stat;
-}
main
. It creates a new application
-instance. And connects three signals startup, shutdown and activate to
-their handlers. It runs the application. It releases the reference to
-the application and returns with stat
value.This file is almost same as before. An argument “export_dynamic: -true” is added to executable function.
+class="sourceCode numberSource C numberLines">static void
+resize_cb (GtkWidget *widget, int width, int height, gpointer user_data) {
+ cairo_t *cr;
+
+ if (surface)
+ cairo_surface_destroy (surface);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
+ if (surface_save)
+ cairo_surface_destroy (surface_save);
+ surface_save = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
+ /* Paint the surface white. It is the background color. */
+ cr = cairo_create (surface);
+ cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+}
+This callback is called when the GtkDrawingArea is shown. It is the +only call because the window is not resizable.
+It creates image surfaces for surface
and
+surface_save
. The surface
surface is painted
+white, which is the background color.
The drawing function copies surface
to the
+GtkDrawingArea surface.
project('color', 'c')
-
-gtkdep = dependency('gtk4')
-
-gnome=import('gnome')
-resources = gnome.compile_resources('resources','color.gresource.xml')
-
-sourcefiles=files('colorapplication.c', '../tfetextview/tfetextview.c')
-
-executable('color', sourcefiles, resources, dependencies: gtkdep, export_dynamic: true)
Type the following to compile the program.
-$ meson _build
-$ ninja -C _build
-The application is made in _build
directory. Type the
-following to execute it.
$ _build/color
-Type “red”, “green”, “blue”, “white”, black”, “light” or “dark” in
-the TfeTextView. No new line charactor is needed. Then, click on the
-Run
button. Make sure the color of GtkDrawingArea
-changes.
In this program TfeTextView is used to change the color. You can use -buttons or menus instead of textview. Probably it is more appropriate. -Using textview is unnatural. It is a good practice to make such -application by yourself.
+class="sourceCode numberSource C numberLines">static void
+draw_cb (GtkDrawingArea *da, cairo_t *cr, int width, int height, gpointer user_data) {
+ if (surface) {
+ cairo_set_source_surface (cr, surface, 0.0, 0.0);
+ cairo_paint (cr);
+ }
+}
+This function is called by the system when it needs to redraw the +drawing area.
+Two surfaces surface
and surface_save
are
+destroyed before the application quits.
static void
+app_shutdown (GApplication *application) {
+ if (surface)
+ cairo_surface_destroy (surface);
+ if (surface_save)
+ cairo_surface_destroy (surface_save);
+}
Gesture class is used to recognize human gestures such as click, +drag, pan, swipe and so on. It is a subclass of GtkEventController. +GtkGesture class is abstract and there are several implementations.
+The program rect.c
uses GtkGestureDrag. It is the
+implementation for drags. The parent-child relationship is as
+follows.
GObject -- GtkEventController -- GtkGesture -- GtkGestureSingle -- GtkGestureDrag
+GtkGestureSingle is a subclass of GtkGesture and optimized for +singe-touch and mouse gestures.
+A GtkGestureDrag instance is created and initialized in the startup
+signal handler in rect.c
. See line 18 to 23 in the
+following.
static void
+app_startup (GApplication *application) {
+ GtkApplication *app = GTK_APPLICATION (application);
+ GtkBuilder *build;
+ GtkWindow *win;
+ GtkDrawingArea *da;
+ GtkGesture *drag;
+
+ build = gtk_builder_new_from_resource ("/com/github/ToshioCP/rect/rect.ui");
+ win = GTK_WINDOW (gtk_builder_get_object (build, "win"));
+ da = GTK_DRAWING_AREA (gtk_builder_get_object (build, "da"));
+ gtk_window_set_application (win, app);
+ g_object_unref (build);
+
+ gtk_drawing_area_set_draw_func (da, draw_cb, NULL, NULL);
+ g_signal_connect_after (da, "resize", G_CALLBACK (resize_cb), NULL);
+
+ drag = gtk_gesture_drag_new ();
+ gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (drag), GDK_BUTTON_PRIMARY);
+ gtk_widget_add_controller (GTK_WIDGET (da), GTK_EVENT_CONTROLLER (drag));
+ g_signal_connect (drag, "drag-begin", G_CALLBACK (drag_begin), NULL);
+ g_signal_connect (drag, "drag-update", G_CALLBACK (drag_update), da);
+ g_signal_connect (drag, "drag-end", G_CALLBACK (drag_end), da);
+}
gtk_gesture_drag_new
creates a new
+GtkGestureDrag instance.gtk_gesture_single_set_button
sets the
+button number to listen to. The constant GDK_BUTTON_PRIMARY
+is the left button of a mouse.gtk_widget_add_controller
adds an event
+controller, gestures are descendants of the event controller, to a
+widget.The process during the drag is as follows.
+We need two global variables for the start point.
+static double start_x;
+static double start_y;
The following is the handler for the “drag-begin” signal.
+static void
+copy_surface (cairo_surface_t *src, cairo_surface_t *dst) {
+ if (!src || !dst)
+ return;
+ cairo_t *cr = cairo_create (dst);
+ cairo_set_source_surface (cr, src, 0.0, 0.0);
+ cairo_paint (cr);
+ cairo_destroy (cr);
+}
+
+static void
+drag_begin (GtkGestureDrag *gesture, double x, double y, gpointer user_data) {
+ // save the surface and record (x, y)
+ copy_surface (surface, surface_save);
+ start_x = x;
+ start_y = y;
+}
surface
to surface_save
, which is
+an image just before the dragging.start_x
and
+start_y
.static void
+drag_update (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
+ GtkWidget *da = GTK_WIDGET (user_data);
+ cairo_t *cr;
+
+ copy_surface (surface_save, surface);
+ cr = cairo_create (surface);
+ cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
+ cairo_set_line_width (cr, 1.0);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+ gtk_widget_queue_draw (da);
+}
surface
from surface_save
.gtk_widget_queue_draw
to add the GtkDrawingArea
+to the queue to redraw.static void
+drag_end (GtkGestureDrag *gesture, double offset_x, double offset_y, gpointer user_data) {
+ GtkWidget *da = GTK_WIDGET (user_data);
+ cairo_t *cr;
+
+ copy_surface (surface_save, surface);
+ cr = cairo_create (surface);
+ cairo_rectangle (cr, start_x, start_y, offset_x, offset_y);
+ cairo_set_line_width (cr, 6.0);
+ cairo_stroke (cr);
+ cairo_destroy (cr);
+ gtk_widget_queue_draw (da);
+}
surface
from surface_save
.gtk_widget_queue_draw
to add the GtkDrawingArea
+to the queue to redraw.Download the repository. Change
+your current directory to src/custom_drawing
. Run meson and
+ninja to build the program. Type _build/rect
to run the
+program. Try to draw rectangles.
$ cd src/custom_drawing
+$ meson setup _build
+$ ninja -C _build
+$ _build/rect
+