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

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