Root/eeshow/gui/over.c

1/*
2 * gui/over.c - GUI: overlays
3 *
4 * Written 2016 by Werner Almesberger
5 * Copyright 2016 by Werner Almesberger
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 */
12
13/*
14 * Resources:
15 *
16 * http://zetcode.com/gfx/cairo/cairobackends/
17 * https://developer.gnome.org/gtk3/stable/gtk-migrating-2-to-3.html
18 * https://www.cairographics.org/samples/rounded_rectangle/
19 *
20 * Section "Description" in
21 * https://developer.gnome.org/pango/stable/pango-Cairo-Rendering.html
22 */
23
24#include <stddef.h>
25#include <stdarg.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <math.h>
29#include <assert.h>
30
31#include <cairo/cairo.h>
32#include <pango/pangocairo.h>
33
34#include "misc/util.h"
35#include "gui/fmt-pango.h"
36#include "gui/aoi.h"
37#include "gui/style.h"
38#include "gui/over.h"
39
40
41struct overlay {
42    const char *s;
43    cairo_surface_t *icon;
44
45    struct overlay_style style;
46
47    struct aoi **aois;
48    bool (*hover)(void *user, bool on, int dx, int dy);
49    void (*click)(void *user);
50    void *user;
51
52    struct aoi *aoi;
53    const struct overlay *related;
54
55    struct overlay *next, *prev;
56};
57
58
59/* ----- Drawing helper functions ------------------------------------------ */
60
61
62static void rrect(cairo_t *cr, double x, double y, double w, double h, int r)
63{
64    const double deg = M_PI / 180.0;
65
66    cairo_new_path(cr);
67    cairo_arc(cr, x + w - r, y + r, r, -90 * deg, 0);
68    cairo_arc(cr, x + w - r, y + h - r, r, 0, 90 * deg);
69    cairo_arc(cr, x + r, y + h - r, r, 90 * deg, 180 * deg);
70    cairo_arc(cr, x + r, y + r, r, 180 * deg, 270 * deg);
71    cairo_close_path(cr);
72}
73
74
75static void background(const struct overlay *over, cairo_t *cr,
76    int x, int y, unsigned w, unsigned h)
77{
78    const struct overlay_style *style = &over->style;
79    const struct color *bg = &style->bg;
80    const struct color *frame = &style->frame;
81    double center;
82
83    center = style->width / 2.0;
84    rrect(cr, x - center, y - center, w + style->width, h + style->width,
85        style->radius);
86
87    cairo_set_source_rgba(cr, bg->r, bg->g, bg->b, bg->alpha);
88    cairo_fill_preserve(cr);
89    cairo_set_source_rgba(cr, frame->r, frame->g, frame->b, frame->alpha);
90    cairo_set_line_width(cr, style->width);
91    cairo_stroke(cr);
92}
93
94
95static void post_aoi(struct overlay *over, int x, int y, unsigned w, unsigned h)
96{
97    struct aoi aoi_cfg = {
98        .x = x,
99        .y = y,
100        .w = w,
101        .h = h,
102        .hover = over->hover,
103        .click = over->click,
104        .user = over->user,
105    };
106
107
108    if (!over->hover && !over->click)
109        return;
110
111    if (over->aoi) {
112        aoi_update(over->aoi, &aoi_cfg);
113    } else {
114        over->aoi = aoi_add(over->aois, &aoi_cfg);
115        if (over->related) {
116            assert(over->related->aoi);
117            aoi_set_related(over->aoi, over->related->aoi);
118        }
119    }
120}
121
122
123/* ----- Drawing text ------------------------------------------------------ */
124
125
126static unsigned overlay_draw_text(struct overlay *over, cairo_t *cr,
127    int x, int y, int dx, int dy)
128{
129    const struct overlay_style *style = &over->style;
130    const struct color *fg = &style->fg;
131    unsigned ink_w, ink_h; /* effectively used text area size */
132    unsigned w, h; /* box size */
133    int tx, ty; /* text start position */
134
135    PangoLayout *layout;
136    PangoFontDescription *desc;
137    PangoRectangle ink_rect;
138
139    desc = pango_font_description_from_string(style->font);
140    layout = pango_cairo_create_layout(cr);
141    pango_layout_set_font_description(layout, desc);
142    pango_layout_set_markup(layout, over->s, -1);
143    pango_font_description_free(desc);
144
145    pango_layout_get_extents(layout, &ink_rect, NULL);
146#if 0
147fprintf(stderr, "%d + %d %d + %d\n",
148    ink_rect.x / PANGO_SCALE, ink_rect.width / PANGO_SCALE,
149    ink_rect.y / PANGO_SCALE, ink_rect.height / PANGO_SCALE);
150#endif
151    ink_w = ink_rect.width / PANGO_SCALE;
152    ink_h = ink_rect.height / PANGO_SCALE;
153
154    ink_w = ink_w > style->wmin ? ink_w : style->wmin;
155    ink_w = !style->wmax || ink_w < style->wmax ? ink_w : style->wmax;
156    w = ink_w + 2 * style->pad;
157
158    ink_h = ink_h > style->hmin ? ink_h : style->hmin;
159    ink_h = !style->hmax || ink_h < style->hmax ? ink_h : style->hmax;
160    h = ink_h + 2 * style->pad;
161
162    if (dx < 0)
163        x -= w;
164    if (dy < 0)
165        y -= h;
166
167    tx = x - ink_rect.x / PANGO_SCALE + style->pad;
168    ty = y - ink_rect.y / PANGO_SCALE + style->pad;
169
170    background(over, cr, x, y, w, h);
171
172    if (style->wmax) {
173        cairo_new_path(cr);
174#if 0
175fprintf(stderr, "%u(%d) %u %.60s\n", ty, ink_rect.y / PANGO_SCALE, ink_h, over->s);
176#endif
177/*
178 * @@@ for some mysterious reason, we get
179 * ink_h = ink_rect.height / PANGO_SCALE = 5
180 * instead of 2 if using overlay_style_dense_selected. Strangely, changing
181 * overlay_style_dense_selected such that it becomes more like
182 * overlay_style_dense has no effect.
183 *
184 * This causes the text to be cut vertically, roughly in the middle. We hack
185 * around this problem by growind the clipping area vertically. This works,
186 * since we're currently only concerned about horizontal clipping anyway.
187 */
188
189        cairo_rectangle(cr, tx, ty, ink_w, ink_h + 20);
190        cairo_clip(cr);
191    }
192
193    cairo_set_source_rgba(cr, fg->r, fg->g, fg->b, fg->alpha);
194    cairo_move_to(cr, tx, ty);
195
196    pango_cairo_update_layout(cr, layout);
197    pango_cairo_show_layout(cr, layout);
198    cairo_reset_clip(cr);
199    g_object_unref(layout);
200
201    post_aoi(over, x, y, w, h);
202
203    return h;
204}
205
206
207/* ----- Drawing an icon --------------------------------------------------- */
208
209
210static unsigned overlay_draw_icon(struct overlay *over, cairo_t *cr,
211    int x, int y, int dx, int dy)
212{
213    const struct overlay_style *style = &over->style;
214    unsigned iw, ih; /* icon size */
215    unsigned w, h; /* box size */
216    int ix, iy; /* icon start position */
217
218    iw = cairo_image_surface_get_width(over->icon);
219    iw = iw > style->wmin ? iw : style->wmin;
220    iw = !style->wmax || iw < style->wmax ? iw : style->wmax;
221
222    ih = cairo_image_surface_get_height(over->icon);
223    ih = ih > style->hmin ? ih : style->hmin;
224    ih = !style->hmax || ih < style->hmax ? ih : style->hmax;
225
226    w = iw + 2 * style->pad;
227    h = ih + 2 * style->pad;
228
229    if (dx < 0)
230        x -= w;
231    if (dy < 0)
232        y -= h;
233
234    ix = x + style->pad;
235    iy = y + style->pad;
236
237    background(over, cr, x, y, w, h);
238
239    cairo_set_source_surface(cr, over->icon, ix, iy);
240    cairo_paint(cr);
241
242    post_aoi(over, x, y, w, h);
243
244    return h;
245}
246
247
248/* ----- Drawing interfaces ------------------------------------------------ */
249
250
251static unsigned overlay_draw(struct overlay *over, cairo_t *cr,
252    int x, int y, int dx, int dy)
253{
254    if (over->s)
255        return overlay_draw_text(over, cr, x, y, dx, dy);
256    else
257        return overlay_draw_icon(over, cr, x, y, dx, dy);
258}
259
260
261void overlay_draw_all_d(struct overlay *overlays, cairo_t *cr,
262    int x, int y, int dx, int dy)
263{
264    struct overlay *over = overlays;
265    unsigned h;
266
267    if (dy < 0)
268        while (over && over->next)
269            over = over->next;
270    while (over) {
271        h = overlay_draw(over, cr, x, y, dx, dy);
272        y += dy * (h + over->style.skip);
273        if (dy >= 0)
274            over = over->next;
275        else
276            over = over->prev;
277        
278    }
279}
280
281
282void overlay_draw_all(struct overlay *overlays, cairo_t *cr, int x, int y)
283{
284    int dx = 1;
285    int dy = 1;
286
287    if (x < 0 || y < 0) {
288        double x1, y1, x2, y2;
289        int sw, sh;
290
291        cairo_clip_extents(cr, &x1, &y1, &x2, &y2);
292        sw = x2 - x1;
293        sh = y2 - y1;
294        if (x < 0) {
295            x = sw + x;
296            dx = -1;
297        }
298        if (y < 0) {
299            y = sh + y;
300            dy = -1;
301        }
302    }
303
304    overlay_draw_all_d(overlays, cr, x, y, dx, dy);
305}
306
307
308/* ----- Sizing text ------------------------------------------------------- */
309
310
311static void overlay_size_text(const struct overlay *over,
312    PangoContext *pango_context, int *w, int *h)
313{
314    const struct overlay_style *style = &over->style;
315    PangoLayout *layout;
316    PangoFontDescription *desc;
317    PangoRectangle ink_rect;
318    unsigned ink_w, ink_h; /* effectively used text area size */
319
320    /*
321     * Note that we need the caller to provide the Cairo context, because
322     * the font size changes subtly even between image (which we could
323     * create locally) and screen (which is better left to the outside
324     * world).
325     */
326
327    desc = pango_font_description_from_string(style->font);
328    layout = pango_layout_new(pango_context);
329    pango_layout_set_font_description(layout, desc);
330    pango_layout_set_markup(layout, over->s, -1);
331    pango_font_description_free(desc);
332
333    pango_layout_get_extents(layout, &ink_rect, NULL);
334    g_object_unref(layout);
335
336    ink_w = ink_rect.width / PANGO_SCALE;
337    ink_h = ink_rect.height / PANGO_SCALE;
338
339    ink_w = ink_w > style->wmin ? ink_w : style->wmin;
340    ink_w = !style->wmax || ink_w < style->wmax ? ink_w : style->wmax;
341
342    ink_h = ink_h > style->hmin ? ink_h : style->hmin;
343    ink_h = !style->hmax || ink_h < style->hmax ? ink_h : style->hmax;
344
345    if (w)
346        *w = ink_w + 2 * style->pad;
347    if (h)
348        *h = ink_h + 2 * style->pad;
349}
350
351
352/* ----- Sizing icons ------------------------------------------------------ */
353
354
355static void overlay_size_icon(const struct overlay *over, int *w, int *h)
356{
357    const struct overlay_style *style = &over->style;
358    unsigned iw, ih; /* effectively used icon size */
359
360    iw = cairo_image_surface_get_width(over->icon);
361    iw = iw > style->wmin ? iw : style->wmin;
362    iw = !style->wmax || iw < style->wmax ? iw : style->wmax;
363
364    ih = cairo_image_surface_get_height(over->icon);
365    ih = ih > style->hmin ? ih : style->hmin;
366    ih = !style->hmax || ih < style->hmax ? ih : style->hmax;
367
368    if (w)
369        *w = iw + 2 * style->pad;
370    if (h)
371        *h = ih + 2 * style->pad;
372}
373
374
375/* ----- Sizing ------------------------------------------------------------ */
376
377
378void overlay_size(const struct overlay *over, PangoContext *pango_context,
379    int *w, int *h)
380{
381    if (over->s)
382        overlay_size_text(over, pango_context, w, h);
383    else
384        overlay_size_icon(over, w, h);
385}
386
387
388void overlay_size_all(const struct overlay *overlays,
389    PangoContext *pango_context, bool dx, bool dy, int *w, int *h)
390{
391    const struct overlay *over;
392    int w1, h1;
393
394    if (w)
395        *w = 0;
396    if (h)
397        *h = 0;
398
399    for (over = overlays; over; over = over->next) {
400        int skip = over == overlays ? 0 : over->style.skip;
401
402        overlay_size(over, pango_context, &w1, &h1);
403        if (w) {
404            if (dx)
405                *w += w1 + skip;
406            else
407                *w = *w > w1 ? *w : w1;
408        }
409        if (h) {
410            if (dy)
411                *h += h1 + skip;
412            else
413                *h = *h > h1 ? *h : h1;
414        }
415    }
416}
417
418
419/* ----- Creation ---------------------------------------------------------- */
420
421
422struct overlay *overlay_add(struct overlay **overlays, struct aoi **aois,
423    bool (*hover)(void *user, bool on, int dx, int dy),
424    void (*click)(void *user), void *user)
425{
426    struct overlay *over, *prev;
427    struct overlay **anchor;
428
429    over = alloc_type(struct overlay);
430    over->s = NULL;
431    over->icon = NULL;
432    over->style = overlay_style_default;
433
434    over->aois = aois;
435    over->hover = hover;
436    over->click = click;
437    over->user = user;
438    over->aoi = NULL;
439    over->related = NULL;
440
441    prev = NULL;
442    for (anchor = overlays; *anchor; anchor = &(*anchor)->next)
443        prev = *anchor;
444    over->next = NULL;
445    over->prev = prev;
446    *anchor = over;
447
448    return over;
449}
450
451
452/* ----- Configuration ----------------------------------------------------- */
453
454
455void overlay_style(struct overlay *over, const struct overlay_style *style)
456{
457    over->style = *style;
458}
459
460
461void overlay_text_raw(struct overlay *over, const char *s)
462{
463    free((char *) over->s);
464    over->s = stralloc(s);
465}
466
467
468void overlay_text(struct overlay *over, const char *fmt, ...)
469{
470    va_list ap;
471
472    va_start(ap, fmt);
473    overlay_text_raw(over, vfmt_pango(fmt, ap));
474    va_end(ap);
475}
476
477
478void overlay_icon(struct overlay *over, cairo_surface_t *icon)
479{
480    assert(!over->s);
481    over->icon = cairo_surface_reference(icon);
482}
483
484
485/* ----- Nesting ----------------------------------------------------------- */
486
487
488void overlay_set_related(struct overlay *over, struct overlay *related)
489{
490    /*
491     * Relatedness is a property that only matters to AoIs, but we have to
492     * defer propagating it because we only know our AoI after drawing the
493     * overlay.
494     */
495
496    assert(!over->related);
497    over->related = related;
498}
499
500
501void overlay_set_related_all(struct overlay *overlays, struct overlay *related)
502{
503    struct overlay *over;
504
505    for (over = overlays; over; over = over->next)
506        overlay_set_related(over, related);
507}
508
509
510/* ----- Removal ----------------------------------------------------------- */
511
512
513static void overlay_free(struct overlay *over)
514{
515    if (over->aoi)
516        aoi_remove(over->aois, over->aoi);
517    free((void *) over->s);
518    if (!over->s)
519        cairo_surface_destroy(over->icon);
520    free(over);
521}
522
523
524void overlay_remove(struct overlay **overlays, struct overlay *over)
525{
526    if (over->next)
527        over->next->prev = over->prev;
528    if (over->prev)
529        over->prev->next = over->next;
530    else
531        *overlays = over->next;
532    overlay_free(over);
533}
534
535
536void overlay_remove_all(struct overlay **overlays)
537{
538    struct overlay *next;
539
540    while (*overlays) {
541        next = (*overlays)->next;
542        overlay_remove(overlays, *overlays);
543        *overlays = next;
544    }
545}
546

Archive Download this file

Branches:
master



interactive