diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 08b90b677a..39d6ad2db8 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -181,6 +181,11 @@ fontify.h main.ui + + demo3widget.c + demo3widget.h + demo3widget.ui + shortcuts.ui shortcuts-builder.ui @@ -265,6 +270,7 @@ list_store.c main.c markup.c + menu.c overlay.c overlay2.c paint.c diff --git a/demos/gtk-demo/demo3widget.c b/demos/gtk-demo/demo3widget.c new file mode 100644 index 0000000000..3a309927e8 --- /dev/null +++ b/demos/gtk-demo/demo3widget.c @@ -0,0 +1,243 @@ +#include +#include "demo3widget.h" + +enum +{ + PROP_PAINTABLE = 1, + PROP_SCALE +}; + +struct _Demo3Widget +{ + GtkWidget parent_instance; + + GdkPaintable *paintable; + float scale; + + GtkWidget *menu; +}; + +struct _Demo3WidgetClass +{ + GtkWidgetClass parent_class; +}; + +G_DEFINE_TYPE (Demo3Widget, demo3_widget, GTK_TYPE_WIDGET) + +static void +demo3_widget_init (Demo3Widget *self) +{ + self->scale = 1.f; + gtk_widget_init_template (GTK_WIDGET (self)); +} + +static void +demo3_widget_dispose (GObject *object) +{ + Demo3Widget *self = DEMO3_WIDGET (object); + + g_clear_object (&self->paintable); + g_clear_pointer (&self->menu, gtk_widget_unparent); + + G_OBJECT_CLASS (demo3_widget_parent_class)->dispose (object); +} + +static void +demo3_widget_snapshot (GtkWidget *widget, + GtkSnapshot *snapshot) +{ + Demo3Widget *self = DEMO3_WIDGET (widget); + int x, y, width, height; + double w, h; + + width = gtk_widget_get_width (widget); + height = gtk_widget_get_height (widget); + + w = self->scale * gdk_paintable_get_intrinsic_width (self->paintable); + h = self->scale * gdk_paintable_get_intrinsic_height (self->paintable); + + x = MAX (0, (width - ceil (w)) / 2); + y = MAX (0, (height - ceil (h)) / 2); + + gtk_snapshot_push_clip (snapshot, &GRAPHENE_RECT_INIT (0, 0, width, height)); + gtk_snapshot_save (snapshot); + gtk_snapshot_translate (snapshot, &GRAPHENE_POINT_INIT (x, y)); + gdk_paintable_snapshot (self->paintable, snapshot, w, h); + gtk_snapshot_restore (snapshot); + gtk_snapshot_pop (snapshot); +} + +static void +demo3_widget_measure (GtkWidget *widget, + GtkOrientation orientation, + int for_size, + int *minimum, + int *natural, + int *minimum_baseline, + int *natural_baseline) +{ + Demo3Widget *self = DEMO3_WIDGET (widget); + int size; + + if (orientation == GTK_ORIENTATION_HORIZONTAL) + size = gdk_paintable_get_intrinsic_width (self->paintable); + else + size = gdk_paintable_get_intrinsic_height (self->paintable); + + *minimum = *natural = self->scale * size; +} + +static void +demo3_widget_size_allocate (GtkWidget *widget, + int width, + int height, + int baseline) +{ + Demo3Widget *self = DEMO3_WIDGET (widget); + + /* Since we are not using a layout manager (who would do this + * for us), we need to allocate a size for our menu by calling + * gtk_native_check_resize(). + */ + gtk_native_check_resize (GTK_NATIVE (self->menu)); +} + +static void +demo3_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + Demo3Widget *self = DEMO3_WIDGET (object); + + switch (prop_id) + { + case PROP_PAINTABLE: + g_clear_object (&self->paintable); + self->paintable = g_value_dup_object (value); + gtk_widget_queue_resize (GTK_WIDGET (object)); + break; + + case PROP_SCALE: + self->scale = g_value_get_float (value); + gtk_widget_queue_resize (GTK_WIDGET (object)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +demo3_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + Demo3Widget *self = DEMO3_WIDGET (object); + + switch (prop_id) + { + case PROP_PAINTABLE: + g_value_set_object (value, self->paintable); + break; + + case PROP_SCALE: + g_value_set_float (value, self->scale); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +pressed_cb (GtkGestureClick *gesture, + guint n_press, + double x, + double y, + Demo3Widget *self) +{ + /* We are placing our menu at the point where + * the click happened, before popping it up. + */ + gtk_popover_set_pointing_to (GTK_POPOVER (self->menu), + &(const GdkRectangle){ x, y, 1, 1 }); + gtk_popover_popup (GTK_POPOVER (self->menu)); +} + +static void +zoom_cb (GtkWidget *widget, + const char *action_name, + GVariant *parameter) +{ + Demo3Widget *self = DEMO3_WIDGET (widget); + float scale; + + if (g_str_equal (action_name, "zoom.in")) + scale = MIN (10, self->scale * M_SQRT2); + else if (g_str_equal (action_name, "zoom.out")) + scale = MAX (0.01, self->scale / M_SQRT2); + else + scale = 1.0; + + gtk_widget_action_set_enabled (widget, "zoom.in", scale < 10); + gtk_widget_action_set_enabled (widget, "zoom.out", scale > 0.01); + gtk_widget_action_set_enabled (widget, "zoom.reset", scale != 1); + + g_object_set (widget, "scale", scale, NULL); +} + +static void +demo3_widget_class_init (Demo3WidgetClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + object_class->dispose = demo3_widget_dispose; + object_class->set_property = demo3_widget_set_property; + object_class->get_property = demo3_widget_get_property; + + widget_class->snapshot = demo3_widget_snapshot; + widget_class->measure = demo3_widget_measure; + widget_class->size_allocate = demo3_widget_size_allocate; + + g_object_class_install_property (object_class, PROP_PAINTABLE, + g_param_spec_object ("paintable", "Paintable", "Paintable", + GDK_TYPE_PAINTABLE, + G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, PROP_SCALE, + g_param_spec_float ("scale", "Scale", "Scale", + 0.0, 10.0, 1.0, + G_PARAM_READWRITE)); + + /* These are the actions that we are using in the menu */ + gtk_widget_class_install_action (widget_class, "zoom.in", NULL, zoom_cb); + gtk_widget_class_install_action (widget_class, "zoom.out", NULL, zoom_cb); + gtk_widget_class_install_action (widget_class, "zoom.reset", NULL, zoom_cb); + + gtk_widget_class_set_template_from_resource (widget_class, "/menu/demo3widget.ui"); + gtk_widget_class_bind_template_child (widget_class, Demo3Widget, menu); + gtk_widget_class_bind_template_callback (widget_class, pressed_cb); +} + +GtkWidget * +demo3_widget_new (const char *resource) +{ + Demo3Widget *self; + GdkPixbuf *pixbuf; + GdkPaintable *paintable; + + pixbuf = gdk_pixbuf_new_from_resource (resource, NULL); + paintable = GDK_PAINTABLE (gdk_texture_new_for_pixbuf (pixbuf)); + + self = g_object_new (DEMO3_TYPE_WIDGET, "paintable", paintable, NULL); + + g_object_unref (pixbuf); + g_object_unref (paintable); + + return GTK_WIDGET (self); +} diff --git a/demos/gtk-demo/demo3widget.h b/demos/gtk-demo/demo3widget.h new file mode 100644 index 0000000000..5bf86bb855 --- /dev/null +++ b/demos/gtk-demo/demo3widget.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#define DEMO3_TYPE_WIDGET (demo3_widget_get_type ()) +G_DECLARE_FINAL_TYPE (Demo3Widget, demo3_widget, DEMO3, WIDGET, GtkWidget) + +GtkWidget * demo3_widget_new (const char *resource); diff --git a/demos/gtk-demo/demo3widget.ui b/demos/gtk-demo/demo3widget.ui new file mode 100644 index 0000000000..71f1f57255 --- /dev/null +++ b/demos/gtk-demo/demo3widget.ui @@ -0,0 +1,30 @@ + + + + Zoom Out + zoom.out + + + Zoom In + zoom.in + + + 1∶1 + zoom.reset + + + + diff --git a/demos/gtk-demo/menu.c b/demos/gtk-demo/menu.c new file mode 100644 index 0000000000..cdcfd0ed20 --- /dev/null +++ b/demos/gtk-demo/menu.c @@ -0,0 +1,47 @@ +/* Menu + * #Keywords: action, zoom + * + * Demonstrates how to add a context menu to a custom widget + * and connect it with widget actions. + * + * The custom widget we create here is similar to a GtkPicture, + * but allows setting a zoom level for the displayed paintable. + * + * Our context menu has items to change the zoom level. + */ + +#include +#include "demo3widget.h" + + +GtkWidget * +do_menu (GtkWidget *do_widget) +{ + static GtkWidget *window = NULL; + + if (!window) + { + GtkWidget *sw; + GtkWidget *widget; + + window = gtk_window_new (); + gtk_window_set_title (GTK_WINDOW (window), "Menu"); + gtk_window_set_default_size (GTK_WINDOW (window), 600, 400); + gtk_window_set_display (GTK_WINDOW (window), + gtk_widget_get_display (do_widget)); + g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + + sw = gtk_scrolled_window_new (); + gtk_window_set_child (GTK_WINDOW (window), sw); + + widget = demo3_widget_new ("/transparent/portland-rose.jpg"); + gtk_scrolled_window_set_child (GTK_SCROLLED_WINDOW (sw), widget); + } + + if (!gtk_widget_get_visible (window)) + gtk_widget_show (window); + else + gtk_window_destroy (GTK_WINDOW (window)); + + return window; +} diff --git a/demos/gtk-demo/meson.build b/demos/gtk-demo/meson.build index a60f121896..a32fee88e5 100644 --- a/demos/gtk-demo/meson.build +++ b/demos/gtk-demo/meson.build @@ -44,6 +44,7 @@ demos = files([ 'links.c', 'listbox.c', 'listbox2.c', + 'menu.c', 'flowbox.c', 'list_store.c', 'listview_applauncher.c', @@ -112,7 +113,8 @@ extra_demo_sources = files(['main.c', 'demo2layout.c', 'singular_value_decomposition.c', 'four_point_transform.c', - 'demo2widget.c']) + 'demo2widget.c', + 'demo3widget.c']) if harfbuzz_dep.found() and pangoft_dep.found() demos += files('font_features.c')