summaryrefslogtreecommitdiff
path: root/gtkpopover/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'gtkpopover/main.c')
-rw-r--r--gtkpopover/main.c193
1 files changed, 193 insertions, 0 deletions
diff --git a/gtkpopover/main.c b/gtkpopover/main.c
new file mode 100644
index 0000000..2e2ea30
--- /dev/null
+++ b/gtkpopover/main.c
@@ -0,0 +1,193 @@
+// 10 october 2014
+// #qo pkg-config: gtk+-3.0
+#include <gtk/gtk.h>
+
+typedef gint LONG;
+
+typedef struct POINT POINT;
+
+struct POINT {
+ LONG x;
+ LONG y;
+};
+
+struct popover {
+ void *gopopover;
+
+ // a nice consequence of this design is that it allows four arrowheads to jut out at once; in practice only one will ever be used, but hey — simple implementation!
+ LONG arrowLeft;
+ LONG arrowRight;
+ LONG arrowTop;
+ LONG arrowBottom;
+};
+
+struct popover _p = { NULL, -1, -1, 20, -1 };
+struct popover *p = &_p;
+
+#define ARROWHEIGHT 8
+#define ARROWWIDTH 8 /* should be the same for smooth lines */
+
+void makePopoverPath(cairo_t *cr, LONG width, LONG height)
+{
+ POINT pt[20];
+ int n;
+ LONG xmax, ymax;
+
+ cairo_new_path(cr);
+ n = 0;
+
+ // figure out the xmax and ymax of the box
+ xmax = width;
+ if (p->arrowRight >= 0)
+ xmax -= ARROWWIDTH;
+ ymax = height;
+ if (p->arrowBottom >= 0)
+ ymax -= ARROWHEIGHT;
+
+ // the first point is either at (0,0), (0,arrowHeight), (arrowWidth,0), or (arrowWidth,arrowHeight)
+ pt[n].x = 0;
+ if (p->arrowLeft >= 0)
+ pt[n].x = ARROWWIDTH;
+ pt[n].y = 0;
+ if (p->arrowTop >= 0)
+ pt[n].y = ARROWHEIGHT;
+ n++;
+
+ // the left side
+ pt[n].x = pt[n - 1].x;
+ if (p->arrowLeft >= 0) {
+ pt[n].y = pt[n - 1].y + p->arrowLeft;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x;
+ }
+ pt[n].y = ymax;
+ n++;
+
+ // the bottom side
+ pt[n].y = pt[n - 1].y;
+ if (p->arrowBottom >= 0) {
+ pt[n].x = pt[n - 1].x + p->arrowBottom;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].y = pt[n - 1].y;
+ }
+ pt[n].x = xmax;
+ n++;
+
+ // the right side
+ pt[n].x = pt[n - 1].x;
+ if (p->arrowRight >= 0) {
+ pt[n].y = pt[0].y + p->arrowRight + (ARROWHEIGHT * 2);
+ n++;
+ pt[n].x = pt[n - 1].x + ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x;
+ }
+ pt[n].y = pt[0].y;
+ n++;
+
+ // the top side
+ pt[n].y = pt[n - 1].y;
+ if (p->arrowTop >= 0) {
+ pt[n].x = pt[0].x + p->arrowTop + (ARROWWIDTH * 2);
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y - ARROWHEIGHT;
+ n++;
+ pt[n].x = pt[n - 1].x - ARROWWIDTH;
+ pt[n].y = pt[n - 1].y + ARROWHEIGHT;
+ n++;
+ pt[n].y = pt[n - 1].y;
+ }
+ pt[n].x = pt[0].x;
+ n++;
+
+ int i;
+
+ // TODO bypass subpixel rendering
+ cairo_move_to(cr, pt[0].x, pt[0].y);
+ for (i = 1; i < n; i++)
+ cairo_line_to(cr, pt[i].x, pt[i].y);
+}
+
+void drawPopoverFrame(GtkWidget *widget, cairo_t *cr, LONG width, LONG height, int forceAlpha)
+{
+ GtkStyleContext *context;
+ GdkRGBA background, border;
+
+ // TODO see what GtkPopover itself does
+ // TODO drop shadow
+ context = gtk_widget_get_style_context(widget);
+ gtk_style_context_get_background_color(context, GTK_STATE_FLAG_NORMAL, &background);
+ gtk_style_context_get_border_color(context, GTK_STATE_FLAG_NORMAL, &border);
+ if (forceAlpha) {
+ background.alpha = 1;
+ border.alpha = 1;
+ }
+ makePopoverPath(cr, width, height);
+ cairo_set_source_rgba(cr, background.red, background.green, background.blue, background.alpha);
+ cairo_fill_preserve(cr);
+ cairo_set_source_rgba(cr, border.red, border.green, border.blue, border.alpha);
+ cairo_stroke(cr);
+}
+
+gboolean popoverDraw(GtkWidget *widget, cairo_t *cr, gpointer data)
+{
+ gint width, height;
+
+ width = gtk_widget_get_allocated_width(widget);
+ height = gtk_widget_get_allocated_height(widget);
+ drawPopoverFrame(widget, cr, width, height, 0);
+ return FALSE;
+}
+
+void popoverSetSize(GtkWidget *widget, LONG width, LONG height)
+{
+ GdkWindow *gdkwin;
+ cairo_t *cr;
+ cairo_surface_t *cs;
+ cairo_region_t *region;
+
+ gtk_window_resize(GTK_WINDOW(widget), width, height);
+ gdkwin = gtk_widget_get_window(widget);
+ gdk_window_shape_combine_region(gdkwin, NULL, 0, 0);
+ // TODO check errors
+ cs = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
+ cr = cairo_create(cs);
+ drawPopoverFrame(widget, cr, width, height, 1);
+ region = gdk_cairo_region_create_from_surface(cs);
+ gdk_window_shape_combine_region(gdkwin, region, 0, 0);
+ cairo_destroy(cr);
+ cairo_surface_destroy(cs);
+}
+
+int main(void)
+{
+ GtkWidget *w;
+
+ gtk_init(NULL, NULL);
+ w = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_window_set_decorated(GTK_WINDOW(w), FALSE);
+ gtk_widget_set_app_paintable(w, TRUE);
+ g_signal_connect(w, "draw", G_CALLBACK(popoverDraw), NULL);
+ gtk_widget_realize(w);
+ popoverSetSize(w, 200, 200);
+ gtk_widget_show_all(w);
+ gtk_main();
+ return 0;
+}