main neuswc / libswc / decor.c
  1#include "decor.h"
  2
  3#include "compositor.h"
  4#include "internal.h"
  5#include "shm.h"
  6#include "swc.h"
  7#include "util.h"
  8#include "window.h"
  9
 10#include <pixman.h>
 11#include <stdlib.h>
 12#include <string.h>
 13#include <wld/wld.h>
 14
 15#define DEFAULT_DECOR_FONT "sans-serif:size=10"
 16
 17static struct wld_font_context *font_context;
 18
 19enum decor_part_index {
 20	DECOR_PART_TOP_LEFT,
 21	DECOR_PART_TOP,
 22	DECOR_PART_TOP_RIGHT,
 23	DECOR_PART_LEFT,
 24	DECOR_PART_RIGHT,
 25	DECOR_PART_BOTTOM_LEFT,
 26	DECOR_PART_BOTTOM,
 27	DECOR_PART_BOTTOM_RIGHT,
 28	DECOR_PART_COUNT,
 29};
 30
 31static uint32_t
 32decor_title_length(struct wld_font *font, const char *title, uint32_t max_width)
 33{
 34	uint32_t len = 0;
 35	struct wld_extents extents;
 36
 37	if (!title || !max_width) {
 38		return 0;
 39	}
 40
 41	while (title[len]) {
 42		uint32_t next = len + 1;
 43
 44		/* we skip utf-8 continuation bytes */
 45		while ((title[next] & 0xc0) == 0x80) {
 46			next++;
 47		}
 48
 49		wld_font_text_extents_n(font, title, (int32_t)next, &extents);
 50		if (extents.advance > max_width) {
 51			break;
 52		}
 53		len = next;
 54	}
 55
 56	return len;
 57}
 58
 59static uint32_t
 60utf8_next_len(const char *text, uint32_t offset)
 61{
 62	uint32_t next = offset + 1;
 63
 64	while ((text[next] & 0xc0) == 0x80) {
 65		next++;
 66	}
 67
 68	return next - offset;
 69}
 70
 71static uint32_t
 72decor_title_stacked_length(struct wld_font *font, const char *title,
 73                           uint32_t max_height, uint32_t *glyph_count)
 74{
 75	uint32_t len = 0, count = 0;
 76
 77	if (!title || !font->height) {
 78		*glyph_count = 0;
 79		return 0;
 80	}
 81
 82	while (title[len] && (count + 1) * font->height <= max_height) {
 83		len += utf8_next_len(title, len);
 84		count++;
 85	}
 86
 87	*glyph_count = count;
 88	return len;
 89}
 90
 91static bool
 92streq(const char *a, const char *b)
 93{
 94	if (!a || !b) {
 95		return a == b;
 96	}
 97	return strcmp(a, b) == 0;
 98}
 99
