commit 829de8bbcb77ea2607f1ec22113222ac755256b8
parent 87d191828a7c0dd4eec3f61dc07fd40768a45f8c
Author: lumidify <nobody@lumidify.org>
Date: Thu, 14 Jan 2021 22:43:57 +0100
Add basic box widget
The scrollbar isn't supported yet and a lot still needs to be fixed.
Diffstat:
M | .ltk/theme.ini | | | 9 | +++++++++ |
M | Makefile | | | 2 | +- |
M | TODO | | | 2 | ++ |
A | box.c | | | 367 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | box.h | | | 40 | ++++++++++++++++++++++++++++++++++++++++ |
M | grid.c | | | 5 | ++++- |
M | ltk.h | | | 8 | +++++++- |
M | ltkd.c | | | 22 | +++++++++++++++++++--- |
A | scrollbar.c | | | 199 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | scrollbar.h | | | 42 | ++++++++++++++++++++++++++++++++++++++++++ |
A | testbox.gui | | | 6 | ++++++ |
A | testbox.sh | | | 20 | ++++++++++++++++++++ |
12 files changed, 716 insertions(+), 6 deletions(-)
diff --git a/.ltk/theme.ini b/.ltk/theme.ini
@@ -21,3 +21,12 @@ fill_disabled = #292929
[label]
text_color = #FFFFFF
pad = 5
+
+[scrollbar]
+size = 5
+bg = #000000
+bg_disabled = #555555
+fg = #113355
+fg_pressed = #113355
+fg_active = #738194
+fg_disabled = #292929
diff --git a/Makefile b/Makefile
@@ -1,6 +1,6 @@
include config.mk
-OBJ += color.o util.o ltkd.o ini.o grid.o button.o label.o draw.o
+OBJ += color.o util.o ltkd.o ini.o grid.o box.o scrollbar.o button.o label.o draw.o
all: ltkd ltkc
diff --git a/TODO b/TODO
@@ -1,3 +1,5 @@
+Possibly implement xrandr support for proper dpi handling
+
Double-buffering; general improvements to rendering...
Convert points to pixels for stb rendering (currently, the size between
diff --git a/box.c b/box.c
@@ -0,0 +1,367 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "color.h"
+#include "ltk.h"
+#include "util.h"
+#include "scrollbar.h"
+#include "box.h"
+
+static void ltk_box_draw(ltk_box *box);
+static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient);
+static void ltk_box_destroy(ltk_box *box, int shallow);
+static void ltk_recalculate_box(ltk_box *box);
+/* FIXME: Why is sticky unsigned short? */
+static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, char **errstr);
+static int ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errstr);
+/* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, char **errstr); */
+static void ltk_box_mouse_event(ltk_box *box, XEvent event, void (*handler)(ltk_widget *, XEvent));
+static void ltk_box_mouse_press(ltk_box *box, XEvent event);
+static void ltk_box_mouse_release(ltk_box *box, XEvent event);
+static void ltk_box_motion_notify(ltk_box *box, XEvent event);
+
+static int ltk_box_cmd_add(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr);
+static int ltk_box_cmd_remove(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr);
+/*
+static int ltk_box_cmd_clear
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr);
+*/
+static int ltk_box_cmd_create(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr);
+
+static void
+ltk_box_draw(ltk_box *box) {
+ ltk_widget *ptr;
+ for (size_t i = 0; i < box->num_widgets; i++) {
+ ptr = box->widgets[i];
+ ptr->draw(ptr);
+ }
+}
+
+static ltk_box *
+ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
+ ltk_box *box = malloc(sizeof(ltk_box));
+ if (!box)
+ ltk_fatal("Unable to allocate memory for box.\n");
+
+ ltk_fill_widget_defaults(&box->widget, id, window, <k_box_draw,
+ NULL, <k_box_destroy, 0, LTK_BOX);
+ box->widget.mouse_press = <k_box_mouse_press;
+ box->widget.mouse_release = <k_box_mouse_release;
+ box->widget.motion_notify = <k_box_motion_notify;
+ box->widget.resize = <k_recalculate_box;
+ box->widget.remove_child = <k_box_remove;
+
+ box->sc = NULL;
+ box->widgets = NULL;
+ box->num_alloc = 0;
+ box->num_widgets = 0;
+ box->orient = orient;
+
+ return box;
+}
+
+static void
+ltk_box_destroy(ltk_box *box, int shallow) {
+ ltk_widget *ptr;
+ if (!shallow) {
+ for (size_t i = 0; i < box->num_widgets; i++) {
+ ptr = box->widgets[i];
+ ptr->destroy(ptr, shallow);
+ }
+ }
+ free(box->widgets);
+ ltk_remove_widget(box->widget.window, box->widget.id);
+ free(box->widget.id);
+ free(box);
+}
+
+/* FIXME: Allow no sticky value to be centered in middle */
+/* FIXME: Need some sort of "visible rect" so widgets don't draw outside */
+/* FIXME: Make this function name more consistent */
+static void
+ltk_recalculate_box(ltk_box *box) {
+ ltk_widget *ptr;
+ int cur_pos = 0;
+ for (size_t i = 0; i < box->num_widgets; i++) {
+ ptr = box->widgets[i];
+ if (box->orient == LTK_HORIZONTAL) {
+ ptr->rect.x = cur_pos;
+ cur_pos += ptr->rect.w;
+ if (ptr->sticky & LTK_STICKY_TOP && ptr->sticky & LTK_STICKY_BOTTOM)
+ ptr->rect.h = box->widget.rect.h;
+ if (ptr->sticky & LTK_STICKY_TOP)
+ ptr->rect.y = box->widget.rect.y;
+ else if (ptr->sticky & LTK_STICKY_BOTTOM)
+ ptr->rect.y = box->widget.rect.y + box->widget.rect.h - ptr->rect.h;
+ } else {
+ ptr->rect.y = cur_pos;
+ cur_pos += ptr->rect.h;
+ if (ptr->sticky & LTK_STICKY_LEFT && ptr->sticky & LTK_STICKY_RIGHT)
+ ptr->rect.w = box->widget.rect.w;
+ if (ptr->sticky & LTK_STICKY_LEFT)
+ ptr->rect.x = box->widget.rect.x;
+ else if (ptr->sticky & LTK_STICKY_RIGHT)
+ ptr->rect.x = box->widget.rect.x + box->widget.rect.w - ptr->rect.w;
+ }
+ }
+}
+
+static int
+ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, unsigned short sticky, char **errstr) {
+ if (widget->parent) {
+ *errstr = "Widget already inside a container.\n";
+ return 1;
+ }
+ if (box->num_alloc >= box->num_widgets) {
+ size_t new_size = box->num_alloc > 0 ? box->num_alloc * 2 : 4;
+ ltk_widget **new = realloc(box->widgets, new_size * sizeof(ltk_widget *));
+ if (!new)
+ ltk_fatal_errno("Unable to allocate memory for widgets in box.\n");
+ box->num_alloc = new_size;
+ box->widgets = new;
+ }
+
+ box->widgets[box->num_widgets++] = widget;
+ widget->parent = box;
+ widget->sticky = sticky;
+ ltk_recalculate_box(box);
+ ltk_window_invalidate_rect(window, box->widget.rect);
+
+ return 0;
+}
+
+static int
+ltk_box_remove(ltk_window *window, ltk_widget *widget, ltk_box *box, char **errstr) {
+ if (widget->parent != box) {
+ *errstr = "Widget isn't contained in given box.\n";
+ return 1;
+ }
+ widget->parent = NULL;
+ for (size_t i = 0; i < box->num_widgets; i++) {
+ if (box->widgets[i] == widget) {
+ if (i < box->num_widgets - 1)
+ memmove(box->widgets + i, box->widgets + i + 1,
+ (box->num_widgets - i - 1) * sizeof(ltk_widget *));
+ box->num_widgets--;
+ ltk_window_invalidate_rect(window, box->widget.rect);
+ return 0;
+ }
+ }
+
+ *errstr = "Widget isn't contained in given box.\n";
+ return 1;
+}
+
+static void
+ltk_box_mouse_event(ltk_box *box, XEvent event, void (*handler)(ltk_widget *, XEvent)) {
+ int pos, start, size;
+ ltk_widget *widget;
+
+ /*
+ if (ltk_collide_rect(box->sc.widget.rect, event.xbutton.x, event.xbutton.y))
+ handler(box->sc, event);
+ */
+
+ for (size_t i = 0; i < box->num_widgets; i++) {
+ widget = box->widgets[i];
+ if (box->orient == LTK_HORIZONTAL) {
+ pos = event.xbutton.x;
+ start = widget->rect.x;
+ size = widget->rect.w;
+ } else {
+ pos = event.xbutton.y;
+ start = widget->rect.y;
+ size = widget->rect.h;
+ }
+ if (pos >= start && pos <= start + size) {
+ handler(widget, event);
+ return;
+ }
+ }
+}
+
+static void
+ltk_box_mouse_press(ltk_box *box, XEvent event) {
+ ltk_box_mouse_event(box, event, <k_widget_mouse_press_event);
+}
+
+static void
+ltk_box_mouse_release(ltk_box *box, XEvent event) {
+ ltk_box_mouse_event(box, event, <k_widget_mouse_release_event);
+}
+
+/* FIXME: Release events shouldn't happen if that widget wasn't actually
+ pressed! Scrollbar still needs to receive motion notify while pressed
+ even if not actually hovered over! */
+static void
+ltk_box_motion_notify(ltk_box *box, XEvent event) {
+ short pressed = (event.xmotion.state & Button1Mask) == Button1Mask;
+ if (pressed)
+ return;
+ ltk_box_mouse_event(box, event, <k_widget_motion_notify_event);
+}
+
+/* box <box id> add <widget id> <sticky> */
+static int
+ltk_box_cmd_add(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr) {
+ const char *c;
+ ltk_box *box;
+ ltk_widget *widget;
+
+ int sticky = 0;
+ if (num_tokens != 5) {
+ *errstr = "Invalid number of arguments.\n";
+ return 1;
+ }
+ box = ltk_get_widget(tokens[1], LTK_BOX, errstr);
+ widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
+ if (!box || !widget) {
+ *errstr = "Invalid widget ID.\n";
+ return 1;
+ }
+
+ /* FIXME: Allow default value (just no sticky at all) */
+ for (c = tokens[4]; *c != '\0'; c++) {
+ if (*c == 'n') {
+ sticky |= LTK_STICKY_TOP;
+ } else if (*c == 's') {
+ sticky |= LTK_STICKY_BOTTOM;
+ } else if (*c == 'e') {
+ sticky |= LTK_STICKY_RIGHT;
+ } else if (*c == 'w') {
+ sticky |= LTK_STICKY_LEFT;
+ } else if (*c != 'u') {
+ *errstr = "Invalid sticky specification.\n";
+ return 1;
+ }
+ }
+
+ return ltk_box_add(window, widget, box, sticky, errstr);
+}
+
+/* box <box id> remove <widget id> */
+static int
+ltk_box_cmd_remove(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr) {
+ ltk_box *box;
+ ltk_widget *widget;
+
+ if (num_tokens != 4) {
+ *errstr = "Invalid number of arguments.\n";
+ return 1;
+ }
+ box = ltk_get_widget(tokens[1], LTK_BOX, errstr);
+ widget = ltk_get_widget(tokens[3], LTK_WIDGET, errstr);
+ if (!box || !widget) {
+ *errstr = "Invalid widget ID.\n";
+ return 1;
+ }
+
+ return ltk_box_remove(window, widget, box, errstr);
+}
+
+/* box <box id> create <orientation> */
+static int
+ltk_box_cmd_create(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr) {
+ ltk_box *box;
+ ltk_orientation orient;
+
+ if (num_tokens != 4) {
+ *errstr = "Invalid number of arguments.\n";
+ return 1;
+ }
+ if (!ltk_widget_id_free(tokens[1])) {
+ *errstr = "Widget ID already taken.\n";
+ return 1;
+ }
+ if (!strcmp(tokens[3], "horizontal")) {
+ orient = LTK_HORIZONTAL;
+ } else if (!strcmp(tokens[3], "vertical")) {
+ orient = LTK_VERTICAL;
+ } else {
+ *errstr = "Invalid orientation.\n";
+ return 1;
+ }
+ box = ltk_box_create(window, tokens[1], orient);
+ ltk_set_widget(box, tokens[1]);
+
+ return 0;
+}
+
+/* box <box id> <command> ... */
+int
+ltk_box_cmd(
+ ltk_window *window,
+ char **tokens,
+ size_t num_tokens,
+ char **errstr) {
+ if (num_tokens < 3) {
+ *errstr = "Invalid number of arguments.\n";
+ return 1;
+ }
+ if (strcmp(tokens[2], "add") == 0) {
+ return ltk_box_cmd_add(window, tokens, num_tokens, errstr);
+ } else if (strcmp(tokens[2], "remove") == 0) {
+ return ltk_box_cmd_remove(window, tokens, num_tokens, errstr);
+ } else if (strcmp(tokens[2], "create") == 0) {
+ return ltk_box_cmd_create(window, tokens, num_tokens, errstr);
+ } else {
+ *errstr = "Invalid command.\n";
+ return 1;
+ }
+
+ return 0; /* Well, I guess this is impossible anyways... */
+}
diff --git a/box.h b/box.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _LTK_BOX_H_
+#define _LTK_BOX_H_
+
+/* Requires the following includes: "scrollbar.h" "ltk.h" */
+
+typedef struct {
+ ltk_widget widget;
+ ltk_scrollbar *sc;
+ ltk_widget **widgets;
+ size_t num_alloc;
+ size_t num_widgets;
+ ltk_orientation orient;
+} ltk_box;
+
+int ltk_box_cmd(ltk_window *window, char **tokens, size_t num_tokens, char **errstr);
+
+#endif /* _LTK_BOX_H_ */
diff --git a/grid.c b/grid.c
@@ -1,6 +1,6 @@
/*
* This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2016, 2017, 2018, 2020 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016, 2017, 2018, 2020, 2021 lumidify <nobody@lumidify.org>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -104,6 +104,8 @@ ltk_grid_draw(ltk_grid *grid) {
static ltk_grid *
ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
ltk_grid *grid = malloc(sizeof(ltk_grid));
+ if (!grid)
+ ltk_fatal("Unable to allocate memory for grid.\n");
ltk_fill_widget_defaults(&grid->widget, id, window, <k_grid_draw,
NULL, <k_grid_destroy, 0, LTK_GRID);
@@ -123,6 +125,7 @@ ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
/* Positions have one extra for the end */
grid->row_pos = malloc((rows + 1) * sizeof(int));
grid->column_pos = malloc((columns + 1) * sizeof(int));
+ /* FIXME: wow, that's horrible, this should just use memset */
int i;
for (i = 0; i < rows; i++) {
grid->row_heights[i] = 0;
diff --git a/ltk.h b/ltk.h
@@ -47,6 +47,11 @@ typedef enum {
} ltk_sticky_mask;
typedef enum {
+ LTK_VERTICAL,
+ LTK_HORIZONTAL
+} ltk_orientation;
+
+typedef enum {
LTK_NORMAL,
LTK_PRESSED,
LTK_ACTIVE,
@@ -58,7 +63,8 @@ typedef enum {
LTK_BUTTON,
LTK_DRAW,
LTK_LABEL,
- LTK_WIDGET
+ LTK_WIDGET,
+ LTK_BOX
} ltk_widget_type;
typedef struct ltk_window ltk_window;
diff --git a/ltkd.c b/ltkd.c
@@ -53,6 +53,8 @@
#include "draw.h"
#include "button.h"
#include "label.h"
+#include "scrollbar.h"
+#include "box.h"
#define MAX_SOCK_CONNS 20
#define READ_BLK_SIZE 128
@@ -320,6 +322,7 @@ get_sock_path(char *basedir, Window id) {
path = malloc(len + 20);
if (!path)
return NULL;
+ /* FIXME: also check for less than 0 */
if (snprintf(path, len + 20, "%s/%d.sock", basedir, id) >= len + 20)
ltk_fatal("Tell lumidify to fix his code.\n");
@@ -574,6 +577,7 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int
window->dpy = XOpenDisplay(NULL);
window->screen = DefaultScreen(window->dpy);
+ //printf("%d %d\n", WidthOfScreen(XDefaultScreenOfDisplay(window->dpy)), WidthMMOfScreen(XDefaultScreenOfDisplay(window->dpy)));
window->cm = DefaultColormap(window->dpy, window->screen);
theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
window->theme.font = NULL;
@@ -598,6 +602,8 @@ ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int
window->other_event = <k_window_other_event;
+ window->first_event = window->last_event = NULL;
+
window->rect.w = 0;
window->rect.h = 0;
window->rect.x = 0;
@@ -635,6 +641,8 @@ ltk_destroy_window(ltk_window *window) {
XDestroyWindow(window->dpy, window->xwindow);
ltk_cleanup_text();
XCloseDisplay(window->dpy);
+ /* FIXME: This doesn't work because it can sometimes be a readonly
+ string from ltk_window_setup_theme_defaults! */
if (window->theme.font)
free(window->theme.font);
free(window);
@@ -735,9 +743,13 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
void (*draw) (void *), void (*change_state) (void *),
void (*destroy) (void *, int), unsigned int needs_redraw,
ltk_widget_type type) {
- widget->id = strdup(id);
- if (!widget->id)
- ltk_fatal_errno("Unable to allocate copy of widget ID.\n");
+ if (id) {
+ widget->id = strdup(id);
+ if (!widget->id)
+ ltk_fatal_errno("Unable to allocate copy of widget ID.\n");
+ } else {
+ widget->id = NULL;
+ }
widget->window = window;
widget->active_widget = NULL;
widget->parent = NULL;
@@ -793,6 +805,8 @@ void
ltk_widget_mouse_release_event(ltk_widget *widget, XEvent event) {
if (!widget || widget->state == LTK_DISABLED)
return;
+ /* FIXME: Why does it check for LTK_PRESSED? Is this left over from
+ old ltkx, where there was a difference between hover and active? */
if (widget->state == LTK_PRESSED) {
widget->state = LTK_ACTIVE;
if (widget->change_state)
@@ -1144,6 +1158,8 @@ process_commands(ltk_window *window, struct ltk_sock_info *sock) {
continue;
if (strcmp(tokens[0], "grid") == 0) {
err = ltk_grid_cmd(window, tokens, num_tokens, &errstr);
+ } else if (strcmp(tokens[0], "box") == 0) {
+ err = ltk_box_cmd(window, tokens, num_tokens, &errstr);
} else if (strcmp(tokens[0], "button") == 0) {
err = ltk_button_cmd(window, tokens, num_tokens, &errstr);
} else if (strcmp(tokens[0], "label") == 0) {
diff --git a/scrollbar.c b/scrollbar.c
@@ -0,0 +1,199 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+
+#include "color.h"
+#include "ltk.h"
+#include "util.h"
+#include "scrollbar.h"
+
+static void ltk_scrollbar_draw(ltk_scrollbar *scrollbar);
+static void ltk_scrollbar_mouse_press(ltk_scrollbar *scrollbar, XEvent event);
+static void ltk_scrollbar_motion_notify(ltk_scrollbar *scrollbar, XEvent event);
+static ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient);
+static void ltk_scrollbar_destroy(ltk_scrollbar *scrollbar, int shallow);
+
+static struct {
+ int size; /* width or height, depending on orientation */
+ LtkColor bg_normal;
+ LtkColor bg_disabled;
+ LtkColor fg_normal;
+ LtkColor fg_active;
+ LtkColor fg_pressed;
+ LtkColor fg_disabled;
+} theme;
+
+void
+ltk_scrollbar_setup_theme_defaults(ltk_window *window) {
+ theme.size = 15;
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#000000", &theme.bg_normal);
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#555555", &theme.bg_disabled);
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#113355", &theme.fg_normal);
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#738194", &theme.fg_active);
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#113355", &theme.fg_pressed);
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ "#292929", &theme.fg_disabled);
+}
+
+void
+ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value) {
+ if (strcmp(prop, "size") == 0) {
+ theme.size = atoi(value); /* FIXME: proper strtonum */
+ } else if (strcmp(prop, "bg") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.bg_normal);
+ } else if (strcmp(prop, "bg_disabled") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.bg_disabled);
+ } else if (strcmp(prop, "fg") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.fg_normal);
+ } else if (strcmp(prop, "fg_active") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.fg_active);
+ } else if (strcmp(prop, "fg_pressed") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.fg_pressed);
+ } else if (strcmp(prop, "fg_disabled") == 0) {
+ ltk_color_create(window->dpy, window->screen, window->cm,
+ value, &theme.fg_disabled);
+ } else {
+ ltk_warn("Unknown property \"%s\" for scrollbar style.\n", prop);
+ }
+}
+
+static void
+ltk_scrollbar_draw(ltk_scrollbar *scrollbar) {
+ LtkColor *bg, *fg;
+ int handle_x, handle_y, handle_w, handle_h;
+ ltk_window *window = scrollbar->widget.window;
+ ltk_rect rect = scrollbar->widget.rect;
+ switch (scrollbar->widget.state) {
+ case LTK_NORMAL:
+ bg = &theme.bg_normal;
+ fg = &theme.fg_normal;
+ break;
+ case LTK_PRESSED:
+ bg = &theme.bg_normal;
+ fg = &theme.fg_pressed;
+ break;
+ case LTK_ACTIVE:
+ bg = &theme.bg_normal;
+ fg = &theme.fg_active;
+ break;
+ case LTK_DISABLED:
+ bg = &theme.bg_disabled;
+ fg = &theme.fg_disabled;
+ break;
+ default:
+ ltk_fatal("No style found for current scrollbar state.\n");
+ }
+ XSetForeground(window->dpy, window->gc, bg->xcolor.pixel);
+ XFillRectangle(window->dpy, window->xwindow, window->gc,
+ rect.x, rect.y, rect.w, rect.h);
+ XSetForeground(window->dpy, window->gc, fg->xcolor.pixel);
+ /* FIXME: maybe too much calculation in draw function - move to
+ resizing function? */
+ if (scrollbar->orient == LTK_HORIZONTAL) {
+ handle_y = rect.y;
+ handle_h = rect.h;
+ handle_x = (int)(rect.x + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.w);
+ handle_w = (int)((rect.w / (double)scrollbar->virtual_size) * rect.w);
+ } else {
+ handle_x = rect.x;
+ handle_w = rect.w;
+ handle_y = (int)(rect.x + (scrollbar->cur_pos / (double)scrollbar->virtual_size) * rect.h);
+ handle_h = (int)((rect.h / (double)scrollbar->virtual_size) * rect.h);
+ }
+ if (handle_w <= 0 || handle_h <= 0)
+ return;
+ XFillRectangle(window->dpy, window->xwindow, window->gc,
+ handle_x, handle_y, handle_w, handle_h);
+}
+
+static void
+ltk_scrollbar_mouse_press(ltk_scrollbar *scrollbar, XEvent event) {
+ scrollbar->last_mouse_x = event.xbutton.x;
+ scrollbar->last_mouse_y = event.xbutton.y;
+}
+
+static void
+ltk_scrollbar_motion_notify(ltk_scrollbar *scrollbar, XEvent event) {
+ double scale;
+ int delta, max_pos;
+ if (scrollbar->widget.state != LTK_PRESSED)
+ return;
+ if (scrollbar->orient == LTK_HORIZONTAL) {
+ delta = event.xbutton.x - scrollbar->last_mouse_x;
+ max_pos = scrollbar->virtual_size - scrollbar->widget.rect.w;
+ scale = scrollbar->virtual_size / (double)scrollbar->widget.rect.w;
+ } else {
+ delta = event.xbutton.y - scrollbar->last_mouse_y;
+ max_pos = scrollbar->virtual_size - scrollbar->widget.rect.h;
+ scale = scrollbar->virtual_size / (double)scrollbar->widget.rect.h;
+ }
+ scrollbar->cur_pos += (int)(scale * delta);
+ if (scrollbar->cur_pos < 0)
+ scrollbar->cur_pos = 0;
+ else if (scrollbar->cur_pos > max_pos)
+ scrollbar->cur_pos = max_pos;
+ scrollbar->last_mouse_x = event.xbutton.x;
+ scrollbar->last_mouse_y = event.xbutton.y;
+}
+
+static ltk_scrollbar *
+ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) {
+ ltk_scrollbar *sc = malloc(sizeof(ltk_scrollbar));
+ if (!sc)
+ ltk_fatal_errno("Unable to allocate memory for scrollbar.\n");
+ ltk_fill_widget_default(sc, NULL, window, <k_scrollbar_draw,
+ NULL, <k_scrollbar_destroy, 1);
+ sc->last_mouse_x = sc->last_mouse_y = 0;
+ sc->virtual_size = 0;
+ sc->cur_pos = 0;
+ sc->orient = orient;
+ if (orient == LTK_HORIZONTAL)
+ sc->widget.rect.h = theme.size;
+ else
+ sc->widget.rect.w = theme.size;
+
+ return sc;
+}
+
+static void
+ltk_scrollbar_destroy(ltk_scrollbar *scrollbar, int shallow) {
+ free(scrollbar);
+}
diff --git a/scrollbar.h b/scrollbar.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2021 lumidify <nobody@lumidify.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#ifndef _LTK_SCROLLBAR_H_
+#define _LTK_SCROLLBAR_H_
+
+/* Requires: "ltk.h" */
+
+typedef struct {
+ ltk_widget widget;
+ int cur_pos;
+ int virtual_size;
+ int last_mouse_x;
+ int last_mouse_y;
+ ltk_orientation orient;
+} ltk_scrollbar;
+
+void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
+void ltk_scrollbar_setup_theme_defaults(ltk_window *window);
+void ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value);
+
+#endif /* _LTK_SCROLLBAR_H_ */
diff --git a/testbox.gui b/testbox.gui
@@ -0,0 +1,6 @@
+box box1 create vertical
+set-root-widget box1
+button btn1 create "I'm a button!"
+button btn2 create "I'm also a button!"
+box box1 add btn1 ew
+box box1 add btn2 e
diff --git a/testbox.sh b/testbox.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+export LTKDIR="`pwd`/.ltk"
+ltk_id=`./ltkd -t "Cool Window"`
+if [ $? -ne 0 ]; then
+ echo "Unable to start ltkd." >&2
+ exit 1
+fi
+
+cat testbox.gui | ./ltkc $ltk_id | while read cmd
+do
+ case "$cmd" in
+ "btn1 button_click")
+ echo "quit"
+ ;;
+ *)
+ printf "%s\n" "$cmd" >&2
+ ;;
+ esac
+done | ./ltkc $ltk_id