ltk

Socket-based GUI for X11 (WIP)
git clone git://lumidify.org/ltk.git (fast, but not encrypted)
git clone https://lumidify.org/git/ltk.git (encrypted, but very slow)
Log | Files | Refs | README | LICENSE

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+++++++++
MMakefile | 2+-
MTODO | 2++
Abox.c | 367+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abox.h | 40++++++++++++++++++++++++++++++++++++++++
Mgrid.c | 5++++-
Mltk.h | 8+++++++-
Mltkd.c | 22+++++++++++++++++++---
Ascrollbar.c | 199+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ascrollbar.h | 42++++++++++++++++++++++++++++++++++++++++++
Atestbox.gui | 6++++++
Atestbox.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, &ltk_box_draw, + NULL, &ltk_box_destroy, 0, LTK_BOX); + box->widget.mouse_press = &ltk_box_mouse_press; + box->widget.mouse_release = &ltk_box_mouse_release; + box->widget.motion_notify = &ltk_box_motion_notify; + box->widget.resize = &ltk_recalculate_box; + box->widget.remove_child = &ltk_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, &ltk_widget_mouse_press_event); +} + +static void +ltk_box_mouse_release(ltk_box *box, XEvent event) { + ltk_box_mouse_event(box, event, &ltk_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, &ltk_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, &ltk_grid_draw, NULL, &ltk_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 = &ltk_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, &ltk_scrollbar_draw, + NULL, &ltk_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