ltk

GUI toolkit for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/ltk.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
Log | Files | Refs | README | 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 = &ltk_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 = &ltk_checkbutton_draw,
     53 	.destroy = &ltk_checkbutton_destroy,
     54 	.child_size_change = NULL,
     55 	.remove_child = NULL,
     56 	.recalc_ideal_size = &ltk_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 }