Skip to content

Instantly share code, notes, and snippets.

@djvanderlaan
Last active July 17, 2023 14:24
Show Gist options
  • Save djvanderlaan/09691e59fdd212fc7f8442bd8badfecb to your computer and use it in GitHub Desktop.
Save djvanderlaan/09691e59fdd212fc7f8442bd8badfecb to your computer and use it in GitHub Desktop.
// This example adds handling stylus input to the Custom Drawing example
// from the GTK4 Getting Started.
// See: https://docs.gtk.org/gtk4/getting_started.html#custom-drawing
#include <gtk/gtk.h>
/* Surface to store current scribbles */
static cairo_surface_t *surface = NULL;
static void clear_surface(void) {
cairo_t* 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 void resize_cb(GtkWidget *widget, int width, int height, gpointer data) {
if (surface) {
cairo_surface_destroy(surface);
surface = NULL;
}
if (gtk_native_get_surface(gtk_widget_get_native(widget))) {
surface = gdk_surface_create_similar_surface(
gtk_native_get_surface(gtk_widget_get_native(widget)),
CAIRO_CONTENT_COLOR, gtk_widget_get_width(widget),
gtk_widget_get_height(widget));
/* Initialize the surface to white */
clear_surface();
}
}
/* Redraw the screen from the surface. Note that the draw
* callback receives a ready-to-be-used cairo_t that is already
* clipped to only draw the exposed areas of the widget
*/
static void draw_cb(GtkDrawingArea *drawing_area, cairo_t *cr,
int width, int height, gpointer data) {
cairo_set_source_surface(cr, surface, 0, 0);
cairo_paint(cr);
}
/* Draw a rectangle on the surface at the given position */
static void draw_brush (GtkWidget *widget, double x, double y, double size = 0.5) {
/* Paint to the surface, where we store our state */
cairo_t* cr = cairo_create (surface);
double w = 12*size;
cairo_rectangle (cr, x - w*0.5, y - w*0.5, w, w);
cairo_fill (cr);
cairo_destroy (cr);
/* Now invalidate the drawing area. */
gtk_widget_queue_draw (widget);
}
static double start_x;
static double start_y;
static GtkGesture* stylus;
static void drag_begin(GtkGestureDrag *gesture, double x, double y,
GtkWidget *area) {
// First check if the event sequence is already handled by the stylus gesture; if not
// we will handle is here; if it is deny it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
if (gtk_gesture_get_sequence_state(GTK_GESTURE(stylus), sequence) == GTK_EVENT_SEQUENCE_CLAIMED) {
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_DENIED);
} else {
start_x = x;
start_y = y;
draw_brush(area, x, y);
}
}
static void drag_update(GtkGestureDrag *gesture, double x, double y,
GtkWidget *area) {
// First check if the event sequence is already handled by the stylus gesture; if not
// we will handle is here; if it is deny it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
if (gtk_gesture_get_sequence_state(GTK_GESTURE(stylus), sequence) == GTK_EVENT_SEQUENCE_CLAIMED) {
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_DENIED);
} else {
draw_brush(area, start_x + x, start_y + y);
}
}
static void drag_end(GtkGestureDrag *gesture, double x, double y,
GtkWidget *area) {
// First check if the event sequence is already handled by the stylus gesture; if not
// we will handle is here; if it is deny it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
if (gtk_gesture_get_sequence_state(GTK_GESTURE(stylus), sequence) == GTK_EVENT_SEQUENCE_CLAIMED) {
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_DENIED);
} else {
draw_brush(area, start_x + x, start_y + y);
}
}
static void stylus_down(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
double pressure;
gtk_gesture_stylus_get_axis(gesture, GDK_AXIS_PRESSURE, &pressure);
draw_brush(area, x, y, pressure);
// Claim the event sequence; the drag gesture should not handle it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_CLAIMED);
}
static void stylus_motion(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
double pressure;
gtk_gesture_stylus_get_axis(gesture, GDK_AXIS_PRESSURE, &pressure);
draw_brush(area, x, y, pressure);
// Claim the event sequence; the drag gesture should not handle it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_CLAIMED);
}
static void stylus_up(GtkGestureStylus* gesture, double x, double y, GtkWidget* area) {
double pressure;
gtk_gesture_stylus_get_axis(gesture, GDK_AXIS_PRESSURE, &pressure);
draw_brush(area, x, y, pressure);
// Claim the event sequence; the drag gesture should not handle it
GdkEventSequence* sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture));
gtk_gesture_set_sequence_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_CLAIMED);
}
static void pressed (GtkGestureClick *gesture, int n_press, double x,
double y, GtkWidget *area) {
clear_surface ();
gtk_widget_queue_draw (area);
}
static void close_window(void) {
if (surface) cairo_surface_destroy(surface);
}
static void activate(GtkApplication *app, gpointer user_data) {
GtkWidget* window = gtk_application_window_new(app);
gtk_window_set_title(GTK_WINDOW(window), "Drawing Area");
g_signal_connect(window, "destroy", G_CALLBACK(close_window), NULL);
GtkWidget* frame = gtk_frame_new(NULL);
gtk_window_set_child(GTK_WINDOW(window), frame);
GtkWidget* drawing_area = gtk_drawing_area_new();
gtk_widget_set_size_request(drawing_area, 500, 500);
gtk_frame_set_child(GTK_FRAME(frame), drawing_area);
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(drawing_area), draw_cb, NULL, NULL);
g_signal_connect_after(drawing_area, "resize", G_CALLBACK(resize_cb), NULL);
// Add the drag gesture before the stylus gesture; we want to have the stylus
// gesture handling the events first.
GtkGesture* drag = gtk_gesture_drag_new();
gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(drag), GDK_BUTTON_PRIMARY);
gtk_widget_add_controller(drawing_area, GTK_EVENT_CONTROLLER(drag));
g_signal_connect(drag, "drag-begin", G_CALLBACK(drag_begin), drawing_area);
g_signal_connect(drag, "drag-update", G_CALLBACK(drag_update), drawing_area);
g_signal_connect(drag, "drag-end", G_CALLBACK(drag_end), drawing_area);
// We store the stylus gesture globally as it is also needed by the
// drag gesture to check if the stylus gesture has already handled the
// event sequence
stylus = gtk_gesture_stylus_new();
gtk_widget_add_controller(drawing_area, GTK_EVENT_CONTROLLER(stylus));
g_signal_connect(stylus, "down", G_CALLBACK(stylus_down), drawing_area);
g_signal_connect(stylus, "up", G_CALLBACK(stylus_up), drawing_area);
g_signal_connect(stylus, "motion", G_CALLBACK(stylus_motion), drawing_area);
// Right click clears the page
GtkGesture* press = gtk_gesture_click_new ();
gtk_gesture_single_set_button (GTK_GESTURE_SINGLE (press), GDK_BUTTON_SECONDARY);
gtk_widget_add_controller (drawing_area, GTK_EVENT_CONTROLLER (press));
g_signal_connect (press, "pressed", G_CALLBACK (pressed), drawing_area);
gtk_widget_show(window);
}
int main(int argc, char* argv[]) {
GtkApplication* app = gtk_application_new("org.gtk.example", G_APPLICATION_FLAGS_NONE);
g_signal_connect(app, "activate", G_CALLBACK(activate), NULL);
int status = g_application_run(G_APPLICATION(app), argc, argv);
g_object_unref(app);
return status;
}
@Friz64
Copy link

Friz64 commented Nov 23, 2022

Hey @djvanderlaan, this code doesn't emit stylus signals for me. When drawing with my tablet I only see the drag gesture signals emitting, even though the stylus gesture works in other GTK apps on my computer. This is how I'm compiling the code. Is there something I'm missing?

@Friz64
Copy link

Friz64 commented Nov 23, 2022

Sorry to bother, it turns out when running the program through the integrated VSCode terminal, the GDK_BACKEND=x11 environment variable is inherited, which breaks everything...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment