URI: 
       checkbutton.c - ltk - GUI toolkit for X11 (WIP)
  HTML git clone git://lumidify.org/ltk.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/ltk.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/ltk.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR 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 }