gtk/demos/gtk-demo/paint.c
Matthias Clasen d732c869c2 gtk-demo: Add keywords to demos
Add the names of the main widgets as keywords to
our demos, but also things like "game". This helps
finding relevant demos in our growing list. You
can now for example type "label", and find the
"error states" and "links" demos showing GtkLabel
features.
2020-09-13 15:00:14 -04:00

418 lines
12 KiB
C

/* 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;
} 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" },
};
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,
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)
{
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
));
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);
}
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
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
{
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 (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].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)
{
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;
}
static GtkWidget *
drawing_area_new (void)
{
return g_object_new (drawing_area_get_type (), NULL);
}
static void
drawing_area_set_color (DrawingArea *area,
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 (GtkColorButton *button,
DrawingArea *draw_area)
{
GdkRGBA color;
gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &color);
drawing_area_set_color (draw_area, &color);
}
static void
drawing_area_color_set (DrawingArea *area,
GdkRGBA *color,
GtkColorButton *button)
{
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (button), color);
}
GtkWidget *
do_paint (GtkWidget *toplevel)
{
static GtkWidget *window = NULL;
if (!window)
{
GtkWidget *draw_area, *headerbar, *colorbutton;
window = gtk_window_new ();
draw_area = drawing_area_new ();
gtk_window_set_child (GTK_WINDOW (window), draw_area);
headerbar = gtk_header_bar_new ();
colorbutton = gtk_color_button_new ();
g_signal_connect (colorbutton, "color-set",
G_CALLBACK (color_button_color_set), draw_area);
g_signal_connect (draw_area, "color-set",
G_CALLBACK (drawing_area_color_set), colorbutton);
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);
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_show (window);
else
gtk_window_destroy (GTK_WINDOW (window));
return window;
}