/* Paint
* # Keywords : GdkDrawingArea , GtkGesture
*
* Demonstrates practical handling of drawing tablets in a real world
* usecase .
*/
#include <glib/gi18n.h>
#include <gtk/gtk.h>
enum {
COLOR_SET,
N_SIGNALS
};
static guint area_signals[N_SIGNALS] = { 0 , };
typedef struct
{
GtkWidget parent_instance;
cairo_surface_t *surface;
cairo_t *cr;
GdkRGBA draw_color;
GtkPadController *pad_controller;
double brush_size;
GtkGesture *gesture;
} DrawingArea;
typedef struct
{
GtkWidgetClass parent_class;
} DrawingAreaClass;
static GtkPadActionEntry pad_actions[] = {
{ GTK_PAD_ACTION_BUTTON, 1 , -1 , N_("Black" ), "pad.black" },
{ GTK_PAD_ACTION_BUTTON, 2 , -1 , N_("Pink" ), "pad.pink" },
{ GTK_PAD_ACTION_BUTTON, 3 , -1 , N_("Green" ), "pad.green" },
{ GTK_PAD_ACTION_BUTTON, 4 , -1 , N_("Red" ), "pad.red" },
{ GTK_PAD_ACTION_BUTTON, 5 , -1 , N_("Purple" ), "pad.purple" },
{ GTK_PAD_ACTION_BUTTON, 6 , -1 , N_("Orange" ), "pad.orange" },
{ GTK_PAD_ACTION_STRIP, -1 , -1 , N_("Brush size" ), "pad.brush_size" },
{ GTK_PAD_ACTION_DIAL, -1 , -1 , N_("Brush size" ), "pad.change_brush_size" },
};
static const char *pad_colors[] = {
"black" ,
"pink" ,
"green" ,
"red" ,
"purple" ,
"orange"
};
static GType drawing_area_get_type (void );
G_DEFINE_TYPE (DrawingArea, drawing_area, GTK_TYPE_WIDGET)
static void drawing_area_set_color (DrawingArea *area,
const GdkRGBA *color);
static void
drawing_area_ensure_surface (DrawingArea *area,
int width,
int height)
{
if (!area->surface ||
cairo_image_surface_get_width (area->surface) != width ||
cairo_image_surface_get_height (area->surface) != height)
{
cairo_surface_t *surface;
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
width, height);
if (area->surface)
{
cairo_t *cr;
cr = cairo_create (surface);
cairo_set_source_surface (cr, area->surface, 0 , 0 );
cairo_paint (cr);
cairo_surface_destroy (area->surface);
cairo_destroy (area->cr);
cairo_destroy (cr);
}
area->surface = surface;
area->cr = cairo_create (surface);
}
}
static void
drawing_area_size_allocate (GtkWidget *widget,
int width,
int height,
int baseline)
{
DrawingArea *area = (DrawingArea *) widget;
drawing_area_ensure_surface (area, width, height);
GTK_WIDGET_CLASS (drawing_area_parent_class)->size_allocate (widget, width, height, baseline);
}
static void
drawing_area_map (GtkWidget *widget)
{
GTK_WIDGET_CLASS (drawing_area_parent_class)->map (widget);
drawing_area_ensure_surface ((DrawingArea *) widget,
gtk_widget_get_width (widget),
gtk_widget_get_height (widget));
}
static void
drawing_area_unmap (GtkWidget *widget)
{
DrawingArea *area = (DrawingArea *) widget;
g_clear_pointer (&area->cr, cairo_destroy);
g_clear_pointer (&area->surface, cairo_surface_destroy);
GTK_WIDGET_CLASS (drawing_area_parent_class)->unmap (widget);
}
static void
drawing_area_snapshot (GtkWidget *widget,
GtkSnapshot *snapshot)
{
DrawingArea *area = (DrawingArea *) widget;
int width, height;
cairo_t *cr;
width = gtk_widget_get_width (widget);
height = gtk_widget_get_height (widget);
cr = gtk_snapshot_append_cairo (snapshot, &GRAPHENE_RECT_INIT (0 , 0 , width, height));
cairo_set_source_rgb (cr, 1 , 1 , 1 );
cairo_paint (cr);
cairo_set_source_surface (cr, area->surface, 0 , 0 );
cairo_paint (cr);
cairo_set_source_rgb (cr, 0 .6 , 0 .6 , 0 .6 );
cairo_rectangle (cr, 0 , 0 , width, height);
cairo_stroke (cr);
cairo_destroy (cr);
}
static void
on_pad_button_activate (GSimpleAction *action,
GVariant *parameter,
DrawingArea *area)
{
const char *color = g_object_get_data (G_OBJECT (action), "color" );
GdkRGBA rgba;
gdk_rgba_parse (&rgba, color);
drawing_area_set_color (area, &rgba);
}
static void
on_pad_knob_change (GSimpleAction *action,
GVariant *parameter,
DrawingArea *area)
{
double value = g_variant_get_double (parameter);
area->brush_size = value;
}
static void
on_pad_dial_change (GSimpleAction *action,
GVariant *parameter,
DrawingArea *area)
{
double value = g_variant_get_double (parameter);
area->brush_size += value / 120 .0 ;
}
static void
drawing_area_unroot (GtkWidget *widget)
{
DrawingArea *area = (DrawingArea *) widget;
GtkWidget *toplevel;
toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
if (area->pad_controller)
{
gtk_widget_remove_controller (toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
area->pad_controller = NULL;
}
GTK_WIDGET_CLASS (drawing_area_parent_class)->unroot (widget);
}
static void
drawing_area_root (GtkWidget *widget)
{
DrawingArea *area = (DrawingArea *) widget;
GSimpleActionGroup *action_group;
GSimpleAction *action;
GtkWidget *toplevel;
int i;
GTK_WIDGET_CLASS (drawing_area_parent_class)->root (widget);
toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (area)));
action_group = g_simple_action_group_new ();
area->pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (action_group), NULL);
for (i = 0 ; i < G_N_ELEMENTS (pad_actions); i++)
{
if (pad_actions[i].type == GTK_PAD_ACTION_BUTTON)
{
action = g_simple_action_new (pad_actions[i].action_name, NULL);
g_object_set_data (G_OBJECT (action), "color" ,
(gpointer) pad_colors[i]);
g_signal_connect (action, "activate" ,
G_CALLBACK (on_pad_button_activate), area);
}
else if (pad_actions[i].type == GTK_PAD_ACTION_DIAL)
{
action = g_simple_action_new_stateful (pad_actions[i].action_name,
G_VARIANT_TYPE_DOUBLE, NULL);
g_signal_connect (action, "activate" ,
G_CALLBACK (on_pad_dial_change), area);
}
else
{
action = g_simple_action_new_stateful (pad_actions[i].action_name,
G_VARIANT_TYPE_DOUBLE, NULL);
g_signal_connect (action, "activate" ,
G_CALLBACK (on_pad_knob_change), area);
}
g_action_map_add_action (G_ACTION_MAP (action_group), G_ACTION (action));
g_object_unref (action);
}
gtk_pad_controller_set_action_entries (area->pad_controller, pad_actions,
G_N_ELEMENTS (pad_actions));
gtk_widget_add_controller (toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
}
static void
drawing_area_class_init (DrawingAreaClass *klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
widget_class->size_allocate = drawing_area_size_allocate;
widget_class->snapshot = drawing_area_snapshot;
widget_class->map = drawing_area_map;
widget_class->unmap = drawing_area_unmap;
widget_class->root = drawing_area_root;
widget_class->unroot = drawing_area_unroot;
area_signals[COLOR_SET] =
g_signal_new ("color-set" ,
G_TYPE_FROM_CLASS (widget_class),
G_SIGNAL_RUN_FIRST,
0 , NULL, NULL, NULL,
G_TYPE_NONE, 1 , GDK_TYPE_RGBA);
}
static void
drawing_area_apply_stroke (DrawingArea *area,
GdkDeviceTool *tool,
double x,
double y,
double pressure)
{
if (tool && gdk_device_tool_get_tool_type (tool) == GDK_DEVICE_TOOL_TYPE_ERASER)
{
cairo_set_line_width (area->cr, 10 * pressure * area->brush_size);
cairo_set_operator (area->cr, CAIRO_OPERATOR_DEST_OUT);
}
else
{
cairo_set_line_width (area->cr, 4 * pressure * area->brush_size);
cairo_set_operator (area->cr, CAIRO_OPERATOR_SATURATE);
}
cairo_set_source_rgba (area->cr, area->draw_color.red,
area->draw_color.green, area->draw_color.blue,
area->draw_color.alpha * pressure);
cairo_line_to (area->cr, x, y);
cairo_stroke (area->cr);
cairo_move_to (area->cr, x, y);
}
static void
stylus_gesture_down (GtkGestureStylus *gesture,
double x,
double y,
DrawingArea *area)
{
cairo_new_path (area->cr);
}
static void
stylus_gesture_motion (GtkGestureStylus *gesture,
double x,
double y,
DrawingArea *area)
{
GdkTimeCoord *backlog;
GdkDeviceTool *tool;
double pressure;
guint n_items;
tool = gtk_gesture_stylus_get_device_tool (gesture);
if (gtk_gesture_stylus_get_backlog (gesture, &backlog, &n_items))
{
guint i;
for (i = 0 ; i < n_items; i++)
{
drawing_area_apply_stroke (area, tool,
backlog[i].axes[GDK_AXIS_X],
backlog[i].axes[GDK_AXIS_Y],
backlog[i].flags & GDK_AXIS_FLAG_PRESSURE
? backlog[i].axes[GDK_AXIS_PRESSURE]
: 1 );
}
g_free (backlog);
}
else
{
if (!gtk_gesture_stylus_get_axis (gesture, GDK_AXIS_PRESSURE, &pressure))
pressure = 1 ;
drawing_area_apply_stroke (area, tool, x, y, pressure);
}
gtk_widget_queue_draw (GTK_WIDGET (area));
}
static void
drawing_area_init (DrawingArea *area)
{
GtkGesture *gesture;
gesture = gtk_gesture_stylus_new ();
g_signal_connect (gesture, "down" ,
G_CALLBACK (stylus_gesture_down), area);
g_signal_connect (gesture, "motion" ,
G_CALLBACK (stylus_gesture_motion), area);
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture));
area->draw_color = (GdkRGBA) { 0 , 0 , 0 , 1 };
area->brush_size = 1 ;
area->gesture = gesture;
}
static GtkWidget *
drawing_area_new (void )
{
return g_object_new (drawing_area_get_type (), NULL);
}
static void
drawing_area_set_color (DrawingArea *area,
const GdkRGBA *color)
{
if (gdk_rgba_equal (&area->draw_color, color))
return ;
area->draw_color = *color;
g_signal_emit (area, area_signals[COLOR_SET], 0 , &area->draw_color);
}
static void
color_button_color_set (GtkColorDialogButton *button,
GParamSpec *pspec,
DrawingArea *draw_area)
{
const GdkRGBA *color;
color = gtk_color_dialog_button_get_rgba (button);
drawing_area_set_color (draw_area, color);
}
static void
drawing_area_color_set (DrawingArea *area,
GdkRGBA *color,
GtkColorDialogButton *button)
{
gtk_color_dialog_button_set_rgba (button, color);
}
static void
drawing_area_clear (GtkButton *button,
DrawingArea *area)
{
int width, height;
g_clear_pointer (&area->cr, cairo_destroy);
g_clear_pointer (&area->surface, cairo_surface_destroy);
width = gtk_widget_get_width (GTK_WIDGET (area));
height = gtk_widget_get_height (GTK_WIDGET (area));
drawing_area_ensure_surface (area, width, height);
gtk_widget_queue_draw (GTK_WIDGET (area));
}
static GtkGesture *
drawing_area_get_gesture (DrawingArea *area)
{
return area->gesture;
}
GtkWidget *
do_paint (GtkWidget *toplevel)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *draw_area, *headerbar, *button;
window = gtk_window_new ();
draw_area = drawing_area_new ();
gtk_window_set_child (GTK_WINDOW (window), draw_area);
headerbar = gtk_header_bar_new ();
button = gtk_color_dialog_button_new (gtk_color_dialog_new ());
g_signal_connect (button, "notify::rgba" ,
G_CALLBACK (color_button_color_set), draw_area);
g_signal_connect (draw_area, "color-set" ,
G_CALLBACK (drawing_area_color_set), button);
gtk_color_dialog_button_set_rgba (GTK_COLOR_DIALOG_BUTTON (button),
&(GdkRGBA) { 0 , 0 , 0 , 1 });
gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), button);
button = gtk_button_new_from_icon_name ("view-refresh-symbolic" );
g_signal_connect (button, "clicked" ,
G_CALLBACK (drawing_area_clear), draw_area);
gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), button);
button = gtk_check_button_new_with_label ("Stylus only" );
g_object_bind_property (button, "active" ,
drawing_area_get_gesture ((DrawingArea *)draw_area), "stylus-only" ,
G_BINDING_SYNC_CREATE);
gtk_header_bar_pack_start (GTK_HEADER_BAR (headerbar), button);
gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
gtk_window_set_title (GTK_WINDOW (window), "Paint" );
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
}
if (!gtk_widget_get_visible (window))
gtk_widget_set_visible (window, TRUE );
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}
Messung V0.5 in Prozent C=100 H=97 G=98
¤ Dauer der Verarbeitung: 0.12 Sekunden
(vorverarbeitet am 2026-07-03)
¤
*© Formatika GbR, Deutschland