checkbutton.c - ltk - GUI toolkit for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
checkbutton.c (11259B)
---
1 /*
2 * Copyright (c) 2024 lumidify <nobody@lumidify.org>
3 *
4 * Permission to use, copy, modify, and/or distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
16
17 #include <stdio.h>
18
19 #include "config.h"
20 #include "checkbutton.h"
21 #include "color.h"
22 #include "graphics.h"
23 #include "ltk.h"
24 #include "memory.h"
25 #include "rect.h"
26 #include "text.h"
27 #include "util.h"
28 #include "widget.h"
29
30 #define MAX_CHECKBUTTON_BORDER_WIDTH 10000
31 #define MAX_CHECKBUTTON_PADDING 50000
32 #define MAX_CHECKBUTTON_BOX_SIZE 50000
33
34 static void ltk_checkbutton_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
35 static int ltk_checkbutton_release(ltk_widget *self);
36 static void ltk_checkbutton_destroy(ltk_widget *self, int shallow);
37 static void ltk_checkbutton_recalc_ideal_size(ltk_widget *self);
38
39 static struct ltk_widget_vtable vtable = {
40 .key_press = NULL,
41 .key_release = NULL,
42 .mouse_press = NULL,
43 .mouse_release = NULL,
44 .release = <k_checkbutton_release,
45 .motion_notify = NULL,
46 .mouse_leave = NULL,
47 .mouse_enter = NULL,
48 .change_state = NULL,
49 .get_child_at_pos = NULL,
50 .resize = NULL,
51 .hide = NULL,
52 .draw = <k_checkbutton_draw,
53 .destroy = <k_checkbutton_destroy,
54 .child_size_change = NULL,
55 .remove_child = NULL,
56 .recalc_ideal_size = <k_checkbutton_recalc_ideal_size,
57 .type = LTK_WIDGET_CHECKBUTTON,
58 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
59 .invalid_signal = LTK_CHECKBUTTON_SIGNAL_INVALID,
60 };
61
62 static struct {
63 ltk_color *text_color;
64
65 ltk_color *fill;
66 ltk_color *fill_pressed;
67 ltk_color *fill_hover;
68 ltk_color *fill_active;
69 ltk_color *fill_disabled;
70
71 ltk_color *box_fill;
72 ltk_color *box_border;
73
74 ltk_color *box_fill_pressed;
75 ltk_color *box_border_pressed;
76
77 ltk_color *box_fill_hover;
78 ltk_color *box_border_hover;
79
80 ltk_color *box_fill_active;
81 ltk_color *box_border_active;
82
83 ltk_color *box_fill_disabled;
84 ltk_color *box_border_disabled;
85
86 ltk_color *box_fill_checked;
87 ltk_color *box_border_checked;
88
89 ltk_color *box_fill_pressed_checked;
90 ltk_color *box_border_pressed_checked;
91
92 ltk_color *box_fill_hover_checked;
93 ltk_color *box_border_hover_checked;
94
95 ltk_color *box_fill_active_checked;
96 ltk_color *box_border_active_checked;
97
98 ltk_color *box_fill_disabled_checked;
99 ltk_color *box_border_disabled_checked;
100
101 char *font;
102 ltk_size box_size;
103 ltk_size box_border_width;
104 ltk_size pad;
105 ltk_size font_size;
106 } theme;
107
108 static ltk_theme_parseinfo parseinfo[] = {
109 {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#000000"}, 0, 0, 0},
110 {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#222222"}, 0, 0, 0},
111 {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#222222"}, 0, 0, 0},
112 {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
113 {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#222222"}, 0, 0, 0},
114
115 {"box-fill", THEME_COLOR, {.color = &theme.box_fill}, {.color = "#000000"}, 0, 0, 0},
116 {"box-fill-hover", THEME_COLOR, {.color = &theme.box_fill_hover}, {.color = "#222222"}, 0, 0, 0},
117 {"box-fill-active", THEME_COLOR, {.color = &theme.box_fill_active}, {.color = "#222222"}, 0, 0, 0},
118 {"box-fill-disabled", THEME_COLOR, {.color = &theme.box_fill_disabled}, {.color = "#292929"}, 0, 0, 0},
119 {"box-fill-pressed", THEME_COLOR, {.color = &theme.box_fill_pressed}, {.color = "#222222"}, 0, 0, 0},
120 {"box-border", THEME_COLOR, {.color = &theme.box_border}, {.color = "#FFFFFF"}, 0, 0, 0},
121 {"box-border-hover", THEME_COLOR, {.color = &theme.box_border_hover}, {.color = "#FFFFFF"}, 0, 0, 0},
122 {"box-border-active", THEME_COLOR, {.color = &theme.box_border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
123 {"box-border-disabled", THEME_COLOR, {.color = &theme.box_border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
124 {"box-border-pressed", THEME_COLOR, {.color = &theme.box_border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
125
126 {"box-fill-checked", THEME_COLOR, {.color = &theme.box_fill_checked}, {.color = "#113355"}, 0, 0, 0},
127 {"box-fill-hover-checked", THEME_COLOR, {.color = &theme.box_fill_hover_checked}, {.color = "#738194"}, 0, 0, 0},
128 {"box-fill-active-checked", THEME_COLOR, {.color = &theme.box_fill_active_checked}, {.color = "#113355"}, 0, 0, 0},
129 {"box-fill-disabled-checked", THEME_COLOR, {.color = &theme.box_fill_disabled_checked}, {.color = "#292929"}, 0, 0, 0},
130 {"box-fill-pressed-checked", THEME_COLOR, {.color = &theme.box_fill_pressed_checked}, {.color = "#113355"}, 0, 0, 0},
131 {"box-border-checked", THEME_COLOR, {.color = &theme.box_border_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
132 {"box-border-hover-checked", THEME_COLOR, {.color = &theme.box_border_hover_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
133 {"box-border-active-checked", THEME_COLOR, {.color = &theme.box_border_active_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
134 {"box-border-disabled-checked", THEME_COLOR, {.color = &theme.box_border_disabled_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
135 {"box-border-pressed-checked", THEME_COLOR, {.color = &theme.box_border_pressed_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
136
137 {"box-size", THEME_SIZE, {.size = &theme.box_size}, {.size = {.val = 500, .unit = LTK_UNIT_MM}}, 0, MAX_CHECKBUTTON_BOX_SIZE, 0},
138 {"box-border-width", THEME_SIZE, {.size = &theme.box_border_width}, {.size = {.val = 25, .unit = LTK_UNIT_MM}}, 0, MAX_CHECKBUTTON_BORDER_WIDTH, 0},
139 {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_CHECKBUTTON_PADDING, 0},
140 {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
141 {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
142 {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
143 };
144
145 void
146 ltk_checkbutton_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
147 *p = parseinfo;
148 *len = LENGTH(parseinfo);
149 }
150
151 /* FIXME: a lot more theme settings */
152 static void
153 ltk_checkbutton_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
154 ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self);
155 ltk_rect lrect = self->lrect;
156 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
157 if (clip_final.w <= 0 || clip_final.h <= 0)
158 return;
159
160 int box_size = ltk_size_to_pixel(theme.box_size, self->last_dpi);
161 int box_bw = ltk_size_to_pixel(theme.box_border_width, self->last_dpi);
162 int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
163 ltk_color *fill = NULL, *box_border = NULL, *box_fill = NULL;
164 if (self->state & LTK_DISABLED) {
165 fill = theme.fill_disabled;
166 box_border = button->checked ? theme.box_border_disabled_checked : theme.box_border_disabled;
167 box_fill = button->checked ? theme.box_fill_disabled_checked : theme.box_fill_disabled;
168 } else if (self->state & LTK_PRESSED) {
169 fill = theme.fill_pressed;
170 box_border = button->checked ? theme.box_border_pressed_checked : theme.box_border_pressed;
171 box_fill = button->checked ? theme.box_fill_pressed_checked : theme.box_fill_pressed;
172 } else if (self->state & LTK_HOVER) {
173 fill = theme.fill_hover;
174 box_border = button->checked ? theme.box_border_hover_checked : theme.box_border_hover;
175 box_fill = button->checked ? theme.box_fill_hover_checked : theme.box_fill_hover;
176 } else if (self->state & LTK_ACTIVE) {
177 fill = theme.fill_active;
178 box_border = button->checked ? theme.box_border_active_checked : theme.box_border_active;
179 box_fill = button->checked ? theme.box_fill_active_checked : theme.box_fill_active;
180 } else {
181 fill = theme.fill;
182 box_border = button->checked ? theme.box_border_checked : theme.box_border;
183 box_fill = button->checked ? theme.box_fill_checked : theme.box_fill;
184 }
185 ltk_rect box_rect = {x + pad, y + pad, box_size, box_size};
186 ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
187 ltk_rect box_clip = ltk_rect_intersect(box_rect, draw_clip);
188 ltk_surface_fill_rect(draw_surf, fill, draw_clip);
189 ltk_surface_fill_rect(draw_surf, box_fill, box_clip);
190 if (box_bw > 0) {
191 ltk_surface_draw_border_clipped(
192 draw_surf, box_border, box_rect, box_bw, LTK_BORDER_ALL, box_clip
193 );
194 }
195 int text_w, text_h;
196 ltk_text_line_get_size(button->tl, &text_w, &text_h);
197 int text_x = x + 2 * pad + box_size;
198 int text_y = y + (lrect.h - text_h) / 2;
199 ltk_text_line_draw_clipped(button->tl, draw_surf, theme.text_color, text_x, text_y, draw_clip);
200 /* FIXME: only redraw if dirty (needs to be handled higher-up to only
201 call draw when dirty or window rect invalidated */
202 self->dirty = 0;
203 }
204
205 static int
206 ltk_checkbutton_release(ltk_widget *self) {
207 ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self);
208 button->checked = !button->checked;
209 ltk_widget_emit_signal(self, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST);
210 return 1;
211 }
212
213 int
214 ltk_checkbutton_get_checked(ltk_checkbutton *button) {
215 return button->checked;
216 }
217
218 void
219 ltk_checkbutton_set_checked(ltk_checkbutton *button, int checked) {
220 button->checked = checked;
221 ltk_widget *self = LTK_CAST_WIDGET(button);
222 ltk_window_invalidate_widget_rect(self->window, self);
223 ltk_widget_emit_signal(self, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST);
224 }
225
226 #define MAX(a, b) ((a) > (b) ? (a) : (b))
227
228 static void
229 recalc_ideal_size(ltk_checkbutton *button) {
230 int text_w, text_h;
231 ltk_text_line_get_size(button->tl, &text_w, &text_h);
232 int box_size = ltk_size_to_pixel(theme.box_size, LTK_CAST_WIDGET(button)->last_dpi);
233 int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi);
234 button->widget.ideal_w = text_w + pad * 3 + box_size;
235 button->widget.ideal_h = MAX(text_h, box_size) + pad * 2;
236 }
237
238 static void
239 ltk_checkbutton_recalc_ideal_size(ltk_widget *self) {
240 ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self);
241 int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
242 ltk_text_line_set_font_size(button->tl, font_size);
243 recalc_ideal_size(button);
244 }
245
246 ltk_checkbutton *
247 ltk_checkbutton_create(ltk_window *window, const char *text, int checked) {
248 ltk_checkbutton *button = ltk_malloc(sizeof(ltk_checkbutton));
249 ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0);
250 button->checked = checked;
251
252 button->tl = ltk_text_line_create_const_text_default(
253 theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1
254 );
255 recalc_ideal_size(button);
256 button->widget.dirty = 1;
257
258 return button;
259 }
260
261 static void
262 ltk_checkbutton_destroy(ltk_widget *self, int shallow) {
263 (void)shallow;
264 ltk_checkbutton *button = LTK_CAST_CHECKBUTTON(self);
265 if (!button) {
266 ltk_warn("Tried to destroy NULL checkbutton.\n");
267 return;
268 }
269 ltk_text_line_destroy(button->tl);
270 ltk_free(button);
271 }