bemenu 1.0.0
Dynamic menu library and client program inspired by dmenu
cairo.h
1 #ifndef _BM_CAIRO_H_
2 #define _BM_CAIRO_H_
3 
4 #include "internal.h"
5 #include <string.h>
6 #include <assert.h>
7 #include <math.h>
8 #include <cairo/cairo.h>
9 #include <pango/pangocairo.h>
10 
11 struct cairo {
12  cairo_t *cr;
13  cairo_surface_t *surface;
14  PangoContext *pango;
15 };
16 
17 struct cairo_color {
18  float r, g, b, a;
19 };
20 
21 struct cairo_paint {
22  struct cairo_color fg;
23  struct cairo_color bg;
24  const char *font;
25 
26  struct box {
27  int32_t lx, rx; // left/right offset (pos.x - lx, box.w + rx)
28  int32_t ty, by; // top/bottom offset (pos.y - ty, box.h + by)
29  int32_t w, h; // 0 for text width/height
30  } box;
31 
32  struct pos {
33  int32_t x, y;
34  } pos;
35 };
36 
37 struct cairo_result {
38  uint32_t x_advance;
39  uint32_t height;
40 };
41 
43  uint32_t displayed;
44  uint32_t height;
45 };
46 
47 static size_t blen = 0;
48 static char *buffer = NULL;
49 
50 __attribute__((unused)) static bool
51 bm_cairo_create_for_surface(struct cairo *cairo, cairo_surface_t *surface)
52 {
53  assert(cairo && surface);
54  if (!(cairo->cr = cairo_create(surface)))
55  goto fail;
56 
57  if (!(cairo->pango = pango_cairo_create_context(cairo->cr)))
58  goto fail;
59 
60  cairo->surface = surface;
61  return true;
62 
63 fail:
64  if (cairo->cr)
65  cairo_destroy(cairo->cr);
66  return false;
67 }
68 
69 __attribute__((unused)) static void
70 bm_cairo_destroy(struct cairo *cairo)
71 {
72  if (cairo->cr)
73  cairo_destroy(cairo->cr);
74  if (cairo->surface)
75  cairo_surface_destroy(cairo->surface);
76 }
77 
78 __attribute__((unused)) static PangoLayout*
79 bm_pango_get_layout(struct cairo *cairo, struct cairo_paint *paint, const char *buffer)
80 {
81  PangoLayout *layout = pango_cairo_create_layout(cairo->cr);
82  pango_layout_set_text(layout, buffer, -1);
83  PangoFontDescription *desc = pango_font_description_from_string(paint->font);
84  pango_layout_set_font_description(layout, desc);
85  pango_layout_set_single_paragraph_mode(layout, 1);
86  pango_font_description_free(desc);
87  return layout;
88 }
89 
90 __attribute__((unused)) BM_LOG_ATTR(4, 5) static bool
91 bm_pango_get_text_extents(struct cairo *cairo, struct cairo_paint *paint, struct cairo_result *result, const char *fmt, ...)
92 {
93  assert(cairo && paint && result && fmt);
94  memset(result, 0, sizeof(struct cairo_result));
95 
96  va_list args;
97  va_start(args, fmt);
98  bool ret = bm_vrprintf(&buffer, &blen, fmt, args);
99  va_end(args);
100 
101  if (!ret)
102  return false;
103 
104  PangoRectangle rect;
105  PangoLayout *layout = bm_pango_get_layout(cairo, paint, buffer);
106  pango_layout_get_pixel_extents(layout, &rect, NULL);
107  g_object_unref(layout);
108 
109  result->x_advance = rect.x + rect.width;
110  result->height = rect.height;
111  return true;
112 }
113 
114 __attribute__((unused)) BM_LOG_ATTR(4, 5) static bool
115 bm_cairo_draw_line(struct cairo *cairo, struct cairo_paint *paint, struct cairo_result *result, const char *fmt, ...)
116 {
117  assert(cairo && paint && result && fmt);
118  memset(result, 0, sizeof(struct cairo_result));
119 
120  va_list args;
121  va_start(args, fmt);
122  bool ret = bm_vrprintf(&buffer, &blen, fmt, args);
123  va_end(args);
124 
125  if (!ret)
126  return false;
127 
128  PangoLayout *layout = bm_pango_get_layout(cairo, paint, buffer);
129  pango_cairo_update_layout(cairo->cr, layout);
130 
131  int width, height;
132  pango_layout_get_pixel_size(layout, &width, &height);
133  int base = pango_layout_get_baseline(layout) / PANGO_SCALE;
134  int yoff = height - base;
135 
136  cairo_set_source_rgba(cairo->cr, paint->bg.r, paint->bg.b, paint->bg.g, paint->bg.a);
137  cairo_rectangle(cairo->cr,
138  paint->pos.x - paint->box.lx, paint->pos.y - paint->box.ty,
139  (paint->box.w > 0 ? paint->box.w : width) + paint->box.rx + paint->box.lx,
140  (paint->box.h > 0 ? paint->box.h : height) + paint->box.by + paint->box.ty);
141  cairo_fill(cairo->cr);
142 
143  cairo_set_source_rgba(cairo->cr, paint->fg.r, paint->fg.b, paint->fg.g, paint->fg.a);
144  cairo_move_to(cairo->cr, paint->box.lx + paint->pos.x, paint->pos.y - yoff + paint->box.ty);
145  pango_cairo_show_layout(cairo->cr, layout);
146 
147  g_object_unref(layout);
148 
149  result->x_advance = width + paint->box.rx;
150  result->height = height + paint->box.by;
151  return true;
152 }
153 
154 __attribute__((unused)) static void
155 bm_cairo_color_from_menu_color(const struct bm_menu *menu, enum bm_color color, struct cairo_color *c)
156 {
157  assert(menu);
158  c->r = (float)menu->colors[color].r / 255.0f;
159  c->g = (float)menu->colors[color].g / 255.0f;
160  c->b = (float)menu->colors[color].b / 255.0f;
161  c->a = 1.0f;
162 }
163 
164 __attribute__((unused)) static void
165 bm_cairo_paint(struct cairo *cairo, uint32_t width, uint32_t height, uint32_t max_height, const struct bm_menu *menu, struct cairo_paint_result *out_result)
166 {
167  assert(cairo && menu && out_result);
168 
169  memset(out_result, 0, sizeof(struct cairo_paint_result));
170  out_result->displayed = 1;
171 
172  cairo_set_source_rgb(cairo->cr, 0, 0, 0);
173  cairo_rectangle(cairo->cr, 0, 0, width, height);
174  cairo_fill(cairo->cr);
175 
176  struct cairo_paint paint;
177  memset(&paint, 0, sizeof(paint));
178  paint.font = menu->font.name;
179 
180  struct cairo_result result;
181  memset(&result, 0, sizeof(result));
182 
183  uint32_t title_x = 0;
184  if (menu->title) {
185  bm_cairo_color_from_menu_color(menu, BM_COLOR_TITLE_FG, &paint.fg);
186  bm_cairo_color_from_menu_color(menu, BM_COLOR_TITLE_BG, &paint.bg);
187  paint.pos = (struct pos){ result.x_advance, 2 };
188  paint.box = (struct box){ 4, 8, 2, 2, 0, 0 };
189  bm_cairo_draw_line(cairo, &paint, &result, "%s", menu->title);
190  title_x = result.x_advance;
191  }
192 
193  bm_cairo_color_from_menu_color(menu, BM_COLOR_FILTER_FG, &paint.fg);
194  bm_cairo_color_from_menu_color(menu, BM_COLOR_FILTER_BG, &paint.bg);
195  paint.pos = (struct pos){ (menu->title ? 2 : 0) + result.x_advance, 2 };
196  paint.box = (struct box){ (menu->title ? 2 : 4), 0, 2, 2, width - paint.pos.x, 0 };
197  bm_cairo_draw_line(cairo, &paint, &result, "%s", (menu->filter ? menu->filter : ""));
198  const uint32_t titleh = result.height;
199  out_result->height = titleh;
200 
201  uint32_t count;
202  struct bm_item **items = bm_menu_get_filtered_items(menu, &count);
203  uint32_t lines = (menu->lines > 0 ? menu->lines : 1);
204 
205  if (menu->lines > 0) {
206  /* vertical mode */
207 
208  const bool scrollbar = (menu->scrollbar > BM_SCROLLBAR_NONE && (menu->scrollbar != BM_SCROLLBAR_AUTOHIDE || count > lines) ? true : false);
209  const uint32_t spacing_x = (scrollbar ? 4 : 0);
210  uint32_t spacing_y = 0; // 0 == variable width spacing
211  if (lines > max_height / titleh) {
212  /* there is more lines than screen can fit, enter fixed spacing mode */
213  lines = max_height / titleh - 1;
214  spacing_y = titleh;
215  }
216 
217  uint32_t prefix_x = 0;
218  if (menu->prefix) {
219  bm_pango_get_text_extents(cairo, &paint, &result, "%s ", menu->prefix);
220  prefix_x += result.x_advance;
221  }
222 
223  uint32_t posy = titleh;
224  const uint32_t page = (menu->index / lines) * lines;
225  for (uint32_t l = 0, i = page; l < lines && i < count && posy < max_height; ++i, ++l) {
226  bool highlighted = (items[i] == bm_menu_get_highlighted_item(menu));
227 
228  if (highlighted) {
229  bm_cairo_color_from_menu_color(menu, BM_COLOR_HIGHLIGHTED_FG, &paint.fg);
230  bm_cairo_color_from_menu_color(menu, BM_COLOR_HIGHLIGHTED_BG, &paint.bg);
231  } else if (bm_menu_item_is_selected(menu, items[i])) {
232  bm_cairo_color_from_menu_color(menu, BM_COLOR_SELECTED_FG, &paint.fg);
233  bm_cairo_color_from_menu_color(menu, BM_COLOR_SELECTED_BG, &paint.bg);
234  } else {
235  bm_cairo_color_from_menu_color(menu, BM_COLOR_ITEM_FG, &paint.fg);
236  bm_cairo_color_from_menu_color(menu, BM_COLOR_ITEM_BG, &paint.bg);
237  }
238 
239  if (menu->prefix && highlighted) {
240  paint.pos = (struct pos){ 0, 2 + posy };
241  paint.box = (struct box){ 4 + spacing_x, 0, 2, 2, width - paint.pos.x, 0 };
242  bm_cairo_draw_line(cairo, &paint, &result, "%s %s", menu->prefix, (items[i]->text ? items[i]->text : ""));
243  } else {
244  paint.pos = (struct pos){ 0, 2 + posy };
245  paint.box = (struct box){ 4 + spacing_x + prefix_x, 0, 2, 2, width - paint.pos.x, 0 };
246  bm_cairo_draw_line(cairo, &paint, &result, "%s", (items[i]->text ? items[i]->text : ""));
247  }
248 
249  posy += (spacing_y ? spacing_y : result.height);
250  out_result->height = posy + 2;
251  out_result->displayed++;
252  }
253 
254  if (scrollbar && count > 0) {
255  bm_cairo_color_from_menu_color(menu, BM_COLOR_SCROLLBAR_BG, &paint.bg);
256  bm_cairo_color_from_menu_color(menu, BM_COLOR_SCROLLBAR_FG, &paint.fg);
257 
258  const uint32_t sheight = out_result->height - titleh;
259  cairo_set_source_rgba(cairo->cr, paint.bg.r, paint.bg.b, paint.bg.g, paint.bg.a);
260  cairo_rectangle(cairo->cr, 0, titleh, 2, sheight);
261  cairo_fill(cairo->cr);
262 
263  const float percent = fmin(((float)page / (count - lines)), 1.0f);
264  const uint32_t size = fmax(sheight * ((float)lines / count), 2.0f);
265  const uint32_t posy = percent * (sheight - size);
266  cairo_set_source_rgba(cairo->cr, paint.fg.r, paint.fg.b, paint.fg.g, paint.fg.a);
267  cairo_rectangle(cairo->cr, 0, titleh + posy, 2, size);
268  cairo_fill(cairo->cr);
269  }
270  } else {
271  /* single-line mode */
272  bm_pango_get_text_extents(cairo, &paint, &result, "lorem ipsum lorem ipsum lorem ipsum lorem");
273  uint32_t cl = fmin(title_x + result.x_advance, width / 4);
274 
275  if (menu->wrap || menu->index > 0) {
276  paint.pos = (struct pos){ cl, 2 };
277  paint.box = (struct box){ 1, 2, 2, 2, 0, 0 };
278  bm_cairo_draw_line(cairo, &paint, &result, "<");
279  cl += result.x_advance + 1;
280  }
281 
282  for (uint32_t i = menu->index; i < count && cl < width; ++i) {
283  bool highlighted = (items[i] == bm_menu_get_highlighted_item(menu));
284 
285  if (highlighted) {
286  bm_cairo_color_from_menu_color(menu, BM_COLOR_HIGHLIGHTED_FG, &paint.fg);
287  bm_cairo_color_from_menu_color(menu, BM_COLOR_HIGHLIGHTED_BG, &paint.bg);
288  } else if (bm_menu_item_is_selected(menu, items[i])) {
289  bm_cairo_color_from_menu_color(menu, BM_COLOR_SELECTED_FG, &paint.fg);
290  bm_cairo_color_from_menu_color(menu, BM_COLOR_SELECTED_BG, &paint.bg);
291  } else {
292  bm_cairo_color_from_menu_color(menu, BM_COLOR_ITEM_FG, &paint.fg);
293  bm_cairo_color_from_menu_color(menu, BM_COLOR_ITEM_BG, &paint.bg);
294  }
295 
296  paint.pos = (struct pos){ cl, 2 };
297  paint.box = (struct box){ 2, 4, 2, 2, 0, 0 };
298  bm_cairo_draw_line(cairo, &paint, &result, "%s", (items[i]->text ? items[i]->text : ""));
299  cl += result.x_advance + 2;
300  out_result->displayed += (cl < width);
301  out_result->height = fmax(out_result->height, result.height);
302  }
303 
304  if (menu->wrap || menu->index + 1 < count) {
305  bm_cairo_color_from_menu_color(menu, BM_COLOR_FILTER_FG, &paint.fg);
306  bm_cairo_color_from_menu_color(menu, BM_COLOR_FILTER_BG, &paint.bg);
307  bm_pango_get_text_extents(cairo, &paint, &result, ">");
308  paint.pos = (struct pos){ width - result.x_advance - 2, 2 };
309  paint.box = (struct box){ 1, 2, 2, 2, 0, 0 };
310  bm_cairo_draw_line(cairo, &paint, &result, ">");
311  }
312  }
313 }
314 
315 #endif /* _BM_CAIRO_H */
316 
317 /* vim: set ts=8 sw=4 tw=0 :*/
Definition: cairo.h:26
struct bm_item ** bm_menu_get_filtered_items(const struct bm_menu *menu, uint32_t *out_nmemb)
Definition: cairo.h:32
struct bm_item * bm_menu_get_highlighted_item(const struct bm_menu *menu)
Definition: cairo.h:37
Definition: cairo.h:21
Definition: cairo.h:11
bm_color
Definition: bemenu.h:204
Definition: cairo.h:42
Definition: wayland.h:70
Definition: cairo.h:17