commit d0a001aaf611434b7abb0ca173e9bcf1fae73983
parent fcbc1ce6f1df72e293da709c8f5a29e30a1ac6ba
Author: lumidify <nobody@lumidify.org>
Date: Fri, 3 May 2024 23:16:12 +0200
Add basic radiobutton
Diffstat:
10 files changed, 377 insertions(+), 0 deletions(-)
diff --git a/Makefile b/Makefile
@@ -58,6 +58,7 @@ OBJ_LTK = \
src/ltk/ltk.o \
src/ltk/button.o \
src/ltk/checkbutton.o \
+ src/ltk/radiobutton.o \
src/ltk/graphics_xlib.o \
src/ltk/surface_cache.o \
src/ltk/event_xlib.o \
@@ -96,6 +97,7 @@ OBJ_TEST = examples/ltk/test.o
HDR_LTK = \
src/ltk/button.h \
src/ltk/checkbutton.h \
+ src/ltk/radiobutton.h \
src/ltk/color.h \
src/ltk/label.h \
src/ltk/rect.h \
diff --git a/examples/ltk/test.c b/examples/ltk/test.c
@@ -10,6 +10,7 @@
#include <ltk/menu.h>
#include <ltk/box.h>
#include <ltk/checkbutton.h>
+#include <ltk/radiobutton.h>
int
quit(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
@@ -87,6 +88,11 @@ main(int argc, char *argv[]) {
ltk_box_add(box, LTK_CAST_WIDGET(cbtn1), LTK_STICKY_LEFT);
ltk_box_add(box, LTK_CAST_WIDGET(cbtn2), LTK_STICKY_LEFT);
+ ltk_radiobutton *rbtn1 = ltk_radiobutton_create(window, "Radiobutton1", 0, NULL);
+ ltk_radiobutton *rbtn2 = ltk_radiobutton_create(window, "Radiobutton2", 1, rbtn1);
+ ltk_box_add(box, LTK_CAST_WIDGET(rbtn1), LTK_STICKY_LEFT);
+ ltk_box_add(box, LTK_CAST_WIDGET(rbtn2), LTK_STICKY_LEFT);
+
ltk_grid_add(grid, LTK_CAST_WIDGET(menu), 0, 0, 1, 2, LTK_STICKY_LEFT|LTK_STICKY_RIGHT);
ltk_grid_add(grid, LTK_CAST_WIDGET(button), 1, 0, 1, 1, LTK_STICKY_LEFT);
ltk_grid_add(grid, LTK_CAST_WIDGET(button1), 1, 1, 1, 1, LTK_STICKY_RIGHT);
diff --git a/src/ltk/checkbutton.c b/src/ltk/checkbutton.c
@@ -220,6 +220,7 @@ ltk_checkbutton_set_checked(ltk_checkbutton *button, int checked) {
button->checked = checked;
ltk_widget *self = LTK_CAST_WIDGET(button);
ltk_window_invalidate_widget_rect(self->window, self);
+ ltk_widget_emit_signal(self, LTK_CHECKBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST);
}
#define MAX(a, b) ((a) > (b) ? (a) : (b))
diff --git a/src/ltk/config.c b/src/ltk/config.c
@@ -85,6 +85,7 @@ static struct theme_handlerinfo {
{"theme:submenu", <k_submenu_get_theme_parseinfo, "theme:window", 0},
{"theme:submenuentry", <k_submenuentry_get_theme_parseinfo, "theme:window", 0},
{"theme:checkbutton", <k_checkbutton_get_theme_parseinfo, "theme:window", 0},
+ {"theme:radiobutton", <k_radiobutton_get_theme_parseinfo, "theme:window", 0},
};
GEN_SORT_SEARCH_HELPERS(themehandler, struct theme_handlerinfo, name)
diff --git a/src/ltk/graphics.h b/src/ltk/graphics.h
@@ -62,6 +62,8 @@ void ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int li
void ltk_surface_draw_border_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip_rect, int line_width, ltk_border_sides border_sides);
void ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints);
void ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points, size_t npoints, ltk_rect clip);
+void ltk_surface_fill_ellipse(ltk_surface *s, ltk_color *c, ltk_rect rect);
+void ltk_surface_fill_ellipse_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip);
/* dpi is actually 5 * the real dpi! */
/* sz.val is scaled by 100! */
diff --git a/src/ltk/graphics_xlib.c b/src/ltk/graphics_xlib.c
@@ -329,6 +329,21 @@ ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points
}
void
+ltk_surface_fill_ellipse(ltk_surface *s, ltk_color *c, ltk_rect rect) {
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
+ XFillArc(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h, 0, 360 * 64);
+}
+
+void
+ltk_surface_fill_ellipse_clipped(ltk_surface *s, ltk_color *c, ltk_rect rect, ltk_rect clip) {
+ /* NOTE: XRectangle only uses short, so this could cause issues */
+ XRectangle xclip = {clip.x, clip.y, clip.w, clip.h};
+ XSetClipRectangles(s->window->renderdata->dpy, s->window->gc, 0, 0, &xclip, 1, Unsorted);
+ ltk_surface_fill_ellipse(s, c, rect);
+ XSetClipMask(s->window->renderdata->dpy, s->window->gc, None);
+}
+
+void
ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y) {
XCopyArea(
src->window->renderdata->dpy, src->d, dst->d, src->window->gc,
diff --git a/src/ltk/radiobutton.c b/src/ltk/radiobutton.c
@@ -0,0 +1,308 @@
+/*
+ * Copyright (c) 2024 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <stdio.h>
+
+#include "config.h"
+#include "radiobutton.h"
+#include "color.h"
+#include "graphics.h"
+#include "ltk.h"
+#include "memory.h"
+#include "rect.h"
+#include "text.h"
+#include "util.h"
+#include "widget.h"
+
+/* FIXME: a lot of duplicated code from checkbutton */
+
+#define MAX_RADIOBUTTON_BORDER_WIDTH 10000
+#define MAX_RADIOBUTTON_PADDING 50000
+#define MAX_RADIOBUTTON_CIRCLE_SIZE 50000
+
+static void ltk_radiobutton_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
+static int ltk_radiobutton_release(ltk_widget *self);
+static void ltk_radiobutton_destroy(ltk_widget *self, int shallow);
+static void ltk_radiobutton_recalc_ideal_size(ltk_widget *self);
+
+static struct ltk_widget_vtable vtable = {
+ .key_press = NULL,
+ .key_release = NULL,
+ .mouse_press = NULL,
+ .mouse_release = NULL,
+ .release = <k_radiobutton_release,
+ .motion_notify = NULL,
+ .mouse_leave = NULL,
+ .mouse_enter = NULL,
+ .change_state = NULL,
+ .get_child_at_pos = NULL,
+ .resize = NULL,
+ .hide = NULL,
+ .draw = <k_radiobutton_draw,
+ .destroy = <k_radiobutton_destroy,
+ .child_size_change = NULL,
+ .remove_child = NULL,
+ .recalc_ideal_size = <k_radiobutton_recalc_ideal_size,
+ .type = LTK_WIDGET_RADIOBUTTON,
+ .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
+ .invalid_signal = LTK_RADIOBUTTON_SIGNAL_INVALID,
+};
+
+static struct {
+ ltk_color *text_color;
+
+ ltk_color *fill;
+ ltk_color *fill_pressed;
+ ltk_color *fill_hover;
+ ltk_color *fill_active;
+ ltk_color *fill_disabled;
+
+ ltk_color *circle_fill;
+ ltk_color *circle_border;
+
+ ltk_color *circle_fill_pressed;
+ ltk_color *circle_border_pressed;
+
+ ltk_color *circle_fill_hover;
+ ltk_color *circle_border_hover;
+
+ ltk_color *circle_fill_active;
+ ltk_color *circle_border_active;
+
+ ltk_color *circle_fill_disabled;
+ ltk_color *circle_border_disabled;
+
+ ltk_color *circle_fill_checked;
+ ltk_color *circle_border_checked;
+
+ ltk_color *circle_fill_pressed_checked;
+ ltk_color *circle_border_pressed_checked;
+
+ ltk_color *circle_fill_hover_checked;
+ ltk_color *circle_border_hover_checked;
+
+ ltk_color *circle_fill_active_checked;
+ ltk_color *circle_border_active_checked;
+
+ ltk_color *circle_fill_disabled_checked;
+ ltk_color *circle_border_disabled_checked;
+
+ char *font;
+ ltk_size circle_size;
+ ltk_size circle_border_width;
+ ltk_size pad;
+ ltk_size font_size;
+} theme;
+
+static ltk_theme_parseinfo parseinfo[] = {
+ {"fill", THEME_COLOR, {.color = &theme.fill}, {.color = "#000000"}, 0, 0, 0},
+ {"fill-hover", THEME_COLOR, {.color = &theme.fill_hover}, {.color = "#222222"}, 0, 0, 0},
+ {"fill-active", THEME_COLOR, {.color = &theme.fill_active}, {.color = "#222222"}, 0, 0, 0},
+ {"fill-disabled", THEME_COLOR, {.color = &theme.fill_disabled}, {.color = "#292929"}, 0, 0, 0},
+ {"fill-pressed", THEME_COLOR, {.color = &theme.fill_pressed}, {.color = "#222222"}, 0, 0, 0},
+
+ {"circle-fill", THEME_COLOR, {.color = &theme.circle_fill}, {.color = "#000000"}, 0, 0, 0},
+ {"circle-fill-hover", THEME_COLOR, {.color = &theme.circle_fill_hover}, {.color = "#222222"}, 0, 0, 0},
+ {"circle-fill-active", THEME_COLOR, {.color = &theme.circle_fill_active}, {.color = "#222222"}, 0, 0, 0},
+ {"circle-fill-disabled", THEME_COLOR, {.color = &theme.circle_fill_disabled}, {.color = "#292929"}, 0, 0, 0},
+ {"circle-fill-pressed", THEME_COLOR, {.color = &theme.circle_fill_pressed}, {.color = "#222222"}, 0, 0, 0},
+ {"circle-border", THEME_COLOR, {.color = &theme.circle_border}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-hover", THEME_COLOR, {.color = &theme.circle_border_hover}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-active", THEME_COLOR, {.color = &theme.circle_border_active}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-disabled", THEME_COLOR, {.color = &theme.circle_border_disabled}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-pressed", THEME_COLOR, {.color = &theme.circle_border_pressed}, {.color = "#FFFFFF"}, 0, 0, 0},
+
+ {"circle-fill-checked", THEME_COLOR, {.color = &theme.circle_fill_checked}, {.color = "#113355"}, 0, 0, 0},
+ {"circle-fill-hover-checked", THEME_COLOR, {.color = &theme.circle_fill_hover_checked}, {.color = "#738194"}, 0, 0, 0},
+ {"circle-fill-active-checked", THEME_COLOR, {.color = &theme.circle_fill_active_checked}, {.color = "#113355"}, 0, 0, 0},
+ {"circle-fill-disabled-checked", THEME_COLOR, {.color = &theme.circle_fill_disabled_checked}, {.color = "#292929"}, 0, 0, 0},
+ {"circle-fill-pressed-checked", THEME_COLOR, {.color = &theme.circle_fill_pressed_checked}, {.color = "#113355"}, 0, 0, 0},
+ {"circle-border-checked", THEME_COLOR, {.color = &theme.circle_border_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-hover-checked", THEME_COLOR, {.color = &theme.circle_border_hover_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-active-checked", THEME_COLOR, {.color = &theme.circle_border_active_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-disabled-checked", THEME_COLOR, {.color = &theme.circle_border_disabled_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"circle-border-pressed-checked", THEME_COLOR, {.color = &theme.circle_border_pressed_checked}, {.color = "#FFFFFF"}, 0, 0, 0},
+
+ {"circle-size", THEME_SIZE, {.size = &theme.circle_size}, {.size = {.val = 500, .unit = LTK_UNIT_MM}}, 0, MAX_RADIOBUTTON_CIRCLE_SIZE, 0},
+ {"circle-border-width", THEME_SIZE, {.size = &theme.circle_border_width}, {.size = {.val = 50, .unit = LTK_UNIT_MM}}, 0, MAX_RADIOBUTTON_BORDER_WIDTH, 0},
+ {"pad", THEME_SIZE, {.size = &theme.pad}, {.size = {.val = 100, .unit = LTK_UNIT_MM}}, 0, MAX_RADIOBUTTON_PADDING, 0},
+ {"text-color", THEME_COLOR, {.color = &theme.text_color}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
+ {"font-size", THEME_SIZE, {.size = &theme.font_size}, {.size = {.val = 1200, .unit = LTK_UNIT_PT}}, 0, 20000, 0},
+};
+
+void
+ltk_radiobutton_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len) {
+ *p = parseinfo;
+ *len = LENGTH(parseinfo);
+}
+
+/* FIXME: a lot more theme settings */
+static void
+ltk_radiobutton_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
+ ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self);
+ ltk_rect lrect = self->lrect;
+ ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
+ if (clip_final.w <= 0 || clip_final.h <= 0)
+ return;
+
+ int circle_size = ltk_size_to_pixel(theme.circle_size, self->last_dpi);
+ int circle_bw = ltk_size_to_pixel(theme.circle_border_width, self->last_dpi);
+ int pad = ltk_size_to_pixel(theme.pad, self->last_dpi);
+ ltk_color *fill = NULL, *circle_border = NULL, *circle_fill = NULL;
+ if (self->state & LTK_DISABLED) {
+ fill = theme.fill_disabled;
+ circle_border = button->checked ? theme.circle_border_disabled_checked : theme.circle_border_disabled;
+ circle_fill = button->checked ? theme.circle_fill_disabled_checked : theme.circle_fill_disabled;
+ } else if (self->state & LTK_PRESSED) {
+ fill = theme.fill_pressed;
+ circle_border = button->checked ? theme.circle_border_pressed_checked : theme.circle_border_pressed;
+ circle_fill = button->checked ? theme.circle_fill_pressed_checked : theme.circle_fill_pressed;
+ } else if (self->state & LTK_HOVER) {
+ fill = theme.fill_hover;
+ circle_border = button->checked ? theme.circle_border_hover_checked : theme.circle_border_hover;
+ circle_fill = button->checked ? theme.circle_fill_hover_checked : theme.circle_fill_hover;
+ } else if (self->state & LTK_ACTIVE) {
+ fill = theme.fill_active;
+ circle_border = button->checked ? theme.circle_border_active_checked : theme.circle_border_active;
+ circle_fill = button->checked ? theme.circle_fill_active_checked : theme.circle_fill_active;
+ } else {
+ fill = theme.fill;
+ circle_border = button->checked ? theme.circle_border_checked : theme.circle_border;
+ circle_fill = button->checked ? theme.circle_fill_checked : theme.circle_fill;
+ }
+ ltk_rect circle_rect = {x + pad, y + pad, circle_size, circle_size};
+ ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
+ ltk_surface_fill_rect(draw_surf, fill, draw_clip);
+ /* Yeah, this draws the inner part of the circle twice... */
+ if (circle_bw > 0) {
+ ltk_surface_fill_ellipse_clipped(draw_surf, circle_border, circle_rect, draw_clip);
+ }
+ if (circle_size > circle_bw * 2) {
+ circle_rect.x += circle_bw;
+ circle_rect.y += circle_bw;
+ circle_rect.w -= circle_bw * 2;
+ circle_rect.h -= circle_bw * 2;
+ ltk_surface_fill_ellipse_clipped(draw_surf, circle_fill, circle_rect, draw_clip);
+ }
+ int text_w, text_h;
+ ltk_text_line_get_size(button->tl, &text_w, &text_h);
+ int text_x = x + 2 * pad + circle_size;
+ int text_y = y + (lrect.h - text_h) / 2;
+ ltk_text_line_draw_clipped(button->tl, draw_surf, theme.text_color, text_x, text_y, draw_clip);
+ /* FIXME: only redraw if dirty (needs to be handled higher-up to only
+ call draw when dirty or window rect invalidated */
+ self->dirty = 0;
+}
+
+static void
+uncheck_other_buttons(ltk_radiobutton *button) {
+ ltk_radiobutton *prev = button->prev, *next = button->next;
+ while (prev) {
+ ltk_radiobutton_set_checked(prev, 0);
+ prev = prev->prev;
+ }
+ while (next) {
+ ltk_radiobutton_set_checked(next, 0);
+ next = next->next;
+ }
+}
+
+static int
+ltk_radiobutton_release(ltk_widget *self) {
+ ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self);
+ button->checked = !button->checked;
+ if (button->checked)
+ uncheck_other_buttons(button);
+ ltk_widget_emit_signal(self, LTK_RADIOBUTTON_SIGNAL_CHANGED, LTK_EMPTY_ARGLIST);
+ return 1;
+}
+
+int
+ltk_radiobutton_get_checked(ltk_radiobutton *button) {
+ return button->checked;
+}
+
+void
+ltk_radiobutton_set_checked(ltk_radiobutton *button, int checked) {
+ button->checked = checked;
+ if (checked)
+ uncheck_other_buttons(button);
+ ltk_widget *self = LTK_CAST_WIDGET(button);
+ ltk_window_invalidate_widget_rect(self->window, self);
+}
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+static void
+recalc_ideal_size(ltk_radiobutton *button) {
+ int text_w, text_h;
+ ltk_text_line_get_size(button->tl, &text_w, &text_h);
+ int circle_size = ltk_size_to_pixel(theme.circle_size, LTK_CAST_WIDGET(button)->last_dpi);
+ int pad = ltk_size_to_pixel(theme.pad, LTK_CAST_WIDGET(button)->last_dpi);
+ button->widget.ideal_w = text_w + pad * 3 + circle_size;
+ button->widget.ideal_h = MAX(text_h, circle_size) + pad * 2;
+}
+
+static void
+ltk_radiobutton_recalc_ideal_size(ltk_widget *self) {
+ ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self);
+ int font_size = ltk_size_to_pixel(theme.font_size, self->last_dpi);
+ ltk_text_line_set_font_size(button->tl, font_size);
+ recalc_ideal_size(button);
+}
+
+ltk_radiobutton *
+ltk_radiobutton_create(ltk_window *window, const char *text, int checked, ltk_radiobutton *group_member) {
+ ltk_radiobutton *button = ltk_malloc(sizeof(ltk_radiobutton));
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, 0, 0);
+ button->checked = checked;
+ button->prev = button->next = NULL;
+ if (group_member) {
+ button->prev = group_member;
+ button->next = group_member->next;
+ group_member->next = button;
+ /* I guess it's technically possible for a button that is only created and
+ never added to the widget hierarchy to cause the other buttons to be
+ unchecked, but I guess that's just the way it is. */
+ if (button->checked)
+ uncheck_other_buttons(button);
+ }
+
+ button->tl = ltk_text_line_create_const_text_default(
+ theme.font, ltk_size_to_pixel(theme.font_size, button->widget.last_dpi), text, -1
+ );
+ recalc_ideal_size(button);
+ button->widget.dirty = 1;
+
+ return button;
+}
+
+static void
+ltk_radiobutton_destroy(ltk_widget *self, int shallow) {
+ (void)shallow;
+ ltk_radiobutton *button = LTK_CAST_RADIOBUTTON(self);
+ if (!button) {
+ ltk_warn("Tried to destroy NULL radiobutton.\n");
+ return;
+ }
+ ltk_text_line_destroy(button->tl);
+ if (button->prev)
+ button->prev->next = button->next;
+ if (button->next)
+ button->next->prev = button->prev;
+ ltk_free(button);
+}
diff --git a/src/ltk/radiobutton.h b/src/ltk/radiobutton.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2024 lumidify <nobody@lumidify.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef LTK_RADIOBUTTON_H
+#define LTK_RADIOBUTTON_H
+
+#include "text.h"
+#include "widget.h"
+#include "window.h"
+
+#define LTK_RADIOBUTTON_SIGNAL_CHANGED -1
+#define LTK_RADIOBUTTON_SIGNAL_INVALID -2
+
+typedef struct ltk_radiobutton {
+ ltk_widget widget;
+ ltk_text_line *tl;
+ struct ltk_radiobutton *prev;
+ struct ltk_radiobutton *next;
+ int checked;
+} ltk_radiobutton;
+
+ltk_radiobutton *ltk_radiobutton_create(ltk_window *window, const char *text, int checked, ltk_radiobutton *group_member);
+int ltk_radiobutton_get_checked(ltk_radiobutton *button);
+void ltk_radiobutton_set_checked(ltk_radiobutton *button, int checked);
+
+#endif /* LTK_RADIOBUTTON_H */
diff --git a/src/ltk/widget.h b/src/ltk/widget.h
@@ -46,6 +46,7 @@ typedef enum {
LTK_WIDGET_WINDOW,
LTK_WIDGET_SCROLLBAR,
LTK_WIDGET_CHECKBUTTON,
+ LTK_WIDGET_RADIOBUTTON,
LTK_NUM_WIDGETS,
} ltk_widget_type;
@@ -186,6 +187,7 @@ typedef struct {
#define LTK_CAST_SCROLLBAR(w) (ltk_assert(w->vtable->type == LTK_WIDGET_SCROLLBAR), (ltk_scrollbar *)(w))
#define LTK_CAST_BOX(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BOX), (ltk_box *)(w))
#define LTK_CAST_CHECKBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_CHECKBUTTON), (ltk_checkbutton *)(w))
+#define LTK_CAST_RADIOBUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_RADIOBUTTON), (ltk_radiobutton *)(w))
/* FIXME: a bit weird because window never gets some of these signals */
#define LTK_WIDGET_SIGNAL_KEY_PRESS 1
diff --git a/src/ltk/widget_internal.h b/src/ltk/widget_internal.h
@@ -27,6 +27,7 @@
void ltk_window_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
void ltk_button_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
void ltk_checkbutton_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
+void ltk_radiobutton_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
void ltk_label_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
void ltk_menu_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);
void ltk_menuentry_get_theme_parseinfo(ltk_theme_parseinfo **p, size_t *len);