Last active
July 17, 2023 14:24
-
-
Save djvanderlaan/09691e59fdd212fc7f8442bd8badfecb to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
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
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?