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 }