100static const struct swc_decor_part *
101decor_part_at(const struct swc_decor_parts *parts, enum decor_part_index index)
102{
103	if (!parts) {
104		return NULL;
105	}
106
107	switch (index) {
108	case DECOR_PART_TOP_LEFT:
109		return &parts->top_left;
110	case DECOR_PART_TOP:
111		return &parts->top;
112	case DECOR_PART_TOP_RIGHT:
113		return &parts->top_right;
114	case DECOR_PART_LEFT:
115		return &parts->left;
116	case DECOR_PART_RIGHT:
117		return &parts->right;
118	case DECOR_PART_BOTTOM_LEFT:
119		return &parts->bottom_left;
120	case DECOR_PART_BOTTOM:
121		return &parts->bottom;
122	case DECOR_PART_BOTTOM_RIGHT:
123		return &parts->bottom_right;
124	case DECOR_PART_COUNT:
125		break;
126	}
127
128	return NULL;
129}
130
131static void
132close_decor_parts(struct compositor_view *view)
133{
134	for (uint32_t i = 0; i < DECOR_PART_COUNT; ++i) {
135		if (view->decor.parts[i].buffer) {
136			wld_buffer_unreference(view->decor.parts[i].buffer);
137			view->decor.parts[i].buffer = NULL;
138		}
139		free(view->decor.parts[i].data);
140		view->decor.parts[i].data = NULL;
141		view->decor.parts[i].width = 0;
142		view->decor.parts[i].height = 0;
143		view->decor.parts[i].stride = 0;
144	}
145	view->decor.parts_key = NULL;
146}
147
148static bool
149decor_part_is_empty(const struct swc_decor_part *part)
150{
151	return !part || !part->data || !part->width || !part->height ||
152	       !part->stride;
153}
154
155static bool
156decor_part_equal(const struct decor_part_buffer *owned,
157                 const struct swc_decor_part *part)
158{
159	size_t size;
160
161	if (decor_part_is_empty(part)) {
162		return !owned->data && !owned->buffer && !owned->width && !owned->height &&
163		       !owned->stride;
164	}
165
166	if (!owned->data || owned->width != part->width ||
167	    owned->height != part->height || owned->stride != part->stride) {
168		return false;
169	}
170
171	size = (size_t)part->stride * part->height;
172	return memcmp(owned->data, part->data, size) == 0;
173}
174
175static bool
176decor_parts_equal(struct compositor_view *view, const struct swc_decor_parts *parts)
177{
178	for (uint32_t i = 0; i < DECOR_PART_COUNT; ++i) {
179		if (!decor_part_equal(&view->decor.parts[i], decor_part_at(parts, i))) {
180			return false;
181		}
182	}
183
184	return true;
185}
186
187static bool
188copy_decor_part(struct decor_part_buffer *dst, const struct swc_decor_part *src)
189{
190	union wld_object object;
191	size_t size;
192
193	if (decor_part_is_empty(src)) {
194		memset(dst, 0, sizeof(*dst));
195		return true;
196	}
197
198	size = (size_t)src->stride * src->height;
199	dst->data = malloc(size);
200	if (!dst->data) {
201		return false;
202	}
203	memcpy(dst->data, src->data, size);
204
205	dst->width = src->width;
206	dst->height = src->height;
207	dst->stride = src->stride;
208	object.ptr = dst->data;
209	dst->buffer = wld_import_buffer(swc.shm->context, WLD_OBJECT_DATA, object,
210	                                src->width, src->height,
211	                                WLD_FORMAT_ARGB8888, src->stride);
212	if (!dst->buffer) {
213		free(dst->data);
214		memset(dst, 0, sizeof(*dst));
215		return false;
216	}
217
218	return true;
219}
220
221static bool
222copy_decor_parts(struct compositor_view *view, const struct swc_decor_parts *parts)
223{
224	struct decor_part_buffer copied[DECOR_PART_COUNT] = { 0 };
225	uint32_t i;
226
227	for (i = 0; i < DECOR_PART_COUNT; ++i) {
228		if (!copy_decor_part(&copied[i], decor_part_at(parts, i))) {
229			goto error;
230		}
231	}
232
233	close_decor_parts(view);
234	memcpy(view->decor.parts, copied, sizeof(copied));
235	view->decor.parts_key = parts;
236	return true;
237
238error:
239	for (i = 0; i < DECOR_PART_COUNT; ++i) {
240		if (copied[i].buffer) {
241			wld_buffer_unreference(copied[i].buffer);
242		}
243		free(copied[i].data);
244	}
245	return false;
246}
247
248static void
249close_decor_font(struct compositor_view *view)
250{
251	if (view->decor.font) {
252		wld_font_close(view->decor.font);
253		view->decor.font = NULL;
254	}
255	free(view->decor.font_name);
256	view->decor.font_name = NULL;
257}
258
259static void
260close_decor_string(struct compositor_view *view)
261{
262	free(view->decor.string);
263	view->decor.string = NULL;
264}
265
266/* draw decor part by tiling it across the target region.
267 * the part buffer is repeated to fill the entirety of some width x height area,
268 * but only damaged regions are actually rendred*/
269static void
270draw_decor_part(struct wld_renderer *renderer,
271                const struct swc_rectangle *target_geom,
272                struct compositor_view *view, pixman_region32_t *damage,
273                const struct decor_part_buffer *part, int32_t x, int32_t y,
274                uint32_t width, uint32_t height)
275{
276	pixman_region32_t region;
277	pixman_box32_t *boxes;
278	int nboxes;
279
280	if (!part->buffer || !part->width || !part->height || !width || !height) {
281		return;
282	}
283
284	pixman_region32_init_rect(&region, x, y, width, height);
285	pixman_region32_intersect(&region, &region, damage);
286	pixman_region32_subtract(&region, &region, &view->clip);
287	boxes = pixman_region32_rectangles(&region, &nboxes);
288
289	for (int i = 0; i < nboxes; ++i) {
290		int32_t rx1 = boxes[i].x1;
291		int32_t ry1 = boxes[i].y1;
292		int32_t rx2 = boxes[i].x2;
293		int32_t ry2 = boxes[i].y2;
294		int32_t start_y = y + ((ry1 - y) / (int32_t)part->height) * (int32_t)part->height;
295
296		if (start_y > ry1) {
297			start_y -= (int32_t)part->height;
298		}
299
300		for (int32_t tile_y = start_y; tile_y < ry2; tile_y += (int32_t)part->height) {
301			int32_t start_x =
302			    x + ((rx1 - x) / (int32_t)part->width) * (int32_t)part->width;
303
304			if (start_x > rx1) {
305				start_x -= (int32_t)part->width;
306			}
307
308			for (int32_t tile_x = start_x; tile_x < rx2;
309			     tile_x += (int32_t)part->width) {
310				int32_t clip_x1 = MAX(tile_x, rx1);
311				int32_t clip_y1 = MAX(tile_y, ry1);
312				int32_t clip_x2 = MIN(tile_x + (int32_t)part->width, rx2);
313				int32_t clip_y2 = MIN(tile_y + (int32_t)part->height, ry2);
314
315				if (clip_x2 > clip_x1 && clip_y2 > clip_y1) {
316					wld_copy_rectangle(renderer, part->buffer,
317					                   clip_x1 - target_geom->x,
318					                   clip_y1 - target_geom->y,
319					                   clip_x1 - tile_x, clip_y1 - tile_y,
320					                   (uint32_t)(clip_x2 - clip_x1),
321					                   (uint32_t)(clip_y2 - clip_y1));
322				}
323			}
324		}
325	}
326
327	pixman_region32_fini(&region);
328}
329
330bool
331decor_initialize(void)
332{
333	font_context = wld_font_create_context();
334	return font_context != NULL;
335}
336
337void
338decor_finalize(void)
339{
340	if (font_context) {
341		wld_font_destroy_context(font_context);
342		font_context = NULL;
343	}
344}
345
346void
347decor_view_initialize(struct compositor_view *view)
348{
349	memset(&view->decor.text, 0, sizeof(view->decor.text));
350	view->decor.parts_key = NULL;
351	memset(view->decor.parts, 0, sizeof(view->decor.parts));
352	view->decor.string = NULL;
353	view->decor.font_name = NULL;
354	view->decor.font = NULL;
355}
356
357void
358decor_view_finalize(struct compositor_view *view)
359{
360	close_decor_parts(view);
361	close_decor_string(view);
362	close_decor_font(view);
363}
364
365void
366decor_repaint(struct wld_renderer *renderer,
367              const struct swc_rectangle *target_geom,
368              struct compositor_view *view, pixman_region32_t *damage)
369{
370	const struct swc_rectangle *geom = &view->base.geometry;
371	const struct swc_decor_text *text = &view->decor.text;
372	struct wld_font *font = view->decor.font;
373	const char *title = view->decor.string;
374	pixman_region32_t decor_region, content_region;
375	uint32_t title_len, max_width, decor_size;
376	int32_t x, y, base_x, base_y, advance, available_width;
377	struct wld_extents extents;
378	pixman_region32_t title_region;
379	int32_t outer_x = geom->x - (int32_t)view->decor.left;
380	int32_t outer_y = geom->y - (int32_t)view->decor.top;
381	uint32_t outer_width = geom->width + view->decor.left + view->decor.right;
382	uint32_t outer_height = geom->height + view->decor.top + view->decor.bottom;
383	uint32_t tl_width = view->decor.parts[DECOR_PART_TOP_LEFT].width;
384	uint32_t tl_height = view->decor.parts[DECOR_PART_TOP_LEFT].height;
385	uint32_t tr_width = view->decor.parts[DECOR_PART_TOP_RIGHT].width;
386	uint32_t tr_height = view->decor.parts[DECOR_PART_TOP_RIGHT].height;
387	uint32_t bl_width = view->decor.parts[DECOR_PART_BOTTOM_LEFT].width;
388	uint32_t bl_height = view->decor.parts[DECOR_PART_BOTTOM_LEFT].height;
389	uint32_t br_width = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].width;
390	uint32_t br_height = view->decor.parts[DECOR_PART_BOTTOM_RIGHT].height;
391
392	if (!view->decor.top && !view->decor.right && !view->decor.bottom &&
393	    !view->decor.left) {
394		return;
395	}
396
397	pixman_region32_init_rect(&decor_region, outer_x, outer_y, outer_width,
398	                          outer_height);
399	pixman_region32_init_rect(&content_region, geom->x, geom->y, geom->width,
400	                          geom->height);
401	pixman_region32_subtract(&decor_region, &decor_region, &content_region);
402	pixman_region32_intersect(&decor_region, &decor_region, damage);
403	pixman_region32_subtract(&decor_region, &decor_region, &view->clip);
404	if (pixman_region32_not_empty(&decor_region)) {
405		pixman_region32_translate(&decor_region, -target_geom->x, -target_geom->y);
406		wld_fill_region(renderer, view->decor.color, &decor_region);
407		pixman_region32_translate(&decor_region, target_geom->x, target_geom->y);
408	}
409	pixman_region32_fini(&decor_region);
410	pixman_region32_fini(&content_region);
411
412	draw_decor_part(renderer, target_geom, view, damage,
413	                &view->decor.parts[DECOR_PART_TOP_LEFT], outer_x, outer_y,
414	                tl_width, tl_height);
415	draw_decor_part(renderer, target_geom, view, damage,
416	                &view->decor.parts[DECOR_PART_TOP_RIGHT],
417	                outer_x + (int32_t)outer_width - (int32_t)tr_width, outer_y,
418	                tr_width, tr_height);
419	draw_decor_part(renderer, target_geom, view, damage,
420	                &view->decor.parts[DECOR_PART_BOTTOM_LEFT], outer_x,
421	                outer_y + (int32_t)outer_height - (int32_t)bl_height,
422	                bl_width, bl_height);
423	draw_decor_part(renderer, target_geom, view, damage,
424	                &view->decor.parts[DECOR_PART_BOTTOM_RIGHT],
425	                outer_x + (int32_t)outer_width - (int32_t)br_width,
426	                outer_y + (int32_t)outer_height - (int32_t)br_height,
427	                br_width, br_height);
428
429	if (outer_width > tl_width + tr_width) {
430		draw_decor_part(renderer, target_geom, view, damage,
431		                &view->decor.parts[DECOR_PART_TOP],
432		                outer_x + (int32_t)tl_width, outer_y,
433		                outer_width - tl_width - tr_width, view->decor.top);
434		draw_decor_part(renderer, target_geom, view, damage,
435		                &view->decor.parts[DECOR_PART_BOTTOM],
436		                outer_x + (int32_t)bl_width,
437		                outer_y + (int32_t)outer_height - (int32_t)view->decor.bottom,
438		                outer_width - bl_width - br_width, view->decor.bottom);
439	}
440
441	if (outer_height > tl_height + bl_height) {
442		draw_decor_part(renderer, target_geom, view, damage,
443		                &view->decor.parts[DECOR_PART_LEFT], outer_x,
444		                outer_y + (int32_t)tl_height, view->decor.left,
445		                outer_height - tl_height - bl_height);
446		draw_decor_part(renderer, target_geom, view, damage,
447		                &view->decor.parts[DECOR_PART_RIGHT],
448		                outer_x + (int32_t)outer_width - (int32_t)view->decor.right,
449		                outer_y + (int32_t)tr_height, view->decor.right,
450		                outer_height - tr_height - br_height);
451	}
452
453	if (!title || !font || !text->enabled) {
454		return;
455	}
456
457	/* calculate text position based on which edge
458	 * the decor is on. base_x base_y is the top-left corner,
459	 * max_width is available space
460	 * decor_size is perpendicular dimension of the edge where text is being drawn */
461	switch (text->edge) {
462	case SWC_DECOR_EDGE_TOP:
463		if (!view->decor.top) {
464			return;
465		}
466		base_x = geom->x - (int32_t)view->decor.left;
467		base_y = geom->y - (int32_t)view->decor.top;
468		max_width = geom->width + view->decor.left + view->decor.right;
469		decor_size = view->decor.top;
470		break;
471	case SWC_DECOR_EDGE_BOTTOM:
472		if (!view->decor.bottom) {
473			return;
474		}
475		base_x = geom->x - (int32_t)view->decor.left;
476		base_y = geom->y + (int32_t)geom->height;
477		max_width = geom->width + view->decor.left + view->decor.right;
478		decor_size = view->decor.bottom;
479		break;
480	case SWC_DECOR_EDGE_LEFT:
481		if (!view->decor.left) {
482			return;
483		}
484		base_x = geom->x - (int32_t)view->decor.left;
485		base_y = geom->y - (int32_t)view->decor.top;
486		max_width = view->decor.left;
487		decor_size = geom->height + view->decor.top + view->decor.bottom;
488		break;
489	case SWC_DECOR_EDGE_RIGHT:
490		if (!view->decor.right) {
491			return;
492		}
493		base_x = geom->x + (int32_t)geom->width;
494		base_y = geom->y - (int32_t)view->decor.top;
495		max_width = view->decor.right;
496		decor_size = geom->height + view->decor.top + view->decor.bottom;
497		break;
498	default:
499		return;
500	}
501
502	x = base_x;
503	y = base_y;
504
505	pixman_region32_init_rect(&title_region, x, y, max_width, decor_size);
506	pixman_region32_intersect(&title_region, &title_region, damage);
507	pixman_region32_subtract(&title_region, &title_region, &view->clip);
508	if (!pixman_region32_not_empty(&title_region)) {
509		pixman_region32_fini(&title_region);
510		return;
511	}
512	pixman_region32_fini(&title_region);
513
514	if (max_width <= text->padding * 2 || decor_size < font->height) {
515		return;
516	}
517	max_width -= text->padding * 2;
518
519	if (text->edge == SWC_DECOR_EDGE_LEFT || text->edge == SWC_DECOR_EDGE_RIGHT) {
520		uint32_t glyph_count, glyph_offset = 0, available_height, total_height;
521
522		if (decor_size <= text->padding * 2) {
523			return;
524		}
525
526		available_height = decor_size - text->padding * 2;
527		title_len = decor_title_stacked_length(font, title, available_height,
528		                                      &glyph_count);
529		if (!title_len) {
530			return;
531		}
532
533		total_height = glyph_count * font->height;
534		y += (int32_t)text->padding;
535		switch (text->align) {
536		case SWC_DECOR_ALIGN_START:
537			break;
538		case SWC_DECOR_ALIGN_CENTER:
539			y += (int32_t)((available_height - total_height) / 2);
540			break;
541		case SWC_DECOR_ALIGN_END:
542			y += (int32_t)(available_height - total_height);
543			break;
544		}
545
546		while (glyph_offset < title_len) {
547			uint32_t glyph_len = utf8_next_len(title, glyph_offset);
548			int32_t glyph_x;
549
550			wld_font_text_extents_n(font, title + glyph_offset,
551			                        (int32_t)glyph_len, &extents);
552			advance = extents.advance > 0 ? extents.advance : 0;
553			glyph_x = x + (int32_t)text->padding;
554			if ((uint32_t)advance < max_width) {
555				glyph_x += ((int32_t)max_width - advance) / 2;
556			}
557
558			wld_draw_text(renderer, font, text->color,
559			              glyph_x + text->offset_x - target_geom->x,
560			              y + (int32_t)font->ascent + text->offset_y -
561			                  target_geom->y,
562			              title + glyph_offset, glyph_len, NULL);
563
564			glyph_offset += glyph_len;
565			y += (int32_t)font->height;
566		}
567
568		return;
569	}
570
571	title_len = decor_title_length(font, title, max_width);
572	if (!title_len) {
573		return;
574	}
575	wld_font_text_extents_n(font, title, (int32_t)title_len, &extents);
576	advance = extents.advance > 0 ? extents.advance : 0;
577	available_width = (int32_t)max_width;
578
579	switch (text->align) {
580	case SWC_DECOR_ALIGN_START:
581		x += text->padding;
582		break;
583	case SWC_DECOR_ALIGN_CENTER:
584		x += (int32_t)text->padding + (available_width - advance) / 2;
585		break;
586	case SWC_DECOR_ALIGN_END:
587		x += (int32_t)text->padding + (int32_t)max_width - advance;
588		break;
589	}
590
591	y += (int32_t)((decor_size - font->height) / 2) + (int32_t)font->ascent;
592
593	x += text->offset_x;
594	y += text->offset_y;
595
596	wld_draw_text(renderer, font, text->color, x - target_geom->x,
597	              y - target_geom->y, title, title_len, NULL);
598}
599
600void
601decor_view_set(struct compositor_view *view, const struct swc_decor *decor)
602{
603	const char *font_name = NULL;
604	struct wld_font *font = NULL;
605	struct swc_decor_text text = { 0 };
606	char *owned_string = NULL;
607	char *owned_font_name = NULL;
608	const struct swc_decor_parts *parts = NULL;
609	uint32_t color = 0, top = 0, right = 0, bottom = 0, left = 0;
610
611	if (!decor) {
612		goto apply;
613	}
614
615	color = decor->color;
616	top = decor->top;
617	right = decor->right;
618	bottom = decor->bottom;
619	left = decor->left;
620	parts = decor->parts;
621	text = decor->title;
622	if (text.enabled) {
623		font_name = text.font ? text.font : DEFAULT_DECOR_FONT;
624	}
625
626	if (view->decor.color == color && view->decor.top == top &&
627	    view->decor.right == right && view->decor.bottom == bottom &&
628	    view->decor.left == left &&
629	    view->decor.text.enabled == text.enabled &&
630	    view->decor.text.edge == text.edge &&
631	    view->decor.text.align == text.align &&
632	    streq(view->decor.string, text.string) &&
633	    view->decor.text.color == text.color &&
634	    view->decor.text.padding == text.padding &&
635	    view->decor.text.offset_x == text.offset_x &&
636	    view->decor.text.offset_y == text.offset_y &&
637	    streq(view->decor.font_name, font_name) &&
638	    decor_parts_equal(view, parts)) {
639		return;
640	}
641
642	if (text.string) {
643		owned_string = strdup(text.string);
644	}
645
646	if (font_name) {
647		owned_font_name = strdup(font_name);
648		if (owned_font_name && font_context) {
649			font = wld_font_open_name(font_context, font_name);
650		}
651	}
652
653apply:
654	view->decor.color = color;
655	view->decor.top = top;
656	view->decor.right = right;
657	view->decor.bottom = bottom;
658	view->decor.left = left;
659	view->decor.text = text;
660	view->decor.text.string = NULL;
661	view->decor.text.font = NULL;
662	if (!copy_decor_parts(view, parts)) {
663		close_decor_parts(view);
664	}
665	close_decor_string(view);
666	view->decor.string = owned_string;
667	close_decor_font(view);
668	view->decor.font_name = owned_font_name;
669	view->decor.font = font;
670	view->decor.damaged = true;
671}
672
673void
674decor_view_damage(struct compositor_view *view)
675{
676	view->decor.damaged = true;
677}