commit a664daed365f583f4a5dc42b31f2f80ceeb83b4d
parent 85b5c97bb7c4e1b85f9c322d1beccd1911f0bf09
Author: lumidify <nobody@lumidify.org>
Date:   Sat,  6 Jun 2020 20:48:53 +0200
Add basic drawing area
Diffstat:
| M | Makefile |  |  | 2 | +- | 
| M | button.c |  |  | 18 | +++++++++--------- | 
| A | draw.c |  |  | 335 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ | 
| A | draw.h |  |  | 42 | ++++++++++++++++++++++++++++++++++++++++++ | 
| M | ltk.c |  |  | 21 | +++++++++++++-------- | 
| M | ltk.h |  |  | 5 | +++-- | 
| A | test_draw.gui |  |  | 10 | ++++++++++ | 
7 files changed, 413 insertions(+), 20 deletions(-)
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
 LIBS = -lm `pkg-config --libs x11 fontconfig`
 STD = -std=c99
 CFLAGS = -g -w -fcommon -Wall -Werror -Wextra `pkg-config --cflags x11 fontconfig` -pedantic
-OBJ = text_line.o text_common.o stb_truetype.o ltk.o ini.o grid.o button.o
+OBJ = text_line.o text_common.o stb_truetype.o ltk.o ini.o grid.o button.o draw.o
 COMPATOBJ = 
 # Uncomment if not using OpenBSD
 #COMPATOBJ = strtonum.o
diff --git a/button.c b/button.c
@@ -58,23 +58,23 @@ ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) 
 	} else if (strcmp(prop, "pad") == 0) {
 		theme->button->pad = atoi(value);
 	} else if (strcmp(prop, "border") == 0) {
-		theme->button->border = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->border);
 	} else if (strcmp(prop, "fill") == 0) {
-		theme->button->fill = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->fill);
 	} else if (strcmp(prop, "border_pressed") == 0) {
-		theme->button->border_pressed = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->border_pressed);
 	} else if (strcmp(prop, "fill_pressed") == 0) {
-		theme->button->fill_pressed = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->fill_pressed);
 	} else if (strcmp(prop, "border_active") == 0) {
-		theme->button->border_active = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->border_active);
 	} else if (strcmp(prop, "fill_active") == 0) {
-		theme->button->fill_active = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->fill_active);
 	} else if (strcmp(prop, "border_disabled") == 0) {
-		theme->button->border_disabled = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->border_disabled);
 	} else if (strcmp(prop, "fill_disabled") == 0) {
-		theme->button->fill_disabled = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->fill_disabled);
 	} else if (strcmp(prop, "text_color") == 0) {
-		theme->button->text_color = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &theme->button->text_color);
 	} else {
 		(void)printf("WARNING: Unknown property \"%s\" for button style.\n", prop);
 	}
