1#include <stdlib.h>
2#include <string.h>
3#include "mixer_clickable.h"
4
5extern int screen_cols;
6extern int screen_lines;
7
8static struct clickable_rect *clickable_rects = NULL;
9static unsigned int clickable_rects_count = 0;
10static unsigned int last_rect = 0;
11
12/* Using 0 instead of -1 for marking free rectangles allows us to use
13 * memset for `freeing` all rectangles at once.
14 * Zero is actually a valid coordinate in ncurses, but since we don't have
15 * any clickables in the top line this is fine. */
16#define FREE_MARKER 0
17#define RECT_IS_FREE(RECT) ((RECT).y1 == FREE_MARKER)
18#define RECT_FREE(RECT) ((RECT).y1 = FREE_MARKER)
19
20void clickable_set(int y1, int x1, int y2, int x2, command_enum command, int arg1) {
21	struct clickable_rect* tmp;
22	unsigned int i;
23
24	for (i = last_rect; i < clickable_rects_count; ++i) {
25		if (RECT_IS_FREE(clickable_rects[i])) {
26			last_rect = i;
27			goto SET_CLICKABLE_DATA;
28		}
29	}
30
31	for (i = 0; i < last_rect; ++i) {
32		if (RECT_IS_FREE(clickable_rects[i])) {
33			last_rect = i;
34			goto SET_CLICKABLE_DATA;
35		}
36	}
37
38	last_rect = clickable_rects_count;
39	tmp = realloc(clickable_rects, (clickable_rects_count + 8) * sizeof(*clickable_rects));
40	if (!tmp) {
41		free(clickable_rects);
42		clickable_rects = NULL;
43		clickable_rects_count = 0;
44		last_rect = 0;
45		return;
46	}
47	clickable_rects = tmp;
48#if FREE_MARKER == 0
49	memset(clickable_rects + clickable_rects_count, 0, 8 * sizeof(*clickable_rects));
50#else
51	for (i = clickable_rects_count; i < clickable_rects_count + 8; ++i)
52		RECT_FREE(clickable_rects[i]);
53#endif
54	clickable_rects_count += 8;
55
56SET_CLICKABLE_DATA:
57	clickable_rects[last_rect] = (struct clickable_rect) {
58		.y1 = y1,
59		.x1 = x1,
60		.x2 = x2,
61		.y2 = y2,
62		.command = command,
63		.arg1 = arg1
64	};
65}
66
67void clickable_set_relative(WINDOW *win, int y1, int x1, int y2, int x2, command_enum command, int arg1) {
68	int y, x;
69	getyx(win, y, x);
70	y1 = y + y1;
71	x1 = x + x1;
72	y2 = y + y2;
73	x2 = x + x2;
74	clickable_set(y1, x1, y2, x2, command, arg1);
75}
76
77void clickable_clear(int y1, int x1, int y2, int x2) {
78#define IS_IN_RECT(Y, X) (Y >= y1 && Y <= y2 && X >= x1 && X <= x2)
79	unsigned int i;
80
81	if (x1 == 0 && x2 == -1 && y2 == -1) {
82		if (y1 == 0) {
83			// Optimize case: clear all
84#if FREE_MARKER == 0
85			if (clickable_rects)
86				memset(clickable_rects, 0,
87						clickable_rects_count * sizeof(*clickable_rects));
88#else
89			for (i = 0; i < clickable_rects_count; ++i)
90				RECT_FREE(clickable_rects[i]);
91#endif
92		}
93		else {
94			// Optimize case: clear all lines beyond y1
95			for (i = 0; i < clickable_rects_count; ++i) {
96				if (clickable_rects[i].y2 >= y1)
97					RECT_FREE(clickable_rects[i]);
98			}
99		}
100		return;
101	}
102
103	if (y2 < 0)
104		y2 = screen_lines + y2 + 1;
105	if (x2 < 0)
106		x2 = screen_cols + x2 + 1;
107
108	for (i = 0; i < clickable_rects_count; ++i) {
109		if (!RECT_IS_FREE(clickable_rects[i]) && (
110				IS_IN_RECT(clickable_rects[i].y1, clickable_rects[i].x1) ||
111				IS_IN_RECT(clickable_rects[i].y2, clickable_rects[i].x2)
112			))
113		{
114			RECT_FREE(clickable_rects[i]);
115		}
116	}
117}
118
119struct clickable_rect* clickable_find(int y, int x) {
120	unsigned int i;
121
122	for (i = 0; i < clickable_rects_count; ++i) {
123		if (
124				!RECT_IS_FREE(clickable_rects[i]) &&
125				y >= clickable_rects[i].y1 &&
126				x >= clickable_rects[i].x1 &&
127				y <= clickable_rects[i].y2 &&
128				x <= clickable_rects[i].x2
129			)
130		{
131			return &clickable_rects[i];
132		}
133	}
134
135	return NULL;
136}
137