tbutton.c - ltk - Socket-based GUI for X11 (WIP)
HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
HTML git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
DIR Log
DIR Files
DIR Refs
DIR README
DIR LICENSE
---
tbutton.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)