2018-02-02 15:04:17 +00:00
|
|
|
/* Paint
|
2020-09-13 18:59:33 +00:00
|
|
|
* #Keywords: GdkDrawingArea, GtkGesture
|
2018-02-02 15:04:17 +00:00
|
|
|
*
|
|
|
|
* Demonstrates practical handling of drawing tablets in a real world
|
|
|
|
* usecase.
|
|
|
|
*/
|
2018-06-26 20:15:27 +00:00
|
|
|
#include <glib/gi18n.h>
|
2018-02-02 15:04:17 +00:00
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
enum {
|
|
|
|
COLOR_SET,
|
|
|
|
N_SIGNALS
|
|
|
|
};
|
|
|
|
|
|
|
|
static guint area_signals[N_SIGNALS] = { 0, };
|
|
|
|
|
2018-02-02 15:04:17 +00:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
GtkWidget parent_instance;
|
|
|
|
cairo_surface_t *surface;
|
|
|
|
cairo_t *cr;
|
|
|
|
GdkRGBA draw_color;
|
2018-06-26 20:15:27 +00:00
|
|
|
GtkPadController *pad_controller;
|
2020-07-24 20:32:16 +00:00
|
|
|
double brush_size;
|
2018-02-02 15:04:17 +00:00
|
|
|
} DrawingArea;
|
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
GtkWidgetClass parent_class;
|
|
|
|
} DrawingAreaClass;
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
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" },
|
|
|
|
};
|
|
|
|
|
2020-07-24 18:40:36 +00:00
|
|
|
static const char *pad_colors[] = {
|
2018-06-26 20:15:27 +00:00
|
|
|
"black",
|
|
|
|
"pink",
|
|
|
|
"green",
|
|
|
|
"red",
|
|
|
|
"purple",
|
|
|
|
"orange"
|
|
|
|
};
|
|
|
|
|
2020-03-07 14:01:21 +00:00
|
|
|
static GType drawing_area_get_type (void);
|
2018-02-02 15:04:17 +00:00
|
|
|
G_DEFINE_TYPE (DrawingArea, drawing_area, GTK_TYPE_WIDGET)
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
static void drawing_area_set_color (DrawingArea *area,
|
|
|
|
GdkRGBA *color);
|
|
|
|
|
2018-02-02 15:04:17 +00:00
|
|
|
static void
|
|
|
|
drawing_area_ensure_surface (DrawingArea *area,
|
2020-07-24 13:54:49 +00:00
|
|
|
int width,
|
|
|
|
int height)
|
2018-02-02 15:04:17 +00:00
|
|
|
{
|
|
|
|
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
|
2018-08-16 04:53:03 +00:00
|
|
|
drawing_area_size_allocate (GtkWidget *widget,
|
|
|
|
int width,
|
|
|
|
int height,
|
|
|
|
int baseline)
|
2018-02-02 15:04:17 +00:00
|
|
|
{
|
|
|
|
DrawingArea *area = (DrawingArea *) widget;
|
|
|
|
|
2018-08-16 04:53:03 +00:00
|
|
|
drawing_area_ensure_surface (area, width, height);
|
2018-02-02 15:04:17 +00:00
|
|
|
|
2018-08-16 04:53:03 +00:00
|
|
|
GTK_WIDGET_CLASS (drawing_area_parent_class)->size_allocate (widget, width, height, baseline);
|
2018-02-02 15:04:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drawing_area_map (GtkWidget *widget)
|
|
|
|
{
|
|
|
|
GtkAllocation allocation;
|
|
|
|
|
|
|
|
GTK_WIDGET_CLASS (drawing_area_parent_class)->map (widget);
|
|
|
|
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
drawing_area_ensure_surface ((DrawingArea *) widget,
|
|
|
|
allocation.width, allocation.height);
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
GtkAllocation allocation;
|
|
|
|
cairo_t *cr;
|
|
|
|
|
|
|
|
gtk_widget_get_allocation (widget, &allocation);
|
|
|
|
cr = gtk_snapshot_append_cairo (snapshot,
|
|
|
|
&GRAPHENE_RECT_INIT (
|
|
|
|
0, 0,
|
|
|
|
allocation.width,
|
|
|
|
allocation.height
|
2018-04-24 01:17:23 +00:00
|
|
|
));
|
2018-02-02 15:04:17 +00:00
|
|
|
|
|
|
|
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, allocation.width, allocation.height);
|
|
|
|
cairo_stroke (cr);
|
|
|
|
|
|
|
|
cairo_destroy (cr);
|
|
|
|
}
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
static void
|
|
|
|
on_pad_button_activate (GSimpleAction *action,
|
|
|
|
GVariant *parameter,
|
|
|
|
DrawingArea *area)
|
|
|
|
{
|
2020-07-24 18:40:36 +00:00
|
|
|
const char *color = g_object_get_data (G_OBJECT (action), "color");
|
2018-06-26 20:15:27 +00:00
|
|
|
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)
|
|
|
|
{
|
2020-07-24 20:32:16 +00:00
|
|
|
double value = g_variant_get_double (parameter);
|
2018-06-26 20:15:27 +00:00
|
|
|
|
|
|
|
area->brush_size = value;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
2019-02-23 06:12:43 +00:00
|
|
|
drawing_area_unroot (GtkWidget *widget)
|
2018-06-26 20:15:27 +00:00
|
|
|
{
|
|
|
|
DrawingArea *area = (DrawingArea *) widget;
|
|
|
|
GtkWidget *toplevel;
|
|
|
|
|
2019-05-20 04:47:50 +00:00
|
|
|
toplevel = GTK_WIDGET (gtk_widget_get_root (widget));
|
2019-02-23 06:12:43 +00:00
|
|
|
|
|
|
|
if (area->pad_controller)
|
2018-06-26 20:15:27 +00:00
|
|
|
{
|
2019-02-23 06:12:43 +00:00
|
|
|
gtk_widget_remove_controller (toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
|
2018-06-26 20:15:27 +00:00
|
|
|
area->pad_controller = NULL;
|
|
|
|
}
|
|
|
|
|
2019-02-23 06:12:43 +00:00
|
|
|
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;
|
2020-07-24 13:54:49 +00:00
|
|
|
int i;
|
2019-02-23 06:12:43 +00:00
|
|
|
|
|
|
|
GTK_WIDGET_CLASS (drawing_area_parent_class)->root (widget);
|
|
|
|
|
2019-05-20 04:47:50 +00:00
|
|
|
toplevel = GTK_WIDGET (gtk_widget_get_root (GTK_WIDGET (area)));
|
2018-06-26 20:15:27 +00:00
|
|
|
|
|
|
|
action_group = g_simple_action_group_new ();
|
2019-02-23 06:12:43 +00:00
|
|
|
area->pad_controller = gtk_pad_controller_new (G_ACTION_GROUP (action_group), NULL);
|
2018-06-26 20:15:27 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
{
|
|
|
|
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));
|
|
|
|
|
2019-02-23 06:12:43 +00:00
|
|
|
gtk_widget_add_controller (toplevel, GTK_EVENT_CONTROLLER (area->pad_controller));
|
2018-06-26 20:15:27 +00:00
|
|
|
}
|
|
|
|
|
2018-02-02 15:04:17 +00:00
|
|
|
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;
|
2019-02-23 06:12:43 +00:00
|
|
|
widget_class->root = drawing_area_root;
|
|
|
|
widget_class->unroot = drawing_area_unroot;
|
2018-06-26 20:15:27 +00:00
|
|
|
|
|
|
|
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);
|
2018-02-02 15:04:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
drawing_area_apply_stroke (DrawingArea *area,
|
|
|
|
GdkDeviceTool *tool,
|
2020-07-24 20:32:16 +00:00
|
|
|
double x,
|
|
|
|
double y,
|
|
|
|
double pressure)
|
2018-02-02 15:04:17 +00:00
|
|
|
{
|
|
|
|
if (gdk_device_tool_get_tool_type (tool) == GDK_DEVICE_TOOL_TYPE_ERASER)
|
|
|
|
{
|
2018-06-26 20:15:27 +00:00
|
|
|
cairo_set_line_width (area->cr, 10 * pressure * area->brush_size);
|
2018-02-02 15:04:17 +00:00
|
|
|
cairo_set_operator (area->cr, CAIRO_OPERATOR_DEST_OUT);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2018-06-26 20:15:27 +00:00
|
|
|
cairo_set_line_width (area->cr, 4 * pressure * area->brush_size);
|
2018-02-02 15:04:17 +00:00
|
|
|
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,
|
2020-07-24 20:32:16 +00:00
|
|
|
double x,
|
|
|
|
double y,
|
2018-02-02 15:04:17 +00:00
|
|
|
DrawingArea *area)
|
|
|
|
{
|
|
|
|
cairo_new_path (area->cr);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
stylus_gesture_motion (GtkGestureStylus *gesture,
|
2020-07-24 20:32:16 +00:00
|
|
|
double x,
|
|
|
|
double y,
|
2018-02-02 15:04:17 +00:00
|
|
|
DrawingArea *area)
|
|
|
|
{
|
|
|
|
GdkTimeCoord *backlog;
|
|
|
|
GdkDeviceTool *tool;
|
2020-07-24 20:32:16 +00:00
|
|
|
double pressure;
|
2018-02-02 15:04:17 +00:00
|
|
|
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].axes[GDK_AXIS_PRESSURE]);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2018-04-26 15:32:43 +00:00
|
|
|
GtkGesture *gesture;
|
|
|
|
|
|
|
|
gesture = gtk_gesture_stylus_new ();
|
|
|
|
g_signal_connect (gesture, "down",
|
2018-02-02 15:04:17 +00:00
|
|
|
G_CALLBACK (stylus_gesture_down), area);
|
2018-04-26 15:32:43 +00:00
|
|
|
g_signal_connect (gesture, "motion",
|
2018-02-02 15:04:17 +00:00
|
|
|
G_CALLBACK (stylus_gesture_motion), area);
|
2018-04-26 15:32:43 +00:00
|
|
|
gtk_widget_add_controller (GTK_WIDGET (area), GTK_EVENT_CONTROLLER (gesture));
|
2018-02-02 15:04:17 +00:00
|
|
|
|
|
|
|
area->draw_color = (GdkRGBA) { 0, 0, 0, 1 };
|
2020-04-24 20:20:09 +00:00
|
|
|
area->brush_size = 1;
|
2018-02-02 15:04:17 +00:00
|
|
|
}
|
|
|
|
|
2020-03-07 14:01:21 +00:00
|
|
|
static GtkWidget *
|
2018-02-02 15:04:17 +00:00
|
|
|
drawing_area_new (void)
|
|
|
|
{
|
|
|
|
return g_object_new (drawing_area_get_type (), NULL);
|
|
|
|
}
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
static void
|
2018-02-02 15:04:17 +00:00
|
|
|
drawing_area_set_color (DrawingArea *area,
|
|
|
|
GdkRGBA *color)
|
|
|
|
{
|
2018-06-26 20:15:27 +00:00
|
|
|
if (gdk_rgba_equal (&area->draw_color, color))
|
|
|
|
return;
|
|
|
|
|
2018-02-02 15:04:17 +00:00
|
|
|
area->draw_color = *color;
|
2018-06-26 20:15:27 +00:00
|
|
|
g_signal_emit (area, area_signals[COLOR_SET], 0, &area->draw_color);
|
2018-02-02 15:04:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
color_button_color_set (GtkColorButton *button,
|
|
|
|
DrawingArea *draw_area)
|
|
|
|
{
|
|
|
|
GdkRGBA color;
|
|
|
|
|
|
|
|
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
|
|
|
|
drawing_area_set_color (draw_area, &color);
|
|
|
|
}
|
|
|
|
|
2018-06-26 20:15:27 +00:00
|
|
|
static void
|
|
|
|
drawing_area_color_set (DrawingArea *area,
|
|
|
|
GdkRGBA *color,
|
|
|
|
GtkColorButton *button)
|
|
|
|
{
|
|
|
|
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (button), color);
|
|
|
|
}
|
|
|
|
|
2018-02-02 15:04:17 +00:00
|
|
|
GtkWidget *
|
|
|
|
do_paint (GtkWidget *toplevel)
|
|
|
|
{
|
|
|
|
static GtkWidget *window = NULL;
|
|
|
|
|
|
|
|
if (!window)
|
|
|
|
{
|
|
|
|
GtkWidget *draw_area, *headerbar, *colorbutton;
|
|
|
|
|
2020-02-14 19:55:36 +00:00
|
|
|
window = gtk_window_new ();
|
2018-02-02 15:04:17 +00:00
|
|
|
|
|
|
|
draw_area = drawing_area_new ();
|
2020-05-02 21:26:54 +00:00
|
|
|
gtk_window_set_child (GTK_WINDOW (window), draw_area);
|
2018-02-02 15:04:17 +00:00
|
|
|
|
|
|
|
headerbar = gtk_header_bar_new ();
|
|
|
|
|
|
|
|
colorbutton = gtk_color_button_new ();
|
|
|
|
g_signal_connect (colorbutton, "color-set",
|
|
|
|
G_CALLBACK (color_button_color_set), draw_area);
|
2018-06-26 20:15:27 +00:00
|
|
|
g_signal_connect (draw_area, "color-set",
|
|
|
|
G_CALLBACK (drawing_area_color_set), colorbutton);
|
2018-02-02 15:04:17 +00:00
|
|
|
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (colorbutton),
|
|
|
|
&(GdkRGBA) { 0, 0, 0, 1 });
|
|
|
|
|
|
|
|
gtk_header_bar_pack_end (GTK_HEADER_BAR (headerbar), colorbutton);
|
|
|
|
gtk_window_set_titlebar (GTK_WINDOW (window), headerbar);
|
2020-04-30 23:04:08 +00:00
|
|
|
gtk_window_set_title (GTK_WINDOW (window), "Paint");
|
2020-05-09 16:03:11 +00:00
|
|
|
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window);
|
2018-02-02 15:04:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!gtk_widget_get_visible (window))
|
|
|
|
gtk_widget_show (window);
|
|
|
|
else
|
2020-05-09 14:26:22 +00:00
|
|
|
gtk_window_destroy (GTK_WINDOW (window));
|
2018-02-02 15:04:17 +00:00
|
|
|
|
|
|
|
return window;
|
|
|
|
}
|