button.c (7200B)
1 /* 2 * Copyright (c) 2016-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 "button.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_BUTTON_BORDER_WIDTH 10000 31 #define MAX_BUTTON_PADDING 50000 32 33 static void ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); 34 static int ltk_button_release(ltk_widget *self); 35 static void ltk_button_destroy(ltk_widget *self, int shallow); 36 static void ltk_button_recalc_ideal_size(ltk_widget *self); 37 38 static struct ltk_widget_vtable vtable = { 39 .key_press = NULL, 40 .key_release = NULL, 41 .mouse_press = NULL, 42 .mouse_release = NULL, 43 .release = <k_button_release, 44 .motion_notify = NULL, 45 .mouse_leave = NULL, 46 .mouse_enter = NULL, 47 .change_state = NULL, 48 .get_child_at_pos = NULL, 49 .resize = NULL, 50 .hide = NULL, 51 .draw = <k_button_draw, 52 .destroy = <k_button_destroy, 53 .child_size_change = NULL, 54 .remove_child = NULL, 55 .recalc_ideal_size = <k_button_recalc_ideal_size, 56 .type = LTK_WIDGET_BUTTON, 57 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS, 58 .invalid_signal = LTK_BUTTON_SIGNAL_INVALID, 59 }; 60 61 static struct { 62 ltk_color *text_color; 63 64 ltk_color *border; 65 ltk_color *fill; 66 67 ltk_color *border_pressed; 68 ltk_color *fill_pressed; 69 70 ltk_color *border_hover; 71 ltk_color *fill_hover; 72 73 ltk_color *border_active; 74 ltk_color *fill_active; 75 76 ltk_color *border_disabled; 77 ltk_color *fill_disabled; 78 79 char *font; 80 ltk_size border_width; 81 ltk_size pad; 82 ltk_size font_size; 83 } theme; 84 85 static ltk_theme_parseinfo parseinfo[] = { 86 {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0}, 87 {"border-hover", THEME_COLOR, {.color = &theme.border_hover}, {.color = "#FFFFFF"}, 0, 0, 0}, 88 {"border-active", THEME_COLOR, {.color = &theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, 89 {"border-disabled", THEME_COLOR, {.color = &theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 90 {"border-pressed", THEME_COLOR, {.color = &theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, 91 {"border-width", THEME_SIZE, {.size = &theme.border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_BUTTON_BORDER_WIDTH, 0}, 92 {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0}, 93 {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0}, 94 {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#738194"}, 0, 0, 0}, 95 {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 0, 0, 0}, 96 {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, 97 {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, 98 {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_BUTTON_PADDING, 0}, 99 {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0}, 100 {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0}, 101 }; 102 103 void 104 ltk_button_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) { 105 *p = parseinfo; 106 *len = LENGTH(parseinfo); 107 } 108 109 static void 110 ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { 111 ltk_button *button = LTK_CAST_BUTTON(self); 112 ltk_rect lrect = self->lrect; 113 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); 114 if (clip_final.w <= 0 || clip_final.h <= 0) 115 return; 116 117 int bw = ltk_size_to_pixel(theme.border_width, self->last_dpi); 118 ltk_color *border = NULL, *fill = NULL; 119 /* FIXME: HOVERACTIVE STATE */ 120 if (self->state & LTK_DISABLED) { 121 border = theme.border_disabled; 122 fill = theme.fill_disabled; 123 } else if (self->state & LTK_PRESSED) { 124 border = theme.border_pressed; 125 fill = theme.fill_pressed; 126 } else if (self->state & LTK_HOVER) { 127 border = theme.border_hover; 128 fill = theme.fill_hover; 129 } else if (self->state & LTK_ACTIVE) { 130 border = theme.border_active; 131 fill = theme.fill_active; 132 } else { 133 border = theme.border; 134 fill = theme.fill; 135 } 136 /* FIXME: helper functions for these common rect calculations */ 137 ltk_rect draw_rect = {x, y, lrect.w, lrect.h}; 138 ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h}; 139 ltk_surface_fill_rect(draw_surf, fill, draw_clip); 140 /* FIXME: support theme setting for border sides */ 141 if (bw > 0) { 142 ltk_surface_draw_border_clipped( 143 draw_surf, border, draw_rect, bw, LTK_BORDER_ALL, draw_clip 144 ); 145 } 146 int text_w, text_h; 147 ltk_text_line_get_size(button->tl, &text_w, &text_h); 148 int text_x = x + (lrect.w - text_w) / 2; 149 int text_y = y + (lrect.h - text_h) / 2; 150 ltk_text_line_draw_clipped(button->tl, draw_surf, theme.text_color, text_x, text_y, draw_clip); 151 /* FIXME: only redraw if dirty (needs to be handled higher-up to only 152 call draw when dirty or window rect invalidated */ 153 self->dirty = 0; 154 } 155 156 static int 157 ltk_button_release(ltk_widget *self) { 158 ltk_widget_emit_signal(self, LTK_BUTTON_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST); 159 return 1; 160 } 161 162 static void 163 recalc_ideal_size(ltk_button *button) { 164 int text_w, text_h; 165 ltk_text_line_get_size(button->tl, &text_w, &text_h); 166 int bw = ltk_size_to_pixel(theme.border_width, LTK_CAST_WIDGET(button)->last_dpi); 167 int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi); 168 button->widget.ideal_w = text_w + bw * 2 + pad * 2; 169 button->widget.ideal_h = text_h + bw * 2 + pad * 2; 170 } 171 172 static void 173 ltk_button_recalc_ideal_size(ltk_widget *self) { 174 ltk_button *button = LTK_CAST_BUTTON(self); 175 int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi); 176 ltk_text_line_set_font_size(button->tl, font_size); 177 recalc_ideal_size(button); 178 } 179 180 ltk_button * 181 ltk_button_create(ltk_window *window, const char *text) { 182 ltk_button *button = ltk_malloc(sizeof(ltk_button)); 183 ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0); 184 185 button->tl = ltk_text_line_create_const_text_default( 186 theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1 187 ); 188 recalc_ideal_size(button); 189 button->widget.dirty = 1; 190 191 return button; 192 } 193 194 static void 195 ltk_button_destroy(ltk_widget *self, int shallow) { 196 (void)shallow; 197 ltk_button *button = LTK_CAST_BUTTON(self); 198 if (!button) { 199 ltk_warn("Tried to destroy NULL button.\n"); 200 return; 201 } 202 ltk_text_line_destroy(button->tl); 203 ltk_free(button); 204 }