diff --git a/draw.c b/draw.c
@@ -0,0 +1,335 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2020 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 <string.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include "util.h"
+#include "khash.h"
+#include "stb_truetype.h"
+#include <fontconfig/fontconfig.h>
+#include "text_common.h"
+#include "ltk.h"
+#include "draw.h"
+
+static void ltk_draw_draw(ltk_draw *draw);
+static ltk_draw *ltk_draw_create(ltk_window *window,
+    const char *id, int w, int h, const char *color);
+static void ltk_draw_resize(ltk_draw *draw, int orig_w, int orig_h);
+static void ltk_draw_destroy(ltk_draw *draw, int shallow);
+static void ltk_draw_clear(ltk_window *window, ltk_draw *draw);
+static void ltk_draw_set_color(ltk_window *window, ltk_draw *draw, const char *color);
+static void ltk_draw_line(ltk_window *window, ltk_draw *draw, int x1, int y1, int x2, int y2);
+static void ltk_draw_rect(ltk_window *window, ltk_draw *draw, int x, int y, int w, int h, int fill);
+static void ltk_draw_cmd_clear(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+static void ltk_draw_cmd_set_color(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+static void ltk_draw_cmd_line(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+static void ltk_draw_cmd_rect(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+static void ltk_draw_cmd_create(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+
+static void
+ltk_draw_draw(ltk_draw *draw) {
+	ltk_window *window = draw->widget.window;
+	ltk_rect rect = draw->widget.rect;
+	XCopyArea(window->dpy, draw->pix, window->xwindow, window->gc, 0, 0, rect.w, rect.h, rect.x, rect.y);
+}
+
+
+static ltk_draw *
+ltk_draw_create(ltk_window *window, const char *id, int w, int h, const char *color) {
+	XWindowAttributes attrs;
+	ltk_draw *draw = malloc(sizeof(ltk_draw));
+	if (!draw) ltk_fatal("ERROR: Unable to allocate memory for ltk_draw.\n");
+
+	ltk_fill_widget_defaults(&draw->widget, id, window,
+	    <k_draw_draw, <k_draw_destroy, 1, LTK_DRAW);
+	draw->widget.resize = <k_draw_resize;
+	draw->widget.rect.w = w;
+	draw->widget.rect.h = h;
+	XGetWindowAttributes(window->dpy, window->xwindow, &attrs);
+	draw->depth = attrs.depth;
+	draw->pix = XCreatePixmap(window->dpy, window->xwindow, w, h, draw->depth);
+	if (!ltk_create_xcolor(window, color, &draw->bg)) {
+		free(draw);
+		return NULL;
+	}
+	draw->fg = draw->bg;
+	XSetForeground(window->dpy, window->gc, draw->bg.pixel);
+	XFillRectangle(window->dpy, draw->pix, window->gc, 0, 0, w, h);
+
+	return draw;
+}
+
+static void
+ltk_draw_resize(ltk_draw *draw, int orig_w, int orig_h) {
+	Window win;
+	int x, y, w, h, bw, d;
+	int new_w, new_h;
+	ltk_window *window = draw->widget.window;
+	ltk_rect rect = draw->widget.rect;
+	XGetGeometry(window->dpy, draw->pix, &win, &x, &y, &w, &h, &bw, &d);
+
+	new_w = w < rect.w ? rect.w : w;
+	new_h = h < rect.h ? rect.h : h;
+	if (new_w < w && new_h < h)
+		return;
+	Pixmap tmp = XCreatePixmap(window->dpy, window->xwindow,
+	    new_w, new_h, draw->depth);
+	XSetForeground(window->dpy, window->gc, draw->bg.pixel);
+	XFillRectangle(window->dpy, tmp, window->gc, 0, 0, new_w, new_h);
+	XCopyArea(window->dpy, draw->pix, tmp, window->gc,
+	    0, 0, w, h, 0, 0);
+	XFreePixmap(window->dpy, draw->pix);
+	draw->pix = tmp;
+}
+
+static void
+ltk_draw_destroy(ltk_draw *draw, int shallow) {
+	if (!draw) {
+		(void)printf("WARNING: Tried to destroy NULL draw.\n");
+		return;
+	}
+	ltk_remove_widget(draw->widget.window, draw->widget.id);
+	free(draw->widget.id);
+	XFreePixmap(draw->widget.window->dpy, draw->pix);
+	free(draw);
+}
+
+static void
+ltk_draw_clear(ltk_window *window, ltk_draw *draw) {
+	Window win;
+	int x, y, w, h, bw, d;
+	XGetGeometry(window->dpy, draw->pix, &win, &x, &y, &w, &h, &bw, &d);
+	XSetForeground(window->dpy, window->gc, draw->bg.pixel);
+	XFillRectangle(window->dpy, window->xwindow, window->gc, 0, 0, w, h);
+	ltk_window_invalidate_rect(window, draw->widget.rect);
+}
+
+static void
+ltk_draw_set_color(ltk_window *window, ltk_draw *draw, const char *color) {
+	XColor tmp;
+	if (ltk_create_xcolor(window, color, &tmp)) {
+		draw->fg = tmp;
+	}
+}
+
+static void
+ltk_draw_line(ltk_window *window, ltk_draw *draw, int x1, int y1, int x2, int y2) {
+	XSetForeground(window->dpy, window->gc, draw->fg.pixel);
+	XSetLineAttributes(window->dpy, window->gc, 2, LineSolid, CapButt, JoinMiter);
+	XDrawLine(window->dpy, draw->pix, window->gc, x1, y1, x2, y2);
+	ltk_window_invalidate_rect(window, draw->widget.rect);
+}
+
+static void
+ltk_draw_rect(ltk_window *window, ltk_draw *draw, int x, int y, int w, int h, int fill) {
+	XSetForeground(window->dpy, window->gc, draw->fg.pixel);
+	if (fill) {
+		XFillRectangle(window->dpy, window->xwindow, window->gc, x, y, w, h);
+	} else {
+		XSetLineAttributes(window->dpy, window->gc, 2, LineSolid, CapButt, JoinMiter);
+		XDrawRectangle(window->dpy, draw->pix, window->gc, x, y, w, h);
+	}
+	ltk_window_invalidate_rect(window, draw->widget.rect);
+}
+
+static void
+ltk_draw_cmd_clear(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	ltk_draw *draw;
+	if (num_tokens != 3) {
+		(void)fprintf(stderr, "draw clear: Invalid number of arguments.\n");
+		return;
+	}
+	draw = ltk_get_widget(window, tokens[1], LTK_DRAW, "draw clear");
+	if (!draw) return;
+	ltk_draw_clear(window, draw);
+}
+
+static void
+ltk_draw_cmd_set_color(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	ltk_draw *draw;
+	if (num_tokens != 4) {
+		(void)fprintf(stderr, "draw set-color: Invalid number of arguments.\n");
+		return;
+	}
+	draw = ltk_get_widget(window, tokens[1], LTK_DRAW, "draw set-color");
+	if (!draw) return;
+	ltk_draw_set_color(window, draw, tokens[3]);
+}
+
+static void
+ltk_draw_cmd_line(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	ltk_draw *draw;
+	int x1, y1, x2, y2;
+	const char *errstr;
+	if (num_tokens != 7) {
+		(void)fprintf(stderr, "draw line: Invalid number of arguments.\n");
+		return;
+	}
+	draw = ltk_get_widget(window, tokens[1], LTK_DRAW, "draw line");
+	if (!draw) return;
+	x1 = strtonum(tokens[3], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw line: Invalid x1: %s\n", errstr);
+		return;
+	}
+	y1 = strtonum(tokens[4], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw line: Invalid y1: %s\n", errstr);
+		return;
+	}
+	x2 = strtonum(tokens[5], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw line: Invalid x2: %s\n", errstr);
+		return;
+	}
+	y2 = strtonum(tokens[6], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw line: Invalid y2: %s\n", errstr);
+		return;
+	}
+	ltk_draw_line(window, draw, x1, y1, x2, y2);
+}
+
+static void
+ltk_draw_cmd_rect(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	ltk_draw *draw;
+	const char *errstr;
+	int x, y, w, h, fill;
+	if (num_tokens != 8) {
+		(void)fprintf(stderr, "draw clear: Invalid number of arguments.\n");
+		return;
+	}
+	draw = ltk_get_widget(window, tokens[1], LTK_DRAW, "draw clear");
+	if (!draw) return;
+	x = strtonum(tokens[3], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw rect: Invalid x: %s\n", errstr);
+		return;
+	}
+	y = strtonum(tokens[4], 0, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw rect: Invalid y: %s\n", errstr);
+		return;
+	}
+	w = strtonum(tokens[5], 1, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw rect: Invalid width: %s\n", errstr);
+		return;
+	}
+	h = strtonum(tokens[6], 1, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw rect: Invalid height: %s\n", errstr);
+		return;
+	}
+	fill = strtonum(tokens[7], 0, 1, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw rect: Invalid fill bool: %s\n", errstr);
+		return;
+	}
+	ltk_draw_rect(window, draw, x, y, w, h, fill);
+}
+
+/* draw <draw id> create <width> <height> <color> */
+static void
+ltk_draw_cmd_create(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	ltk_draw *draw;
+	int w, h;
+	const char *errstr;
+	if (num_tokens != 6) {
+		(void)fprintf(stderr, "draw create: Invalid number of arguments.\n");
+		return;
+	}
+	if (!ltk_check_widget_id_free(window, tokens[1], "draw create"))
+		return;
+	w = strtonum(tokens[3], 1, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw create: Invalid width: %s\n", errstr);
+		return;
+	}
+	h = strtonum(tokens[4], 1, 100000, &errstr);
+	if (errstr) {
+		(void)fprintf(stderr, "draw create: Invalid height: %s\n", errstr);
+		return;
+	}
+	draw = ltk_draw_create(window, tokens[1], w, h, tokens[5]);
+	if (draw)
+		ltk_set_widget(window, draw, tokens[1]);
+}
+
+/* draw <draw id> <command> ... */
+void
+ltk_draw_cmd(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens) {
+	if (num_tokens < 3) {
+		(void)fprintf(stderr, "draw: Invalid number of arguments.\n");
+		return;
+	}
+	if (strcmp(tokens[2], "create") == 0) {
+		ltk_draw_cmd_create(window, tokens, num_tokens);
+	} else if (strcmp(tokens[2], "clear") == 0) {
+		ltk_draw_cmd_clear(window, tokens, num_tokens);
+	} else if (strcmp(tokens[2], "set-color") == 0) {
+		ltk_draw_cmd_set_color(window, tokens, num_tokens);
+	} else if (strcmp(tokens[2], "line") == 0) {
+		ltk_draw_cmd_line(window, tokens, num_tokens);
+	} else if (strcmp(tokens[2], "rect") == 0) {
+		ltk_draw_cmd_rect(window, tokens, num_tokens);
+	} else {
+		(void)fprintf(stderr, "draw: Invalid command.\n");
+	}
+}
diff --git a/draw.h b/draw.h
@@ -0,0 +1,42 @@
+/*
+ * This file is part of the Lumidify ToolKit (LTK)
+ * Copyright (c) 2020 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_DRAW_H_
+#define _LTK_DRAW_H_
+
+/* Requires the following includes: <X11/Xlib.h>, "ltk.h" */
+
+typedef struct {
+	ltk_widget widget;
+	Pixmap pix;
+	int depth;
+	XColor fg;
+	XColor bg;
+} ltk_draw;
+
+void ltk_draw_cmd(
+    ltk_window *window,
+    char **tokens,
+    size_t num_tokens);
+
+#endif /* _LTK_DRAW_H_ */
diff --git a/ltk.c b/ltk.c
@@ -41,6 +41,7 @@
 #include "grid.h"
 #include "text_line.h"
 #include "button.h"
+#include "draw.h"
 
 static void ltk_load_theme(ltk_window *window, const char *path);
 static void ltk_destroy_theme(ltk_theme *theme);
@@ -109,13 +110,15 @@ ltk_fatal(const char *msg) {
 	exit(1);
 };
 
-XColor
-ltk_create_xcolor(ltk_window *window, const char *hex) {
-	XColor color;
-	XParseColor(window->dpy, window->cm, hex, &color);
-	XAllocColor(window->dpy, window->cm, &color);
+int
+ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col) {
+	if (!XParseColor(window->dpy, window->cm, hex, col)) {
+		(void)fprintf(stderr, "Invalid color: %s\n", hex);
+		return 0;
+	}
+	XAllocColor(window->dpy, window->cm, col);
 
-	return color;
+	return 1;
 }
 
 void
@@ -231,6 +234,8 @@ proc_cmds(ltk_window *window) {
 		ltk_button_cmd(window, tokens, tokens_len);
 	} else if (strcmp(tokens[0], "set-root-widget") == 0) {
 		ltk_set_root_widget_cmd(window, tokens, tokens_len);
+	} else if (strcmp(tokens[0], "draw") == 0) {
+		ltk_draw_cmd(window, tokens, tokens_len);
 	} else {
 		(void)fprintf(stderr, "Invalid command.\n");
 	}
@@ -406,9 +411,9 @@ ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) 
 	if (strcmp(prop, "border_width") == 0) {
 		window->theme->window->border_width = atoi(value);
 	} else if (strcmp(prop, "bg") == 0) {
-		window->theme->window->bg = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &window->theme->window->bg);
 	} else if (strcmp(prop, "fg") == 0) {
-		window->theme->window->fg = ltk_create_xcolor(window, value);
+		ltk_create_xcolor(window, value, &window->theme->window->fg);
 	} else if (strcmp(prop, "font") == 0) {
 		window->theme->window->font = strdup(value);
 	} else if (strcmp(prop, "font_size") == 0) {
diff --git a/ltk.h b/ltk.h
@@ -24,7 +24,7 @@
 #ifndef _LTK_H_
 #define _LTK_H_
 
-/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h>, "drw.h" */
+/* Requires the following includes: <X11/Xlib.h>, <X11/Xutil.h> */
 
 typedef struct {
 	int x;
@@ -50,6 +50,7 @@ typedef enum {
 typedef enum {
 	LTK_GRID,
 	LTK_BUTTON,
+	LTK_DRAW,
 	LTK_WIDGET
 } ltk_widget_type;
 
@@ -133,7 +134,7 @@ void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
 
 void ltk_fatal(const char *msg);
 
-XColor ltk_create_xcolor(ltk_window *window, const char *hex);
+int ltk_create_xcolor(ltk_window *window, const char *hex, XColor *col);
 
 void ltk_queue_event(ltk_window *window, const char *id, const char *name);
 
diff --git a/test_draw.gui b/test_draw.gui
@@ -0,0 +1,10 @@
+grid grd1 create 2 2
+grid grd1 set-row-weight 0 1
+grid grd1 set-row-weight 1 1
+grid grd1 set-column-weight 0 1
+grid grd1 set-column-weight 1 1
+set-root-widget grd1
+draw drw1 create 100 100 #fff
+grid grd1 add drw1 0 1 1 1 15
+draw drw1 set-color #000
+draw drw1 line 0 0 100 100