button.c (8299B)
1 /* 2 * Copyright (c) 2016-2023 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 #include <stdlib.h> 19 #include <stdint.h> 20 #include <string.h> 21 #include <stdarg.h> 22 23 #include "proto_types.h" 24 #include "event.h" 25 #include "memory.h" 26 #include "color.h" 27 #include "rect.h" 28 #include "widget.h" 29 #include "ltk.h" 30 #include "util.h" 31 #include "text.h" 32 #include "button.h" 33 #include "graphics.h" 34 #include "surface_cache.h" 35 #include "theme.h" 36 #include "cmd.h" 37 38 #define MAX_BUTTON_BORDER_WIDTH 100 39 #define MAX_BUTTON_PADDING 500 40 41 static void ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip); 42 static int ltk_button_release(ltk_widget *self); 43 static ltk_button *ltk_button_create(ltk_window *window, 44 const char *id, char *text); 45 static void ltk_button_destroy(ltk_widget *self, int shallow); 46 static void ltk_button_redraw_surface(ltk_button *button, ltk_surface *s); 47 48 static struct ltk_widget_vtable vtable = { 49 .key_press = NULL, 50 .key_release = NULL, 51 .mouse_press = NULL, 52 .mouse_release = NULL, 53 .release = <k_button_release, 54 .motion_notify = NULL, 55 .mouse_leave = NULL, 56 .mouse_enter = NULL, 57 .change_state = NULL, 58 .get_child_at_pos = NULL, 59 .resize = NULL, 60 .hide = NULL, 61 .draw = <k_button_draw, 62 .destroy = <k_button_destroy, 63 .child_size_change = NULL, 64 .remove_child = NULL, 65 .type = LTK_WIDGET_BUTTON, 66 .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS, 67 }; 68 69 static struct { 70 int border_width; 71 ltk_color text_color; 72 int pad; 73 74 ltk_color border; 75 ltk_color fill; 76 77 ltk_color border_pressed; 78 ltk_color fill_pressed; 79 80 ltk_color border_hover; 81 ltk_color fill_hover; 82 83 ltk_color border_active; 84 ltk_color fill_active; 85 86 ltk_color border_disabled; 87 ltk_color fill_disabled; 88 } theme; 89 90 static ltk_theme_parseinfo parseinfo[] = { 91 {"border", THEME_COLOR, {.color = &theme.border}, {.color = "#339999"}, 0, 0, 0}, 92 {"border-hover", THEME_COLOR, {.color = &theme.border_hover}, {.color = "#FFFFFF"}, 0, 0, 0}, 93 {"border-active", THEME_COLOR, {.color = &theme.border_active}, {.color = "#FFFFFF"}, 0, 0, 0}, 94 {"border-disabled", THEME_COLOR, {.color = &theme.border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0}, 95 {"border-pressed", THEME_COLOR, {.color = &theme.border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0}, 96 {"border-width", THEME_INT, {.i = &theme.border_width}, {.i = 2}, 0, MAX_BUTTON_BORDER_WIDTH, 0}, 97 {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#113355"}, 0, 0, 0}, 98 {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#738194"}, 0, 0, 0}, 99 {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#113355"}, 0, 0, 0}, 100 {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0}, 101 {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#113355"}, 0, 0, 0}, 102 {"pad", THEME_INT, {.i = &theme.pad}, {.i = 5}, 0, MAX_BUTTON_PADDING, 0}, 103 {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0}, 104 }; 105 static int parseinfo_sorted = 0; 106 107 int 108 ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) { 109 return ltk_theme_handle_value(window, "button", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted); 110 } 111 112 int 113 ltk_button_fill_theme_defaults(ltk_window *window) { 114 return ltk_theme_fill_defaults(window, "button", parseinfo, LENGTH(parseinfo)); 115 } 116 117 void 118 ltk_button_uninitialize_theme(ltk_window *window) { 119 ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo)); 120 } 121 122 /* FIXME: only keep text in surface to avoid large surface */ 123 static void 124 ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) { 125 ltk_button *button = (ltk_button *)self; 126 ltk_rect lrect = self->lrect; 127 ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h}); 128 if (clip_final.w <= 0 || clip_final.h <= 0) 129 return; 130 ltk_surface *s; 131 ltk_surface_cache_request_surface_size(button->key, lrect.w, lrect.h); 132 if (!ltk_surface_cache_get_surface(button->key, &s) || self->dirty) 133 ltk_button_redraw_surface(button, s); 134 ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y); 135 } 136 137 static void 138 ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) { 139 ltk_rect rect = button->widget.lrect; 140 int bw = theme.border_width; 141 ltk_color *border = NULL, *fill = NULL; 142 /* FIXME: HOVERACTIVE STATE */ 143 if (button->widget.state & LTK_DISABLED) { 144 border = &theme.border_disabled; 145 fill = &theme.fill_disabled; 146 } else if (button->widget.state & LTK_PRESSED) { 147 border = &theme.border_pressed; 148 fill = &theme.fill_pressed; 149 } else if (button->widget.state & LTK_HOVER) { 150 border = &theme.border_hover; 151 fill = &theme.fill_hover; 152 } else if (button->widget.state & LTK_ACTIVE) { 153 border = &theme.border_active; 154 fill = &theme.fill_active; 155 } else { 156 border = &theme.border; 157 fill = &theme.fill; 158 } 159 rect.x = 0; 160 rect.y = 0; 161 ltk_surface_fill_rect(s, fill, rect); 162 if (bw > 0) 163 ltk_surface_draw_rect(s, border, (ltk_rect){bw / 2, bw / 2, rect.w - bw, rect.h - bw}, bw); 164 165 int text_w, text_h; 166 ltk_text_line_get_size(button->tl, &text_w, &text_h); 167 int text_x = (rect.w - text_w) / 2; 168 int text_y = (rect.h - text_h) / 2; 169 ltk_text_line_draw(button->tl, s, &theme.text_color, text_x, text_y); 170 button->widget.dirty = 0; 171 } 172 173 static int 174 ltk_button_release(ltk_widget *self) { 175 ltk_queue_specific_event(self, "button", LTK_PWEVENTMASK_BUTTON_PRESS, "press"); 176 return 1; 177 } 178 179 static ltk_button * 180 ltk_button_create(ltk_window *window, const char *id, char *text) { 181 ltk_button *button = ltk_malloc(sizeof(ltk_button)); 182 183 uint16_t font_size = window->theme->font_size; 184 button->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1); 185 int text_w, text_h; 186 ltk_text_line_get_size(button->tl, &text_w, &text_h); 187 ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h); 188 button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2; 189 button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2; 190 button->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, button->widget.ideal_w, button->widget.ideal_h); 191 button->widget.dirty = 1; 192 193 return button; 194 } 195 196 static void 197 ltk_button_destroy(ltk_widget *self, int shallow) { 198 (void)shallow; 199 ltk_button *button = (ltk_button *)self; 200 if (!button) { 201 ltk_warn("Tried to destroy NULL button.\n"); 202 return; 203 } 204 ltk_surface_cache_release_key(button->key); 205 ltk_text_line_destroy(button->tl); 206 ltk_free(button); 207 } 208 209 /* button <button id> create <text> */ 210 static int 211 ltk_button_cmd_create( 212 ltk_window *window, 213 ltk_button *button_unneeded, 214 ltk_cmd_token *tokens, 215 size_t num_tokens, 216 ltk_error *err) { 217 (void)button_unneeded; 218 ltk_cmdarg_parseinfo cmd[] = { 219 {.type = CMDARG_IGNORE, .optional = 0}, 220 {.type = CMDARG_STRING, .optional = 0}, 221 {.type = CMDARG_IGNORE, .optional = 0}, 222 {.type = CMDARG_STRING, .optional = 0}, 223 }; 224 if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err)) 225 return 1; 226 if (!ltk_widget_id_free(cmd[1].val.str)) { 227 err->type = ERR_WIDGET_ID_IN_USE; 228 err->arg = 1; 229 return 1; 230 } 231 ltk_button *button = ltk_button_create(window, cmd[1].val.str, cmd[3].val.str); 232 ltk_set_widget((ltk_widget *)button, cmd[1].val.str); 233 234 return 0; 235 } 236 237 static struct button_cmd { 238 char *name; 239 int (*func)(ltk_window *, ltk_button *, ltk_cmd_token *, size_t, ltk_error *); 240 int needs_all; 241 } button_cmds[] = { 242 {"create", <k_button_cmd_create, 1}, 243 }; 244 245 GEN_CMD_HELPERS(ltk_button_cmd, LTK_WIDGET_BUTTON, ltk_button, button_cmds, struct button_cmd)