commit 43bb385257c126c200662ed207f27a7a285f113e
parent d3c2a1bd950d2f5c11ca88da7f39bdef0a6813a0
Author: lumidify <nobody@lumidify.org>
Date: Fri, 15 Mar 2024 22:00:25 +0100
Remove socket functionality for now
Diffstat:
89 files changed, 4259 insertions(+), 7001 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -1,2 +1,3 @@
*.o
*.core
+ltkd
diff --git a/.ltk/ltk.cfg b/.ltk/ltk.cfg
@@ -1,80 +0,0 @@
-[general]
-explicit-focus = true
-all-activatable = true
-line-editor = "st -e vi %f"
-# In future:
-# text-editor = ...
-
-[key-binding:widget]
-# In future:
-# bind edit-text-external ...
-# bind edit-line-external ...
-bind-keypress move-next sym tab
-bind-keypress move-prev sym tab mods shift
-bind-keypress move-next text n
-bind-keypress move-prev text p
-bind-keypress move-left sym left
-bind-keypress move-right sym right
-bind-keypress move-up sym up
-bind-keypress move-down sym down
-bind-keypress move-left text h
-bind-keypress move-right text l
-bind-keypress move-up text k
-bind-keypress move-down text j
-bind-keypress focus-active sym return
-# FIXME: Test that this works properly once widgets that need
-# focus are added - remove-popups should be called if escape
-# wasn't handled already by unfocus-active.
-bind-keypress unfocus-active sym escape
-bind-keypress remove-popups sym escape
-bind-keypress set-pressed sym return #flags run-always
-bind-keyrelease unset-pressed sym return #flags run-always
-# alternative: rawtext instead of text to ignore mapping
-
-[key-binding:entry]
-bind-keypress cursor-to-beginning sym home
-bind-keypress cursor-to-end sym end
-bind-keypress cursor-left sym left
-bind-keypress cursor-right sym right
-bind-keypress select-all text a mods ctrl
-bind-keypress delete-char-backwards sym backspace
-bind-keypress delete-char-forwards sym delete
-bind-keypress expand-selection-left sym left mods shift
-bind-keypress expand-selection-right sym right mods shift
-bind-keypress selection-to-clipboard text c mods ctrl
-bind-keypress paste-clipboard text v mods ctrl
-bind-keypress switch-selection-side text o mods alt
-bind-keypress edit-external text E mods ctrl
-
-# default mapping (just to silence warnings)
-[key-mapping]
-language = "English (US)"
-
-[key-mapping]
-language = "German"
-map "z" "y"
-map "y" "z"
-map "Z" "Y"
-map "Y" "Z"
-map "Ö" ":"
-map "_" "?"
-map "-" "/"
-map "ä" "'"
-
-[key-mapping]
-language = "Urdu (Pakistan)"
-map "ج" "j"
-map "ک" "k"
-map "ح" "h"
-map "ل" "l"
-map "ن" "n"
-map "پ" "p"
-
-[key-mapping]
-language = "Hindi (Bolnagri)"
-map "ज" "j"
-map "क" "k"
-map "ह" "h"
-map "ल" "l"
-map "न" "n"
-map "प" "p"
diff --git a/LICENSE b/LICENSE
@@ -1,10 +1,10 @@
See src/khash.h, src/ini.*, src/stb_truetype.*, src/strtonum.c,
-and src/ctrlsel.* for third-party licenses.
+src/ctrlsel.*, and src/macros.h for third-party licenses.
ISC License
The Lumidify ToolKit (LTK)
-Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
+Copyright (c) 2016-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
diff --git a/Makefile b/Makefile
@@ -1,7 +1,7 @@
.POSIX:
.SUFFIXES: .c .o
-NAME = ltk
+NAME = test
VERSION = -999-prealpha0
# NOTE/FIXME: stb backend is currently broken
@@ -35,69 +35,63 @@ EXTRA_OBJ = $(EXTRA_OBJ_$(USE_PANGO))
EXTRA_CFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_CFLAGS_$(DEV)) $(EXTRA_CFLAGS_$(USE_PANGO))
EXTRA_LDFLAGS = $(SANITIZE_FLAGS_$(SANITIZE)) $(DEV_LDFLAGS_$(DEV)) $(EXTRA_LDFLAGS_$(USE_PANGO))
-LTK_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -DMEMDEBUG=$(MEMDEBUG) -std=c99 `pkg-config --cflags x11 fontconfig xext xcursor imlib2` -D_POSIX_C_SOURCE=200809L
+LTK_CFLAGS = $(EXTRA_CFLAGS) -DUSE_PANGO=$(USE_PANGO) -DDEV=$(DEV) -DMEMDEBUG=$(MEMDEBUG) -I ./src -std=c99 `pkg-config --cflags x11 fontconfig xext xcursor imlib2` -D_POSIX_C_SOURCE=200809L
LTK_LDFLAGS = $(EXTRA_LDFLAGS) -lm `pkg-config --libs x11 fontconfig xext xcursor imlib2`
OBJ = \
+ examples/test.o \
src/strtonum.o \
src/util.o \
src/memory.o \
+ src/window.o \
src/color_xlib.o \
src/rect.o \
src/widget.o \
- src/ltkd.o \
+ src/ltk.o \
src/ini.o \
- src/grid.o \
- src/box.o \
- src/scrollbar.o \
src/button.o \
- src/entry.o \
- src/label.o \
- src/menu.o \
src/theme.o \
src/graphics_xlib.o \
src/surface_cache.o \
src/event_xlib.o \
- src/err.o \
+ src/grid.o \
src/config.o \
src/clipboard_xlib.o \
src/txtbuf.o \
src/ctrlsel.o \
- src/cmd.o \
- src/image.o \
+ src/label.o \
+ src/image_imlib.o \
src/image_widget.o \
+ src/entry.o \
+ src/menu.o \
+ src/box.o \
+ src/scrollbar.o \
$(EXTRA_OBJ)
# Note: This could be improved so a change in a header only causes the .c files
# which include that header to be recompiled, but the compile times are
# currently so short that I don't really care.
HDR = \
- src/box.h \
src/button.h \
- src/entry.h \
src/color.h \
- src/grid.h \
src/ini.h \
- src/khash.h \
src/label.h \
src/rect.h \
src/widget.h \
src/ltk.h \
+ src/grid.h \
src/memory.h \
- src/scrollbar.h \
src/stb_truetype.h \
src/text.h \
src/util.h \
- src/menu.h \
src/theme.h \
src/graphics.h \
src/surface_cache.h \
src/macros.h \
src/event.h \
src/eventdefs.h \
- src/xlib_shared.h \
- src/err.h \
- src/proto_types.h \
+ src/graphics_xlib.h \
+ src/label.h \
src/config.h \
src/array.h \
src/keys.h \
@@ -105,21 +99,18 @@ HDR = \
src/clipboard.h \
src/txtbuf.h \
src/ctrlsel.h \
- src/cmd.h \
src/image.h \
- src/image_widget.h
+ src/image_widget.h \
+ src/entry.h \
+ src/menu.h \
+ src/box.h \
+ src/scrollbar.h
-all: src/ltkd src/ltkc src/ltkc_img
+all: examples/test
-src/ltkd: $(OBJ)
+examples/test: $(OBJ)
$(CC) -o $@ $(OBJ) $(LTK_LDFLAGS)
-src/ltkc: src/ltkc.o src/util.o src/memory.o src/txtbuf.o
- $(CC) -o $@ src/ltkc.o src/util.o src/memory.o src/txtbuf.o $(LTK_LDFLAGS)
-
-src/ltkc_img: src/ltkc_img.o
- $(CC) -o $@ src/ltkc_img.o $(LTK_LDFLAGS)
-
$(OBJ) : $(HDR)
.c.o:
@@ -128,4 +119,4 @@ $(OBJ) : $(HDR)
.PHONY: clean
clean:
- rm -f src/*.o src/ltkd src/ltkc src/ltkc_img
+ rm -f src/*.o examples/test examples/*.o
diff --git a/README.md b/README.md
@@ -3,7 +3,7 @@ Not much to see here.
WARNING: DON'T TRY TO USE THIS! IT IS ONLY A PLACE FOR ME TO TRY OUT MY
WILDEST FANTASIES, NOT ACTUAL WORKING CODE.
-To build with or without pango: Follow instructions in config.mk.
+To build with or without pango: Follow instructions in Makefile.
Note: The basic (non-pango) text doesn't work properly on all systems.
Note: The basic (non-pango) text is currently completely broken.
@@ -11,18 +11,10 @@ Note: The basic (non-pango) text is currently completely broken.
To test:
make
-./test.sh
-
-If you click the top button, it should exit. That's all it does now.
-Also read the comment in './test.sh'.
-
-./testbox.sh shows my gopherhole, but most buttons don't actually do anything.
-./test2.sh shows an example with menus.
+cd examples && LTKDIR=../config.example/ ./test
Note: I know the default theme is butt-ugly at the moment. It is mainly
to test things, not to look pretty.
-Note: Read 'socket_format.txt' for some documentation and open problems.
-
Note: The image support currently requires at least Imlib2 version 1.10.0.
I might add compatibility support for older versions at some point.
diff --git a/TODO b/TODO
@@ -1,3 +1,5 @@
+NOTE: this it the old todo, some of it only applies to ltkd
+
Error levels: allow to only print errors and ignore regular events
Possibly implement xrandr support for proper dpi handling
diff --git a/.ltk/.gitignore b/config.example/.gitignore
diff --git a/config.example/ltk.cfg b/config.example/ltk.cfg
@@ -0,0 +1,80 @@
+[general]
+explicit-focus = true
+all-activatable = true
+line-editor = "st -e vi %f"
+# In future:
+# text-editor = ...
+
+[key-binding:window]
+# In future:
+# bind edit-text-external ...
+# bind edit-line-external ...
+bind-keypress move-next sym tab
+bind-keypress move-prev sym tab mods shift
+bind-keypress move-next text n
+bind-keypress move-prev text p
+bind-keypress move-left sym left
+bind-keypress move-right sym right
+bind-keypress move-up sym up
+bind-keypress move-down sym down
+bind-keypress move-left text h
+bind-keypress move-right text l
+bind-keypress move-up text k
+bind-keypress move-down text j
+bind-keypress focus-active sym return
+# FIXME: Test that this works properly once widgets that need
+# focus are added - remove-popups should be called if escape
+# wasn't handled already by unfocus-active.
+bind-keypress unfocus-active sym escape
+bind-keypress remove-popups sym escape
+bind-keypress set-pressed sym return #flags run-always
+bind-keyrelease unset-pressed sym return #flags run-always
+# alternative: rawtext instead of text to ignore mapping
+
+[key-binding:entry]
+bind-keypress cursor-to-beginning sym home
+bind-keypress cursor-to-end sym end
+bind-keypress cursor-left sym left
+bind-keypress cursor-right sym right
+bind-keypress select-all text a mods ctrl
+bind-keypress delete-char-backwards sym backspace
+bind-keypress delete-char-forwards sym delete
+bind-keypress expand-selection-left sym left mods shift
+bind-keypress expand-selection-right sym right mods shift
+bind-keypress selection-to-clipboard text c mods ctrl
+bind-keypress paste-clipboard text v mods ctrl
+bind-keypress switch-selection-side text o mods alt
+bind-keypress edit-external text E mods ctrl
+
+# default mapping (just to silence warnings)
+[key-mapping]
+language = "English (US)"
+
+[key-mapping]
+language = "German"
+map "z" "y"
+map "y" "z"
+map "Z" "Y"
+map "Y" "Z"
+map "Ö" ":"
+map "_" "?"
+map "-" "/"
+map "ä" "'"
+
+[key-mapping]
+language = "Urdu (Pakistan)"
+map "ج" "j"
+map "ک" "k"
+map "ح" "h"
+map "ل" "l"
+map "ن" "n"
+map "پ" "p"
+
+[key-mapping]
+language = "Hindi (Bolnagri)"
+map "ज" "j"
+map "क" "k"
+map "ह" "h"
+map "ल" "l"
+map "न" "n"
+map "प" "p"
diff --git a/.ltk/theme.ini b/config.example/theme.ini
diff --git a/examples/.gitignore b/examples/.gitignore
@@ -0,0 +1,2 @@
+*.o
+test
diff --git a/examples/test.c b/examples/test.c
@@ -0,0 +1,87 @@
+#include <stdio.h>
+
+#include "ltk.h"
+#include "label.h"
+#include "button.h"
+#include "image.h"
+#include "image_widget.h"
+#include "grid.h"
+#include "entry.h"
+#include "menu.h"
+#include "box.h"
+
+int
+quit(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
+ (void)self;
+ (void)args;
+ (void)data;
+ ltk_quit();
+ return 1;
+}
+
+int
+printstuff(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
+ (void)self;
+ (void)args;
+ printf("%d\n", LTK_CAST_ARG_INT(data));
+ return 1;
+}
+
+int
+main(int argc, char *argv[]) {
+ (void)argc;
+ (void)argv;
+ ltk_init();
+ ltk_window *window = ltk_window_create("Hi", 0, 0, 500, 500);
+ ltk_grid *grid = ltk_grid_create(window, 5, 2);
+ ltk_grid_set_column_weight(grid, 0, 1);
+ ltk_grid_set_column_weight(grid, 1, 1);
+ ltk_grid_set_row_weight(grid, 4, 1);
+ ltk_button *button = ltk_button_create(window, "I'm a button!");
+ ltk_button *button1 = ltk_button_create(window, "I'm also a button!");
+ ltk_label *label = ltk_label_create(window, "I'm a label!");
+ ltk_image *img = ltk_image_create_from_path("test.jpg");
+ if (!img) {
+ fprintf(stderr, "Unable to load image.\n");
+ return 1;
+ }
+ ltk_image_widget *iw = ltk_image_widget_create(window, img);
+ ltk_entry *entry = ltk_entry_create(window, "");
+ ltk_menu *menu = ltk_menu_create(window);
+ ltk_menuentry *e1 = ltk_menuentry_create(window, "Hi");
+ ltk_menuentry *e2 = ltk_menuentry_create(window, "I'm a submenu");
+ ltk_menu_add_entry(menu, e1);
+ ltk_menu_add_entry(menu, e2);
+ ltk_menu *submenu = ltk_submenu_create(window);
+ ltk_menuentry *e3 = ltk_menuentry_create(window, "Menu Entry 1");
+ ltk_menuentry *e4 = ltk_menuentry_create(window, "Quit");
+ ltk_menu_add_entry(submenu, e3);
+ ltk_menu_add_entry(submenu, e4);
+ ltk_menuentry_attach_submenu(e2, submenu);
+
+ ltk_box *box = ltk_box_create(window, LTK_VERTICAL);
+ ltk_button *btn1 = ltk_button_create(window, "Bla1");
+ ltk_button *btn2 = ltk_button_create(window, "Bla2");
+ ltk_button *btn3 = ltk_button_create(window, "Bla3");
+ ltk_button *btn4 = ltk_button_create(window, "Bla4");
+ ltk_button *btn5 = ltk_button_create(window, "Bla5");
+ ltk_box_add(box, LTK_CAST_WIDGET(btn1), LTK_STICKY_LEFT);
+ ltk_box_add(box, LTK_CAST_WIDGET(btn2), LTK_STICKY_LEFT);
+ ltk_box_add(box, LTK_CAST_WIDGET(btn3), LTK_STICKY_LEFT);
+ ltk_box_add(box, LTK_CAST_WIDGET(btn4), LTK_STICKY_LEFT);
+ ltk_box_add(box, LTK_CAST_WIDGET(btn5), 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);
+ ltk_grid_add(grid, LTK_CAST_WIDGET(label), 2, 0, 1, 1, LTK_STICKY_RIGHT);
+ ltk_grid_add(grid, LTK_CAST_WIDGET(iw), 2, 1, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_PRESERVE_ASPECT_RATIO);
+ ltk_grid_add(grid, LTK_CAST_WIDGET(entry), 3, 0, 1, 1, LTK_STICKY_LEFT|LTK_STICKY_RIGHT);
+ ltk_grid_add(grid, LTK_CAST_WIDGET(box), 4, 0, 1, 2, LTK_STICKY_LEFT|LTK_STICKY_RIGHT|LTK_STICKY_TOP|LTK_STICKY_BOTTOM);
+ ltk_window_set_root_widget(window, LTK_CAST_WIDGET(grid));
+ ltk_widget_register_signal_handler(LTK_CAST_WIDGET(button), LTK_BUTTON_SIGNAL_PRESSED, &quit, LTK_ARG_VOID);
+ ltk_widget_register_signal_handler(LTK_CAST_WIDGET(e4), LTK_BUTTON_SIGNAL_PRESSED, &quit, LTK_ARG_VOID);
+ ltk_widget_register_signal_handler(LTK_CAST_WIDGET(button1), LTK_BUTTON_SIGNAL_PRESSED, &printstuff, LTK_MAKE_ARG_INT(5));
+ ltk_widget_register_signal_handler(LTK_CAST_WIDGET(window), LTK_WINDOW_SIGNAL_CLOSE, &quit, LTK_ARG_VOID);
+ ltk_mainloop();
+}
diff --git a/examples/test.jpg b/examples/test.jpg
Binary files differ.
diff --git a/socket_format.txt b/socket_format.txt
@@ -1,106 +0,0 @@
-General:
-
-All requests, responses, errors, and events start with a sequence number.
-This number starts at 0 and is incremented by the client with each request.
-When the server sends a response, error, or event, it starts with the last
-sequence number that the client sent. The client 'ltkc' adds sequence
-numbers automatically (perhaps it should hide them in its output as well,
-but I'm too lazy to implement that right now). A more advanced client could
-use the numbers to properly check that requests really were received.
-It isn't clear yet what should happen in the pathological case that the
-uint32_t used to store the sequence number overflows before any of the
-requests have been handled (i.e. it isn't clear anymore which request a
-reply is for). I guess this could technically happen when running over a
-broken connection or something, but I don't have a solution right now.
-It doesn't seem like a very realistic scenario, though, considering that
-all the requests/responses would need to be buffered somewhere, which
-would be somewhat unrealistic considering the size of uint32_t.
-
-Requests:
-
-<widget type> <widget id> <command> <args>
-> grid grd1 create 2 2
-
-If the command takes a string, the string may contain newlines:
-> button btn1 create "I'm a
-> button!"
-
-The command line is read until the first newline that is not
-within a string.
-
-Double quotes must be escaped in strings, like so:
-> button btn1 create "Bla\"bla"
-
-Essentially, the individual messages are separated by line
-breaks (\n), but line breaks within strings don't break the
-message.
-
-Responses:
-
-Not properly implemented yet.
-Currently, all requests that don't generate errors just get the response
-"<sequence> res ok". Of course, this will be changed once other response
-types are available (e.g. get-text and others).
-
-It might be good to allow ignoring responses to avoid lots of useless traffic.
-On the client side, the usage could be similar to XCB's checked/unchecked.
-
-Errors:
-
-err <error number> <number of bad argument or -1> <string description of error>
-
-Events:
-
-event[l] <widget id> <widget type or "widget" for generic events> <event name> [further data]
-
-By default, no events are reported. An event mask has to be set first:
-
-mask-add <widget id> <type> <event name> [lock]
-mask-remove <widget id> <type> <event name> [lock]
-mask-set <widget id> <type> <event name> [lock]
-
-<type> is either "widget" for generic events (mousepress, mouserelease,
-mousemotion, configure, statechange) or the widget type for specific events.
-The only specific event currently supported is "press" for both "button"
-and "menuentry". Note that currently, only a single mask type can be
-added/removed at once instead of combining multiple in one request
-(i.e. it isn't possible to do something like "mousepress|mouserelease" yet).
-
-If "lock" is set, the "lock mask" is manipulated instead of the normal one.
-If an event occurs that is included in the lock mask, the event will start
-with "eventl" instead of "event", and the ltk server blocks until it gets the
-request "event-unlock [true/false]" from the client that received the event.
-Note that if multiple clients have an event in their lock mask, all of them will
-receive the event, but only one of them is chosen to listen for the event-unlock
-(basically, this is unspecified behavior that should be avoided). If event-unlock
-includes "true", the event is not processed further by the ltk server. If "false"
-is given instead, the event is processed as usual by the ltk server.
-
-This was added to allow functionality like in regular GUI toolkits where it is
-possible to override events completely. The problem is that it currently isn't
-really clear where exactly the command should be emitted and whether it really
-makes sense to block all further processing (some processing has to be done
-even now for it to make any sense at all). That could possibly lead to very
-weird bugs. It also currently isn't possible to do much after locking because
-no useful low-level functions for widgets exist (yet?). All in all, I'm not
-entirely sure how to make this work nicely so it is actually useful.
-Since all of this is pushed over a socket and will probably be able to run
-over a network connection eventually, it will also cause problems with latency.
-
-Miscellaneous:
-
-It probably isn't too great for security when anyone can do anything with the
-window. Maybe it would be better to allow different clients to have different
-permissions? For instance, maybe only the main client could change things, but
-other clients could have readonly permissions for things like screenreaders.
-That would probably get very over-complicated, though.
-
-I'm also seriously considering switching to a binary socket format. It's nice
-to have a text format, but it's an absolute pain to process, because everything
-has to be converted from/to text. It also isn't nearly as efficient, especially
-if more complicated things are done, such as listening for all mousemotion events.
-Of course, it could be made much more efficient with various lookup tables
-(it isn't implemented very efficiently currently), but still not nearly as good
-as a binary protocol. The idea would be to have a binary protocol, but to still
-have something like ltkc that converts the protocol to a text format so simple
-shell clients can still exist, but more complicated programs aren't hindered by it.
diff --git a/src/.gitignore b/src/.gitignore
@@ -1,5 +1 @@
-ltkd
-ltkc
-ltkc_img
*.o
-*.core
diff --git a/src/array.h b/src/array.h
@@ -1,6 +1,5 @@
/*
- * This file is part of the Lumidify ToolKit (LTK)
- * Copyright (c) 2020, 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2020-2024 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
@@ -21,11 +20,12 @@
* SOFTWARE.
*/
-#ifndef _LTK_ARRAY_H_
-#define _LTK_ARRAY_H_
+#ifndef LTK_ARRAY_H
+#define LTK_ARRAY_H
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include "util.h"
#include "memory.h"
@@ -37,15 +37,22 @@
#define LTK_UNUSED_FUNC
#endif
-#define LTK_ARRAY_INIT_DECL_BASE(name, type, storage) \
+/* FIXME: add accesser macros; also add init function to not allocate array itself but initialize given pointer */
+/* FIXME: separate struct definition and forward-declaration for header files */
+
+#define LTK_ARRAY_INIT_STRUCT_DECL_BASE(name, type, storage) \
typedef struct { \
type *buf; \
size_t buf_size; \
size_t len; \
-} ltk_array_##name; \
- \
-LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_len); \
+} ltk_array_##name;
+
+#define LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type, storage) \
+LTK_UNUSED_FUNC storage ltk_array_##name *ltk_array_create_##name(size_t initial_capacity); \
LTK_UNUSED_FUNC storage type ltk_array_pop_##name(ltk_array_##name *ar); \
+LTK_UNUSED_FUNC storage size_t ltk_array_remove_if_##name( \
+ ltk_array_##name *ar, int (*callback)(type *, void *), void *data \
+); \
LTK_UNUSED_FUNC storage void ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len); \
LTK_UNUSED_FUNC storage void ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t len); \
LTK_UNUSED_FUNC storage void ltk_array_resize_##name(ltk_array_##name *ar, size_t size); \
@@ -58,12 +65,12 @@ LTK_UNUSED_FUNC storage void ltk_array_set_safe_##name(ltk_array_##name *ar, siz
#define LTK_ARRAY_INIT_IMPL_BASE(name, type, storage) \
LTK_UNUSED_FUNC storage ltk_array_##name * \
-ltk_array_create_##name(size_t initial_len) { \
- if (initial_len == 0) \
- ltk_fatal("Array length is zero\n"); \
+ltk_array_create_##name(size_t initial_capacity) { \
+ if (initial_capacity == 0) \
+ ltk_fatal("Array capacity is zero\n"); \
ltk_array_##name *ar = ltk_malloc(sizeof(ltk_array_##name)); \
- ar->buf = ltk_reallocarray(NULL, initial_len, sizeof(type)); \
- ar->buf_size = initial_len; \
+ ar->buf = ltk_reallocarray(NULL, initial_capacity, sizeof(type)); \
+ ar->buf_size = initial_capacity; \
ar->len = 0; \
return ar; \
} \
@@ -76,6 +83,20 @@ ltk_array_pop_##name(ltk_array_##name *ar) { \
return ar->buf[ar->len]; \
} \
\
+LTK_UNUSED_FUNC storage size_t \
+ltk_array_remove_if_##name(ltk_array_##name *ar, int (*callback)(type *, void *), void *data) { \
+ size_t removed = 0; \
+ for (size_t i = 0; i < ar->len; i++) { \
+ if (callback(&ar->buf[i], data)) \
+ removed++; \
+ else \
+ ar->buf[i - removed] = ar->buf[i]; \
+ } \
+ /* FIXME: maybe release memory */ \
+ ar->len -= removed; \
+ return removed; \
+} \
+ \
/* FIXME: having this function in the public interface is ugly */ \
LTK_UNUSED_FUNC storage void \
ltk_array_prepare_gap_##name(ltk_array_##name *ar, size_t index, size_t len) { \
@@ -98,6 +119,16 @@ ltk_array_insert_##name(ltk_array_##name *ar, size_t index, type *elem, size_t l
} \
\
LTK_UNUSED_FUNC storage void \
+ltk_array_delete_##name(ltk_array_##name *ar, size_t index, size_t len) { \
+ /* FIXME: integer overflow protection everywhere */ \
+ if (index + len > ar->len) \
+ ltk_fatal("Array index out of bounds\n"); \
+ /* FIXME: maybe release memory at some point? */ \
+ memmove(ar->buf + index, ar->buf + index + len, (ar->len - index - len) * sizeof(type)); \
+ ar->len -= len; \
+} \
+ \
+LTK_UNUSED_FUNC storage void \
ltk_array_append_##name(ltk_array_##name *ar, type elem) { \
if (ar->len == ar->buf_size) \
ltk_array_resize_##name(ar, ar->len + 1); \
@@ -153,22 +184,36 @@ ltk_array_set_safe_##name(ltk_array_##name *ar, size_t index, type e) { \
}
#define ltk_array(name) ltk_array_##name
-#define ltk_array_create(name, initial_len) ltk_array_create_##name(initial_len)
+#define ltk_array_create(name, initial_capacity) ltk_array_create_##name(initial_capacity)
#define ltk_array_pop(name, ar) ltk_array_pop_##name(ar)
+#define ltk_array_remove_if(name, ar, callback, data) ltk_array_remove_if_##name(ar, callback, data)
#define ltk_array_insert(name, ar, index, elem, len) ltk_array_insert_##name(ar, index, elem, len)
+#define ltk_array_delete(name, ar, index, len) ltk_array_delete_##name(ar, index, len)
#define ltk_array_resize(name, ar, size) ltk_array_resize_##name(ar, size)
#define ltk_array_destroy(name, ar) ltk_array_destroy_##name(ar)
#define ltk_array_clear(name, ar) ltk_array_clear_##name(ar)
#define ltk_array_append(name, ar, elem) ltk_array_append_##name(ar, elem)
#define ltk_array_destroy_deep(name, ar, destroy_func) ltk_array_destroy_deep_##name(ar, destroy_func)
-#define ltk_array_length(ar) ((ar)->len)
+#define ltk_array_len(ar) ((ar)->len)
+#define ltk_array_get_buf(ar) ((ar)->buf)
#define ltk_array_get(ar, index) ((ar)->buf[index])
#define ltk_array_get_safe(name, ar, index) ltk_array_get_safe_##name(ar, index)
#define ltk_array_set_safe(name, ar, index, e) ltk_array_set_safe_##name(ar, index, e)
-#define LTK_ARRAY_INIT_DECL(name, type) LTK_ARRAY_INIT_DECL_BASE(name, type,)
+#define LTK_ARRAY_INIT_DECL(name, type) \
+LTK_ARRAY_INIT_STRUCT_DECL_BASE(name, type,) \
+LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type,)
+
+/* mainly for header files that shouldn't include all the function declarations */
+#define LTK_ARRAY_INIT_STRUCT_DECL(name, type)LTK_ARRAY_INIT_STRUCT_DECL_BASE(name, type,)
+#define LTK_ARRAY_INIT_FUNC_DECL(name, type)LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type,)
+#define LTK_ARRAY_INIT_FUNC_DECL_STATIC(name, type)LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type, static)
+
+#define LTK_ARRAY_INIT_DECL_STATIC(name, type) \
+LTK_ARRAY_INIT_STRUCT_DECL_BASE(name, type, static) \
+LTK_ARRAY_INIT_FUNC_DECL_BASE(name, type, static)
+
#define LTK_ARRAY_INIT_IMPL(name, type) LTK_ARRAY_INIT_IMPL_BASE(name, type,)
-#define LTK_ARRAY_INIT_DECL_STATIC(name, type) LTK_ARRAY_INIT_DECL_BASE(name, type, static)
#define LTK_ARRAY_INIT_IMPL_STATIC(name, type) LTK_ARRAY_INIT_IMPL_BASE(name, type, static)
-#endif /* _LTK_ARRAY_H_ */
+#endif /* LTK_ARRAY_H */
diff --git a/src/box.c b/src/box.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -16,32 +16,24 @@
/* FIXME: implement other sticky options now supported by grid */
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdarg.h>
-#include <stdint.h>
+#include <limits.h>
#include <string.h>
+#include "box.h"
#include "event.h"
+#include "graphics.h"
#include "memory.h"
-#include "color.h"
#include "rect.h"
-#include "widget.h"
-#include "ltk.h"
-#include "util.h"
#include "scrollbar.h"
-#include "box.h"
-#include "cmd.h"
+#include "widget.h"
static void ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
-static ltk_box *ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient);
static void ltk_box_destroy(ltk_widget *self, int shallow);
static void ltk_recalculate_box(ltk_widget *self);
static void ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget);
-static int ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err);
-static int ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err);
-/* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow, ltk_error *err); */
-static void ltk_box_scroll(ltk_widget *self);
+static int ltk_box_remove_child(ltk_widget *self, ltk_widget *widget);
+/* static int ltk_box_clear(ltk_window *window, ltk_box *box, int shallow); */
+static int ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data);
static int ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event);
static ltk_widget *ltk_box_get_child_at_pos(ltk_widget *self, int x, int y);
static void ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r);
@@ -64,7 +56,7 @@ static struct ltk_widget_vtable vtable = {
.destroy = <k_box_destroy,
.resize = <k_recalculate_box,
.child_size_change = <k_box_child_size_change,
- .remove_child = <k_box_remove,
+ .remove_child = <k_box_remove_child,
.key_press = NULL,
.key_release = NULL,
.mouse_press = NULL,
@@ -86,37 +78,12 @@ static struct ltk_widget_vtable vtable = {
.ensure_rect_shown = <k_box_ensure_rect_shown,
.type = LTK_WIDGET_BOX,
.flags = 0,
+ .invalid_signal = LTK_BOX_SIGNAL_INVALID,
};
-static int ltk_box_cmd_add(
- ltk_window *window,
- ltk_box *box,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-static int ltk_box_cmd_remove(
- ltk_window *window,
- ltk_box *box,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-/*
-static int ltk_box_cmd_clear
- ltk_window *window,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-*/
-static int ltk_box_cmd_create(
- ltk_window *window,
- ltk_box *box,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-
static void
ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
ltk_widget *ptr;
/* FIXME: clip out scrollbar */
ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
@@ -127,21 +94,26 @@ ltk_box_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, real_clip));
}
box->sc->widget.vtable->draw(
- (ltk_widget *)box->sc, s,
+ LTK_CAST_WIDGET(box->sc), s,
x + box->sc->widget.lrect.x,
y + box->sc->widget.lrect.y,
ltk_rect_relative(box->sc->widget.lrect, real_clip)
);
}
-static ltk_box *
-ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
+ltk_box *
+ltk_box_create(ltk_window *window, ltk_orientation orient) {
ltk_box *box = ltk_malloc(sizeof(ltk_box));
+ ltk_widget *self = LTK_CAST_WIDGET(box);
- ltk_fill_widget_defaults(&box->widget, id, window, &vtable, 0, 0);
+ ltk_fill_widget_defaults(self, window, &vtable, 0, 0);
- box->sc = ltk_scrollbar_create(window, orient, <k_box_scroll, box);
- box->sc->widget.parent = &box->widget;
+ box->sc = ltk_scrollbar_create(window, orient);
+ box->sc->widget.parent = self;
+ ltk_widget_register_signal_handler(
+ LTK_CAST_WIDGET(box->sc), LTK_SCROLLBAR_SIGNAL_SCROLL,
+ <k_box_scroll_cb, LTK_MAKE_ARG_WIDGET(self)
+ );
box->widgets = NULL;
box->num_alloc = 0;
box->num_widgets = 0;
@@ -150,14 +122,14 @@ ltk_box_create(ltk_window *window, const char *id, ltk_orientation orient) {
box->widget.ideal_h = box->sc->widget.ideal_h;
else
box->widget.ideal_w = box->sc->widget.ideal_w;
- ltk_recalculate_box(&box->widget);
+ ltk_recalculate_box(self);
return box;
}
static void
ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
int delta = 0;
if (box->orient == LTK_HORIZONTAL) {
if (r.x + r.w > self->lrect.w && r.w <= self->lrect.w)
@@ -171,25 +143,22 @@ ltk_box_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
delta = r.y;
}
if (delta)
- ltk_scrollbar_scroll(&box->sc->widget, delta, 0);
+ ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0);
}
static void
ltk_box_destroy(ltk_widget *self, int shallow) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
ltk_widget *ptr;
- ltk_error err;
for (size_t i = 0; i < box->num_widgets; i++) {
ptr = box->widgets[i];
ptr->parent = NULL;
if (!shallow)
- ltk_widget_destroy(ptr, shallow, &err);
+ ltk_widget_destroy(ptr, shallow);
}
ltk_free(box->widgets);
box->sc->widget.parent = NULL;
- /* FIXME: this is a bit weird because sc isn't a normal widget
- (it isn't in the widget hash) */
- ltk_widget_destroy(&box->sc->widget, 0, &err);
+ ltk_widget_destroy(LTK_CAST_WIDGET(box->sc), 0);
ltk_free(box);
}
@@ -200,7 +169,7 @@ ltk_box_destroy(ltk_widget *self, int shallow) {
/* FIXME: avoid complete recalculation when just scrolling (only position updated) */
static void
ltk_recalculate_box(ltk_widget *self) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
ltk_widget *ptr;
ltk_rect *sc_rect = &box->sc->widget.lrect;
int cur_pos = 0;
@@ -248,7 +217,7 @@ ltk_recalculate_box(ltk_widget *self) {
}
*sc_rect = ltk_rect_intersect(*sc_rect, (ltk_rect){0, 0, box->widget.lrect.w, box->widget.lrect.h});
box->sc->widget.crect = ltk_rect_intersect((ltk_rect){0, 0, self->crect.w, self->crect.h}, *sc_rect);
- ltk_widget_resize((ltk_widget *)box->sc);
+ ltk_widget_resize(LTK_CAST_WIDGET(box->sc));
}
/* FIXME: This entire resizing thing is a bit weird. For instance, if a label
@@ -260,7 +229,7 @@ ltk_recalculate_box(ltk_widget *self) {
static void
ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
short size_changed = 0;
/* This is always reset here - if it needs to be changed,
the resize function called by the last child_size_change
@@ -290,17 +259,15 @@ ltk_box_child_size_change(ltk_widget *self, ltk_widget *widget) {
if (size_changed && box->widget.parent && box->widget.parent->vtable->child_size_change)
box->widget.parent->vtable->child_size_change(box->widget.parent, (ltk_widget *)box);
else
- ltk_recalculate_box((ltk_widget *)box);
+ ltk_recalculate_box(LTK_CAST_WIDGET(box));
if (orig_w != widget->lrect.w || orig_h != widget->lrect.h)
ltk_widget_resize(widget);
}
-static int
-ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mask sticky, ltk_error *err) {
- if (widget->parent) {
- err->type = ERR_WIDGET_IN_CONTAINER;
+int
+ltk_box_add(ltk_box *box, ltk_widget *widget, ltk_sticky_mask sticky) {
+ if (widget->parent)
return 1;
- }
if (box->num_widgets >= box->num_alloc) {
size_t new_size = box->num_alloc > 0 ? box->num_alloc * 2 : 4;
ltk_widget **new = ltk_realloc(box->widgets, new_size * sizeof(ltk_widget *));
@@ -321,72 +288,78 @@ ltk_box_add(ltk_window *window, ltk_widget *widget, ltk_box *box, ltk_sticky_mas
if (widget->ideal_w + sc_w > box->widget.ideal_w)
box->widget.ideal_w = widget->ideal_w + sc_w;
}
- widget->parent = (ltk_widget *)box;
+ widget->parent = LTK_CAST_WIDGET(box);
widget->sticky = sticky;
- ltk_box_child_size_change((ltk_widget *)box, widget);
- ltk_window_invalidate_widget_rect(window, &box->widget);
+ ltk_box_child_size_change(LTK_CAST_WIDGET(box), widget);
+ ltk_window_invalidate_widget_rect(box->widget.window, LTK_CAST_WIDGET(box));
return 0;
}
-static int
-ltk_box_remove(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
- ltk_box *box = (ltk_box *)self;
+int
+ltk_box_remove_index(ltk_box *box, size_t index) {
+ if (index >= box->num_widgets)
+ return 1;
+ ltk_widget *self = LTK_CAST_WIDGET(box);
+ ltk_widget *widget = box->widgets[index];
int sc_w = box->sc->widget.lrect.w;
int sc_h = box->sc->widget.lrect.h;
- if (widget->parent != (ltk_widget *)box) {
- err->type = ERR_WIDGET_NOT_IN_CONTAINER;
- return 1;
+ if (index < box->num_widgets - 1)
+ memmove(box->widgets + index, box->widgets + index + 1,
+ (box->num_widgets - index - 1) * sizeof(ltk_widget *));
+ box->num_widgets--;
+ ltk_window_invalidate_widget_rect(self->window, self);
+ /* search for new ideal width/height */
+ /* FIXME: make this all a bit nicer and break the lines better */
+ /* FIXME: other part of ideal size not updated */
+ if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == self->ideal_h) {
+ self->ideal_h = 0;
+ for (size_t j = 0; j < box->num_widgets; j++) {
+ if (box->widgets[j]->ideal_h + sc_h > self->ideal_h)
+ self->ideal_h = box->widgets[j]->ideal_h + sc_h;
+ }
+ if (self->parent)
+ ltk_widget_resize(self->parent);
+ } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == self->ideal_w) {
+ self->ideal_w = 0;
+ for (size_t j = 0; j < box->num_widgets; j++) {
+ if (box->widgets[j]->ideal_w + sc_w > self->ideal_w)
+ self->ideal_w = box->widgets[j]->ideal_w + sc_w;
+ }
+ if (self->parent)
+ ltk_widget_resize(self->parent);
}
+ return 0;
+}
+
+int
+ltk_box_remove(ltk_box *box, ltk_widget *widget) {
+ if (widget->parent != LTK_CAST_WIDGET(box))
+ 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_widget_rect(widget->window, &box->widget);
- /* search for new ideal width/height */
- /* FIXME: make this all a bit nicer and break the lines better */
- /* FIXME: other part of ideal size not updated */
- if (box->orient == LTK_HORIZONTAL && widget->ideal_h + sc_h == box->widget.ideal_h) {
- box->widget.ideal_h = 0;
- for (size_t j = 0; j < box->num_widgets; j++) {
- if (box->widgets[j]->ideal_h + sc_h > box->widget.ideal_h)
- box->widget.ideal_h = box->widgets[j]->ideal_h + sc_h;
- }
- if (box->widget.parent)
- ltk_widget_resize(box->widget.parent);
- } else if (box->orient == LTK_VERTICAL && widget->ideal_w + sc_w == box->widget.ideal_w) {
- box->widget.ideal_w = 0;
- for (size_t j = 0; j < box->num_widgets; j++) {
- if (box->widgets[j]->ideal_w + sc_w > box->widget.ideal_w)
- box->widget.ideal_w = box->widgets[j]->ideal_w + sc_w;
- }
- if (box->widget.parent)
- ltk_widget_resize(box->widget.parent);
- }
- return 0;
+ return ltk_box_remove_index(box, i);
}
}
- err->type = ERR_WIDGET_NOT_IN_CONTAINER;
return 1;
}
+static int
+ltk_box_remove_child(ltk_widget *self, ltk_widget *widget) {
+ return ltk_box_remove(LTK_CAST_BOX(self), widget);
+}
+
/* FIXME: maybe come up with a more efficient method */
static ltk_widget *
ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
ltk_widget *minw = NULL;
int min_dist = INT_MAX;
- int cx = rect.x + rect.w / 2;
- int cy = rect.y + rect.h / 2;
- ltk_rect r;
- int dist;
for (size_t i = 0; i < box->num_widgets; i++) {
- r = box->widgets[i]->lrect;
- dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy);
+ ltk_rect r = box->widgets[i]->lrect;
+ int dist = ltk_rect_fakedist(rect, r);
if (dist < min_dist) {
min_dist = dist;
minw = box->widgets[i];
@@ -397,7 +370,7 @@ ltk_box_nearest_child(ltk_widget *self, ltk_rect rect) {
static ltk_widget *
ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (box->orient == LTK_VERTICAL)
return NULL;
return ltk_box_prev_child(self, widget);
@@ -405,7 +378,7 @@ ltk_box_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (box->orient == LTK_VERTICAL)
return NULL;
return ltk_box_next_child(self, widget);
@@ -413,7 +386,7 @@ ltk_box_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (box->orient == LTK_HORIZONTAL)
return NULL;
return ltk_box_prev_child(self, widget);
@@ -421,7 +394,7 @@ ltk_box_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (box->orient == LTK_HORIZONTAL)
return NULL;
return ltk_box_next_child(self, widget);
@@ -429,7 +402,7 @@ ltk_box_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_box_prev_child(ltk_widget *self, ltk_widget *child) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
for (size_t i = box->num_widgets; i-- > 0;) {
if (box->widgets[i] == child)
return i > 0 ? box->widgets[i-1] : NULL;
@@ -439,7 +412,7 @@ ltk_box_prev_child(ltk_widget *self, ltk_widget *child) {
static ltk_widget *
ltk_box_next_child(ltk_widget *self, ltk_widget *child) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
for (size_t i = 0; i < box->num_widgets; i++) {
if (box->widgets[i] == child)
return i < box->num_widgets - 1 ? box->widgets[i+1] : NULL;
@@ -449,26 +422,29 @@ ltk_box_next_child(ltk_widget *self, ltk_widget *child) {
static ltk_widget *
ltk_box_first_child(ltk_widget *self) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
return box->num_widgets > 0 ? box->widgets[0] : NULL;
}
static ltk_widget *
ltk_box_last_child(ltk_widget *self) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
return box->num_widgets > 0 ? box->widgets[box->num_widgets-1] : NULL;
}
-static void
-ltk_box_scroll(ltk_widget *self) {
- ltk_box *box = (ltk_box *)self;
- ltk_recalculate_box(self);
- ltk_window_invalidate_widget_rect(box->widget.window, &box->widget);
+static int
+ltk_box_scroll_cb(ltk_widget *self, ltk_callback_arglist args, ltk_callback_arg data) {
+ (void)self;
+ (void)args;
+ ltk_widget *boxw = LTK_CAST_ARG_WIDGET(data);
+ ltk_recalculate_box(boxw);
+ ltk_window_invalidate_widget_rect(boxw->window, boxw);
+ return 1;
}
static ltk_widget *
ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (ltk_collide_rect(box->sc->widget.crect, x, y))
return (ltk_widget *)box->sc;
for (size_t i = 0; i < box->num_widgets; i++) {
@@ -480,96 +456,15 @@ ltk_box_get_child_at_pos(ltk_widget *self, int x, int y) {
static int
ltk_box_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
- ltk_box *box = (ltk_box *)self;
+ ltk_box *box = LTK_CAST_BOX(self);
if (event->dy) {
/* FIXME: horizontal scrolling, etc. */
/* FIXME: configure scrollstep */
int delta = event->dy * -15;
- ltk_scrollbar_scroll((ltk_widget *)box->sc, delta, 0);
+ ltk_scrollbar_scroll(LTK_CAST_WIDGET(box->sc), delta, 0);
ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
ltk_window_fake_motion_event(self->window, glob.x, glob.y);
return 1;
}
return 0;
}
-
-/* box <box id> add <widget id> [sticky] */
-static int
-ltk_box_cmd_add(
- ltk_window *window,
- ltk_box *box,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
- {.type = CMDARG_STICKY, .optional = 1},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_box_add(window, cmd[0].val.widget, box, cmd[1].initialized ? cmd[1].val.sticky : 0, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* box <box id> remove <widget id> */
-static int
-ltk_box_cmd_remove(
- ltk_window *window,
- ltk_box *box,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_box_remove(cmd[0].val.widget, (ltk_widget *)box, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* box <box id> create <orientation> */
-static int
-ltk_box_cmd_create(
- ltk_window *window,
- ltk_box *box_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)box_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_ORIENTATION, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_box *box = ltk_box_create(window, cmd[1].val.str, cmd[3].val.orient);
- ltk_set_widget((ltk_widget *)box, cmd[1].val.str);
-
- return 0;
-}
-
-static struct box_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_box *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} box_cmds[] = {
- {"add", <k_box_cmd_add, 0},
- {"create", <k_box_cmd_create, 1},
- {"remove", <k_box_cmd_remove, 0},
-};
-
-GEN_CMD_HELPERS(ltk_box_cmd, LTK_WIDGET_BOX, ltk_box, box_cmds, struct box_cmd)
diff --git a/src/box.h b/src/box.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,14 +14,15 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _LTK_BOX_H_
-#define _LTK_BOX_H_
+#ifndef LTK_BOX_H
+#define LTK_BOX_H
-/* FIXME: include everything here */
-/* Requires the following includes: "scrollbar.h", "rect.h", "widget.h", "ltk.h" */
+#include <stddef.h>
+#include "widget.h"
+#include "window.h"
+#include "scrollbar.h"
-#include "cmd.h"
-#include "err.h"
+#define LTK_BOX_SIGNAL_INVALID -1
typedef struct {
ltk_widget widget;
@@ -34,6 +35,9 @@ typedef struct {
ltk_orientation orient;
} ltk_box;
-GEN_CMD_HELPERS_PROTO(ltk_box_cmd)
+ltk_box *ltk_box_create(ltk_window *window, ltk_orientation orient);
+int ltk_box_add(ltk_box *box, ltk_widget *widget, ltk_sticky_mask sticky);
+int ltk_box_remove(ltk_box *box, ltk_widget *widget);
+int ltk_box_remove_index(ltk_box *box, size_t index);
-#endif /* _LTK_BOX_H_ */
+#endif /* LTK_BOX_H */
diff --git a/src/button.c b/src/button.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016-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
@@ -14,36 +14,27 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdio.h>
-#include <stdlib.h>
#include <stdint.h>
-#include <string.h>
-#include <stdarg.h>
+#include <stdio.h>
-#include "proto_types.h"
-#include "event.h"
-#include "memory.h"
+#include "button.h"
#include "color.h"
-#include "rect.h"
-#include "widget.h"
+#include "graphics.h"
#include "ltk.h"
-#include "util.h"
+#include "memory.h"
+#include "rect.h"
#include "text.h"
-#include "button.h"
-#include "graphics.h"
-#include "surface_cache.h"
#include "theme.h"
-#include "cmd.h"
+#include "util.h"
+#include "widget.h"
#define MAX_BUTTON_BORDER_WIDTH 100
#define MAX_BUTTON_PADDING 500
static void ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
static int ltk_button_release(ltk_widget *self);
-static ltk_button *ltk_button_create(ltk_window *window,
- const char *id, char *text);
+ltk_button *ltk_button_create(ltk_window *window, char *text);
static void ltk_button_destroy(ltk_widget *self, int shallow);
-static void ltk_button_redraw_surface(ltk_button *button, ltk_surface *s);
static struct ltk_widget_vtable vtable = {
.key_press = NULL,
@@ -64,27 +55,28 @@ static struct ltk_widget_vtable vtable = {
.remove_child = NULL,
.type = LTK_WIDGET_BUTTON,
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
+ .invalid_signal = LTK_BUTTON_SIGNAL_INVALID,
};
static struct {
int border_width;
- ltk_color text_color;
+ ltk_color *text_color;
int pad;
- ltk_color border;
- ltk_color fill;
+ ltk_color *border;
+ ltk_color *fill;
- ltk_color border_pressed;
- ltk_color fill_pressed;
+ ltk_color *border_pressed;
+ ltk_color *fill_pressed;
- ltk_color border_hover;
- ltk_color fill_hover;
+ ltk_color *border_hover;
+ ltk_color *fill_hover;
- ltk_color border_active;
- ltk_color fill_active;
+ ltk_color *border_active;
+ ltk_color *fill_active;
- ltk_color border_disabled;
- ltk_color fill_disabled;
+ ltk_color *border_disabled;
+ ltk_color *fill_disabled;
} theme;
static ltk_theme_parseinfo parseinfo[] = {
@@ -105,89 +97,84 @@ static ltk_theme_parseinfo parseinfo[] = {
static int parseinfo_sorted = 0;
int
-ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "button", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
+ltk_button_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "button", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
}
int
-ltk_button_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "button", parseinfo, LENGTH(parseinfo));
+ltk_button_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "button", parseinfo, LENGTH(parseinfo));
}
void
-ltk_button_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
+ltk_button_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
}
-/* FIXME: only keep text in surface to avoid large surface */
static void
ltk_button_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
- ltk_button *button = (ltk_button *)self;
+ ltk_button *button = LTK_CAST_BUTTON(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;
- ltk_surface *s;
- ltk_surface_cache_request_surface_size(button->key, lrect.w, lrect.h);
- if (!ltk_surface_cache_get_surface(button->key, &s) || self->dirty)
- ltk_button_redraw_surface(button, s);
- ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
-}
-static void
-ltk_button_redraw_surface(ltk_button *button, ltk_surface *s) {
- ltk_rect rect = button->widget.lrect;
int bw = theme.border_width;
ltk_color *border = NULL, *fill = NULL;
/* FIXME: HOVERACTIVE STATE */
- if (button->widget.state & LTK_DISABLED) {
- border = &theme.border_disabled;
- fill = &theme.fill_disabled;
- } else if (button->widget.state & LTK_PRESSED) {
- border = &theme.border_pressed;
- fill = &theme.fill_pressed;
- } else if (button->widget.state & LTK_HOVER) {
- border = &theme.border_hover;
- fill = &theme.fill_hover;
- } else if (button->widget.state & LTK_ACTIVE) {
- border = &theme.border_active;
- fill = &theme.fill_active;
+ if (self->state & LTK_DISABLED) {
+ border = theme.border_disabled;
+ fill = theme.fill_disabled;
+ } else if (self->state & LTK_PRESSED) {
+ border = theme.border_pressed;
+ fill = theme.fill_pressed;
+ } else if (self->state & LTK_HOVER) {
+ border = theme.border_hover;
+ fill = theme.fill_hover;
+ } else if (self->state & LTK_ACTIVE) {
+ border = theme.border_active;
+ fill = theme.fill_active;
} else {
- border = &theme.border;
- fill = &theme.fill;
+ border = theme.border;
+ fill = theme.fill;
+ }
+ /* FIXME: helper functions for these common rect calculations */
+ ltk_rect draw_rect = {x, y, lrect.w, lrect.h};
+ 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);
+ /* FIXME: support theme setting for border sides */
+ if (bw > 0) {
+ ltk_surface_draw_border_clipped(
+ draw_surf, border, draw_rect, draw_clip, bw, LTK_BORDER_ALL
+ );
}
- rect.x = 0;
- rect.y = 0;
- ltk_surface_fill_rect(s, fill, rect);
- if (bw > 0)
- ltk_surface_draw_rect(s, border, (ltk_rect){bw / 2, bw / 2, rect.w - bw, rect.h - bw}, bw);
-
int text_w, text_h;
ltk_text_line_get_size(button->tl, &text_w, &text_h);
- int text_x = (rect.w - text_w) / 2;
- int text_y = (rect.h - text_h) / 2;
- ltk_text_line_draw(button->tl, s, &theme.text_color, text_x, text_y);
- button->widget.dirty = 0;
+ int text_x = x + (lrect.w - text_w) / 2;
+ 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 int
ltk_button_release(ltk_widget *self) {
- ltk_queue_specific_event(self, "button", LTK_PWEVENTMASK_BUTTON_PRESS, "press");
+ ltk_widget_emit_signal(self, LTK_BUTTON_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST);
return 1;
}
-static ltk_button *
-ltk_button_create(ltk_window *window, const char *id, char *text) {
+ltk_button *
+ltk_button_create(ltk_window *window, char *text) {
ltk_button *button = ltk_malloc(sizeof(ltk_button));
uint16_t font_size = window->theme->font_size;
- button->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1);
+ button->tl = ltk_text_line_create_default(font_size, text, 0, -1);
int text_w, text_h;
ltk_text_line_get_size(button->tl, &text_w, &text_h);
- ltk_fill_widget_defaults(&button->widget, id, window, &vtable, button->widget.ideal_w, button->widget.ideal_h);
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(button), window, &vtable, button->widget.ideal_w, button->widget.ideal_h);
button->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
button->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2;
- button->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, button->widget.ideal_w, button->widget.ideal_h);
button->widget.dirty = 1;
return button;
@@ -196,50 +183,11 @@ ltk_button_create(ltk_window *window, const char *id, char *text) {
static void
ltk_button_destroy(ltk_widget *self, int shallow) {
(void)shallow;
- ltk_button *button = (ltk_button *)self;
+ ltk_button *button = LTK_CAST_BUTTON(self);
if (!button) {
ltk_warn("Tried to destroy NULL button.\n");
return;
}
- ltk_surface_cache_release_key(button->key);
ltk_text_line_destroy(button->tl);
ltk_free(button);
}
-
-/* button <button id> create <text> */
-static int
-ltk_button_cmd_create(
- ltk_window *window,
- ltk_button *button_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)button_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_button *button = ltk_button_create(window, cmd[1].val.str, cmd[3].val.str);
- ltk_set_widget((ltk_widget *)button, cmd[1].val.str);
-
- return 0;
-}
-
-static struct button_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_button *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} button_cmds[] = {
- {"create", <k_button_cmd_create, 1},
-};
-
-GEN_CMD_HELPERS(ltk_button_cmd, LTK_WIDGET_BUTTON, ltk_button, button_cmds, struct button_cmd)
diff --git a/src/button.h b/src/button.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016, 2017, 2018, 2020, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016-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
@@ -17,21 +17,23 @@
#ifndef LTK_BUTTON_H
#define LTK_BUTTON_H
-/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
+#include "graphics.h"
+#include "text.h"
+#include "widget.h"
+#include "window.h"
-#include "cmd.h"
-#include "err.h"
+#define LTK_BUTTON_SIGNAL_PRESSED -1
+#define LTK_BUTTON_SIGNAL_INVALID -2
typedef struct {
ltk_widget widget;
ltk_text_line *tl;
- ltk_surface_cache_key *key;
} ltk_button;
-int ltk_button_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_button_fill_theme_defaults(ltk_window *window);
-void ltk_button_uninitialize_theme(ltk_window *window);
+int ltk_button_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_button_fill_theme_defaults(ltk_renderdata *data);
+void ltk_button_uninitialize_theme(ltk_renderdata *data);
-GEN_CMD_HELPERS_PROTO(ltk_button_cmd)
+ltk_button *ltk_button_create(ltk_window *window, char *text);
#endif /* LTK_BUTTON_H */
diff --git a/src/clipboard.h b/src/clipboard.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2023-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_CLIPBOARD_H
#define LTK_CLIPBOARD_H
diff --git a/src/clipboard_xlib.c b/src/clipboard_xlib.c
@@ -1,22 +1,37 @@
+/*
+ * Copyright (c) 2023-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.
+ */
+
/* Copied almost exactly from ledit. */
-#include <time.h>
#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
+#include <time.h>
-#include <X11/Xlib.h>
+#include <X11/X.h>
#include <X11/Xatom.h>
+#include <X11/Xlib.h>
-#include "util.h"
-#include "memory.h"
-#include "graphics.h"
-#include "clipboard.h"
#include "clipboard_xlib.h"
-#include "xlib_shared.h"
-#include "macros.h"
-#include "config.h"
+#include "clipboard.h"
#include "ctrlsel.h"
+#include "graphics.h"
+#include "macros.h"
+#include "memory.h"
+#include "txtbuf.h"
+#include "graphics_xlib.h"
/* Some *inspiration* taken from SDL (https://libsdl.org), mainly
the idea to create a separate window just for clipboard handling. */
diff --git a/src/clipboard_xlib.h b/src/clipboard_xlib.h
@@ -1,9 +1,24 @@
+/*
+ * Copyright (c) 2023-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_CLIPBOARD_XLIB_H
#define LTK_CLIPBOARD_XLIB_H
#include <X11/Xlib.h>
#include "clipboard.h"
-#include "txtbuf.h"
/* 1 means the event was used by the clipboard, 0 means it wasn't */
int ltk_clipboard_filter_event(ltk_clipboard *clip, XEvent *e);
diff --git a/src/cmd.c b/src/cmd.c
@@ -1,160 +0,0 @@
-#include "graphics.h"
-#include "surface_cache.h"
-#include "util.h"
-#include "cmd.h"
-#include "memory.h"
-
-int
-ltk_parse_cmd(
- ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens,
- ltk_cmdarg_parseinfo *parseinfo, size_t num_arg, ltk_error *err) {
- const char *errstr = NULL;
- if (num_tokens > num_arg || (num_tokens < num_arg && !parseinfo[num_tokens].optional)) {
- err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
- err->arg = -1;
- return 1;
- }
- size_t i = 0;
- for (; i < num_tokens; i++) {
- if (parseinfo[i].type != CMDARG_DATA && tokens[i].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- switch (parseinfo[i].type) {
- case CMDARG_INT:
- parseinfo[i].val.i = ltk_strtonum(tokens[i].text, parseinfo[i].min, parseinfo[i].max, &errstr);
- if (errstr) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_STICKY:
- parseinfo[i].val.sticky = LTK_STICKY_NONE;
- for (const char *c = tokens[i].text; *c != '\0'; c++) {
- switch (*c) {
- case 't':
- parseinfo[i].val.sticky |= LTK_STICKY_TOP;
- break;
- case 'b':
- parseinfo[i].val.sticky |= LTK_STICKY_BOTTOM;
- break;
- case 'r':
- parseinfo[i].val.sticky |= LTK_STICKY_RIGHT;
- break;
- case 'l':
- parseinfo[i].val.sticky |= LTK_STICKY_LEFT;
- break;
- case 'w':
- parseinfo[i].val.sticky |= LTK_STICKY_SHRINK_WIDTH;
- break;
- case 'h':
- parseinfo[i].val.sticky |= LTK_STICKY_SHRINK_HEIGHT;
- break;
- case 'p':
- parseinfo[i].val.sticky |= LTK_STICKY_PRESERVE_ASPECT_RATIO;
- break;
- default:
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_WIDGET:
- parseinfo[i].val.widget = ltk_get_widget(tokens[i].text, parseinfo[i].widget_type, err);
- if (!parseinfo[i].val.widget) {
- err->arg = i;
- goto error;
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_STRING:
- parseinfo[i].val.str = tokens[i].text;
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_DATA:
- parseinfo[i].val.data = tokens[i].text;
- parseinfo[i].initialized = 1;
- parseinfo[i].len = tokens[i].len;
- break;
- case CMDARG_COLOR:
- if (ltk_color_create(window->renderdata, tokens[i].text, &parseinfo[i].val.color)) {
- /* FIXME: this could fail even if the argument is fine */
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_BOOL:
- if (strcmp(tokens[i].text, "true") == 0) {
- parseinfo[i].val.b = 1;
- } else if (strcmp(tokens[i].text, "false") == 0) {
- parseinfo[i].val.b = 0;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_ORIENTATION:
- if (strcmp(tokens[i].text, "horizontal") == 0) {
- parseinfo[i].val.orient = LTK_HORIZONTAL;
- } else if (strcmp(tokens[i].text, "vertical") == 0) {
- parseinfo[i].val.orient = LTK_VERTICAL;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_BORDERSIDES:
- parseinfo[i].val.border = LTK_BORDER_NONE;
- for (const char *c = tokens[i].text; *c != '\0'; c++) {
- switch (*c) {
- case 't':
- parseinfo[i].val.border |= LTK_BORDER_TOP;
- break;
- case 'b':
- parseinfo[i].val.border |= LTK_BORDER_BOTTOM;
- break;
- case 'l':
- parseinfo[i].val.border |= LTK_BORDER_LEFT;
- break;
- case 'r':
- parseinfo[i].val.border |= LTK_BORDER_RIGHT;
- break;
- default:
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = i;
- goto error;
- }
- }
- parseinfo[i].initialized = 1;
- break;
- case CMDARG_IGNORE:
- parseinfo[i].initialized = 1;
- break;
- default:
- ltk_fatal("Invalid command argument type. This should not happen.\n");
- /* TODO: ltk_assert(0); */
- }
- }
- for (; i < num_arg; i++) {
- parseinfo[i].initialized = 0;
- }
- return 0;
-error:
- for (; i-- > 0;) {
- if (parseinfo[i].type == CMDARG_COLOR) {
- ltk_color_destroy(window->renderdata, &parseinfo[i].val.color);
- }
- }
- return 1;
-}
diff --git a/src/cmd.h b/src/cmd.h
@@ -1,125 +0,0 @@
-#ifndef LTK_CMD_H
-#define LTK_CMD_H
-
-#include "ltk.h"
-
-typedef enum {
- CMDARG_IGNORE,
- CMDARG_STRING, /* nul-terminated string */
- CMDARG_DATA, /* also char*, but may contain nul */
- CMDARG_COLOR,
- CMDARG_INT,
- CMDARG_BOOL,
- CMDARG_BORDERSIDES,
- CMDARG_STICKY,
- CMDARG_WIDGET,
- CMDARG_ORIENTATION
-} ltk_cmdarg_datatype;
-
-/* color needs to be destroyed by cmd handling function if it is not needed anymore */
-/* str must *not* be freed because it is a pointer to the original argument
- -> it must be copied if it needs to be kept around */
-typedef struct {
- ltk_cmdarg_datatype type;
- /* Note: Bool and int are both integers, but they are
- separate just to make it a bit clearer (same goes for str/data) */
- union {
- char *str;
- char *data;
- ltk_color color;
- int i;
- int b;
- ltk_border_sides border;
- ltk_sticky_mask sticky;
- ltk_orientation orient;
- ltk_widget *widget;
- } val;
- size_t len; /* only for data */
- int min, max; /* only for integers */ /* FIXME: which integer type is sensible here? */
- ltk_widget_type widget_type; /* only for widgets */
- int optional;
- int initialized;
-} ltk_cmdarg_parseinfo;
-
-/* Returns 1 on error, 0 on success */
-/* All optional arguments must be in one block at the end */
-int ltk_parse_cmd(
- ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens,
- ltk_cmdarg_parseinfo *parseinfo, size_t num_arg, ltk_error *err
-);
-
-#define GEN_CMD_HELPERS_PROTO(func_name) \
-int func_name(ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err);
-
-
-#define GEN_CMD_HELPERS(func_name, widget_type, widget_typename, array_name, entry_type) \
-/* FIXME: maybe just get rid of this and rely on array already being sorted? */ \
-static int array_name##_sorted = 0; \
- \
-static int \
-array_name##_search_helper(const void *keyv, const void *entryv) { \
- const char *key = (const char *)keyv; \
- entry_type *entry = (entry_type *)entryv; \
- return strcmp(key, entry->name); \
-} \
- \
-static int \
-array_name##_sort_helper(const void *entry1v, const void *entry2v) { \
- entry_type *entry1 = (entry_type *)entry1v; \
- entry_type *entry2 = (entry_type *)entry2v; \
- return strcmp(entry1->name, entry2->name); \
-} \
- \
-int \
-func_name( \
- ltk_window *window, \
- ltk_cmd_token *tokens, \
- size_t num_tokens, \
- ltk_error *err) { \
- if (num_tokens < 3) { \
- err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS; \
- err->arg = -1; \
- return 1; \
- } \
- /* just in case */ \
- if (!array_name##_sorted) { \
- qsort( \
- array_name, LENGTH(array_name), \
- sizeof(array_name[0]), &array_name##_sort_helper); \
- array_name##_sorted = 1; \
- } \
- if (tokens[1].contains_nul) { \
- err->type = ERR_INVALID_ARGUMENT; \
- err->arg = 1; \
- return 1; \
- } else if (tokens[2].contains_nul) { \
- err->type = ERR_INVALID_ARGUMENT; \
- err->arg = 2; \
- return 1; \
- } \
- entry_type *e = bsearch( \
- tokens[2].text, array_name, LENGTH(array_name), \
- sizeof(array_name[0]), &array_name##_search_helper \
- ); \
- if (!e) { \
- err->type = ERR_INVALID_COMMAND; \
- err->arg = -1; \
- return 1; \
- } \
- if (e->needs_all) { \
- return e->func(window, NULL, tokens, num_tokens, err); \
- } else { \
- widget_typename *widget = (widget_typename *)ltk_get_widget(tokens[1].text, widget_type, err); \
- if (!widget) { \
- err->arg = 1; \
- return 1; \
- } \
- int ret = e->func(window, widget, tokens + 3, num_tokens - 3, err); \
- if (ret && err->arg >= 0) \
- err->arg += 3; \
- return ret; \
- } \
- return 0; /* Well, I guess this is impossible anyways... */ \
-}
-
-#endif /* LTK_CMD_H */
diff --git a/src/color.h b/src/color.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,28 +14,14 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _LTK_COLOR_H_
-#define _LTK_COLOR_H_
+#ifndef LTK_COLOR_H
+#define LTK_COLOR_H
-#include "compat.h"
-
-#if USE_XFT == 1
- #include <X11/Xft/Xft.h>
-#endif
-
-/* FIXME: proper compilation option */
-#include <X11/Xlib.h>
-
-typedef struct {
- XColor xcolor;
- #if USE_XFT == 1
- XftColor xftcolor;
- #endif
-} ltk_color;
+typedef struct ltk_color ltk_color;
#include "graphics.h"
-/* returns 1 on failure, 0 on success */
-int ltk_color_create(ltk_renderdata *renderdata, const char *hex, ltk_color *col);
+
+ltk_color *ltk_color_create(ltk_renderdata *renderdata, const char *hex);
void ltk_color_destroy(ltk_renderdata *renderdata, ltk_color *col);
-#endif /* _LTK_COLOR_H_ */
+#endif /* LTK_COLOR_H */
diff --git a/src/color_xlib.c b/src/color_xlib.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,27 +14,36 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdarg.h>
-#include "util.h"
+#include <stddef.h>
+
+#include <X11/Xlib.h>
+
#include "color.h"
-#include "compat.h"
-#include "xlib_shared.h"
+#include "memory.h"
+#include "graphics.h"
+#include "graphics_xlib.h"
-/* FIXME: avoid initializing part of the struct and then error returning */
-/* FIXME: better error codes */
/* FIXME: I think xcolor is unneeded when xft is enabled */
-int
-ltk_color_create(ltk_renderdata *renderdata, const char *hex, ltk_color *col) {
- if (!XParseColor(renderdata->dpy, renderdata->cm, hex, &col->xcolor))
- return 1;
- if (!XAllocColor(renderdata->dpy, renderdata->cm, &col->xcolor))
- return 1;
+ltk_color *
+ltk_color_create(ltk_renderdata *renderdata, const char *hex) {
+ ltk_color *col = ltk_malloc(sizeof(ltk_color));
+ if (!XParseColor(renderdata->dpy, renderdata->cm, hex, &col->xcolor)) {
+ ltk_free(col);
+ return NULL;
+ }
+ if (!XAllocColor(renderdata->dpy, renderdata->cm, &col->xcolor)) {
+ ltk_free(col);
+ return NULL;
+ }
/* FIXME: replace with XftColorAllocValue */
#if USE_XFT == 1
- if (!XftColorAllocName(renderdata->dpy, renderdata->vis, renderdata->cm, hex, &col->xftcolor))
- return 1;
+ if (!XftColorAllocName(renderdata->dpy, renderdata->vis, renderdata->cm, hex, &col->xftcolor)) {
+ XFreeColors(renderdata->dpy, renderdata->cm, &col->xcolor.pixel, 1, 0);
+ ltk_free(col);
+ return NULL;
+ }
#endif
- return 0;
+ return col;
}
void
@@ -44,4 +53,5 @@ ltk_color_destroy(ltk_renderdata *renderdata, ltk_color *col) {
#if USE_XFT == 1
XftColorFree(renderdata->dpy, renderdata->vis, renderdata->cm, &col->xftcolor);
#endif
+ ltk_free(col);
}
diff --git a/src/compat.h b/src/compat.h
@@ -1,9 +0,0 @@
-#ifndef _LTK_COMPAT_H_
-#define _LTK_COMPAT_H_
-
-#if USE_PANGO == 1
-#undef USE_XFT
-#define USE_XFT 1
-#endif
-
-#endif
diff --git a/src/config.c b/src/config.c
@@ -1,6 +1,21 @@
+/*
+ * Copyright (c) 2022-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.
+ */
+
/* FIXME: This really is horrible. */
-#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <limits.h>
@@ -328,8 +343,8 @@ parse_keypress_binding(
*binding_ret = b;
return 0;
error:
- free(b.text);
- free(b.rawtext);
+ ltk_free(b.text);
+ ltk_free(b.rawtext);
if (msg) {
*errstr = ltk_print_fmt(
"%s, line %zu, offset %zu: %s", s->filename, tok->line, tok->line_offset, msg
@@ -375,8 +390,8 @@ parse_keybinding(
if (parse_keypress_binding(s, tok, &b, &name, &nlen, errstr))
return 1;
if (b.text || b.rawtext) {
- free(b.text);
- free(b.rawtext);
+ ltk_free(b.text);
+ ltk_free(b.rawtext);
msg = "Text and rawtext may only be specified for keypress bindings";
goto error;
}
@@ -677,7 +692,7 @@ ltk_config_parsefile(
const char *default_config = "[general]\n"
"explicit-focus = true\n"
"all-activatable = true\n"
-"[key-binding:widget]\n"
+"[key-binding:window]\n"
"bind-keypress move-next sym tab\n"
"bind-keypress move-prev sym tab mods shift\n"
"bind-keypress move-next text n\n"
@@ -697,7 +712,7 @@ ltk_config_load_default(
char **errstr) {
char *config_copied = ltk_strdup(default_config);
int ret = load_from_text("<default config>", config_copied, strlen(config_copied), press_handler, release_handler, errstr);
- free(config_copied);
+ ltk_free(config_copied);
return ret;
}
diff --git a/src/config.h b/src/config.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022-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_CONFIG_H
#define LTK_CONFIG_H
diff --git a/src/entry.c b/src/entry.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -20,38 +20,32 @@
/* FIXME: set imspot - needs to be standardized so widgets don't all do their own thing */
/* FIXME: some sort of width setting (setting a pixel width would be kind of ugly) */
-#include <stdio.h>
#include <ctype.h>
-#include <stdlib.h>
#include <stdint.h>
#include <string.h>
-#include <stdarg.h>
-#include "proto_types.h"
+#include "entry.h"
+#include "array.h"
+#include "clipboard.h"
+#include "color.h"
#include "event.h"
+#include "eventdefs.h"
+#include "graphics.h"
+#include "keys.h"
+#include "ltk.h"
#include "memory.h"
-#include "color.h"
#include "rect.h"
-#include "widget.h"
-#include "ltk.h"
-#include "util.h"
#include "text.h"
-#include "entry.h"
-#include "graphics.h"
-#include "surface_cache.h"
#include "theme.h"
-#include "array.h"
-#include "keys.h"
-#include "cmd.h"
+#include "txtbuf.h"
+#include "util.h"
+#include "widget.h"
#define MAX_ENTRY_BORDER_WIDTH 100
#define MAX_ENTRY_PADDING 500
static void ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
-static ltk_entry *ltk_entry_create(ltk_window *window,
- const char *id, char *text);
static void ltk_entry_destroy(ltk_widget *self, int shallow);
-static void ltk_entry_redraw_surface(ltk_entry *entry, ltk_surface *s);
static int ltk_entry_key_press(ltk_widget *self, ltk_key_event *event);
static int ltk_entry_key_release(ltk_widget *self, ltk_key_event *event);
@@ -186,28 +180,29 @@ static struct ltk_widget_vtable vtable = {
.remove_child = NULL,
.type = LTK_WIDGET_ENTRY,
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_NEEDS_KEYBOARD,
+ .invalid_signal = LTK_ENTRY_SIGNAL_INVALID,
};
static struct {
int border_width;
- ltk_color text_color;
- ltk_color selection_color;
+ ltk_color *text_color;
+ ltk_color *selection_color;
int pad;
- ltk_color border;
- ltk_color fill;
+ ltk_color *border;
+ ltk_color *fill;
- ltk_color border_pressed;
- ltk_color fill_pressed;
+ ltk_color *border_pressed;
+ ltk_color *fill_pressed;
- ltk_color border_hover;
- ltk_color fill_hover;
+ ltk_color *border_hover;
+ ltk_color *fill_hover;
- ltk_color border_active;
- ltk_color fill_active;
+ ltk_color *border_active;
+ ltk_color *fill_active;
- ltk_color border_disabled;
- ltk_color fill_disabled;
+ ltk_color *border_disabled;
+ ltk_color *fill_disabled;
} theme;
/* FIXME:
@@ -233,82 +228,78 @@ static ltk_theme_parseinfo parseinfo[] = {
static int parseinfo_sorted = 0;
int
-ltk_entry_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "entry", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
+ltk_entry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "entry", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
}
int
-ltk_entry_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "entry", parseinfo, LENGTH(parseinfo));
+ltk_entry_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "entry", parseinfo, LENGTH(parseinfo));
}
void
-ltk_entry_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
+ltk_entry_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
}
-/* FIXME: only keep text in surface to avoid large surface */
-/* -> or maybe not even that? */
+/* FIXME: draw cursor in different color on selection side that will be expanded */
static void
ltk_entry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
- ltk_entry *entry = (ltk_entry *)self;
+ ltk_entry *entry = LTK_CAST_ENTRY(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;
- ltk_surface *s;
- ltk_surface_cache_request_surface_size(entry->key, lrect.w, lrect.h);
- if (!ltk_surface_cache_get_surface(entry->key, &s) || self->dirty)
- ltk_entry_redraw_surface(entry, s);
- ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
-}
-/* FIXME: draw cursor in different color on selection side that will be expanded */
-static void
-ltk_entry_redraw_surface(ltk_entry *entry, ltk_surface *s) {
- ltk_rect rect = entry->widget.lrect;
int bw = theme.border_width;
ltk_color *border = NULL, *fill = NULL;
/* FIXME: HOVERACTIVE STATE */
- if (entry->widget.state & LTK_DISABLED) {
- border = &theme.border_disabled;
- fill = &theme.fill_disabled;
- } else if (entry->widget.state & LTK_PRESSED) {
- border = &theme.border_pressed;
- fill = &theme.fill_pressed;
- } else if (entry->widget.state & LTK_ACTIVE) {
- border = &theme.border_active;
- fill = &theme.fill_active;
- } else if (entry->widget.state & LTK_HOVER) {
- border = &theme.border_hover;
- fill = &theme.fill_hover;
+ if (self->state & LTK_DISABLED) {
+ border = theme.border_disabled;
+ fill = theme.fill_disabled;
+ } else if (self->state & LTK_PRESSED) {
+ border = theme.border_pressed;
+ fill = theme.fill_pressed;
+ } else if (self->state & LTK_ACTIVE) {
+ border = theme.border_active;
+ fill = theme.fill_active;
+ } else if (self->state & LTK_HOVER) {
+ border = theme.border_hover;
+ fill = theme.fill_hover;
} else {
- border = &theme.border;
- fill = &theme.fill;
+ border = theme.border;
+ fill = theme.fill;
+ }
+ ltk_rect draw_rect = {x, y, lrect.w, lrect.h};
+ 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);
+ if (bw > 0) {
+ ltk_surface_draw_border_clipped(
+ draw_surf, border, draw_rect, draw_clip, bw, LTK_BORDER_ALL
+ );
}
- rect.x = 0;
- rect.y = 0;
- ltk_surface_fill_rect(s, fill, rect);
- if (bw > 0)
- ltk_surface_draw_rect(s, border, (ltk_rect){bw / 2, bw / 2, rect.w - bw, rect.h - bw}, bw);
int text_w, text_h;
ltk_text_line_get_size(entry->tl, &text_w, &text_h);
/* FIXME: what if text_h > rect.h? */
int x_offset = 0;
- if (text_w < rect.w - 2 * (bw + theme.pad) && ltk_text_line_get_softline_direction(entry->tl, 0) == LTK_TEXT_RTL)
- x_offset = rect.w - 2 * (bw + theme.pad) - text_w;
- int text_x = bw + theme.pad + x_offset;
- int text_y = (rect.h - text_h) / 2;
- ltk_rect clip_rect = (ltk_rect){text_x, text_y, rect.w - 2 * bw - 2 * theme.pad, text_h};
- ltk_text_line_draw_clipped(entry->tl, s, &theme.text_color, text_x - entry->cur_offset, text_y, clip_rect);
- if ((entry->widget.state & LTK_FOCUSED) && entry->sel_start == entry->sel_end) {
- int x, y, w, h;
- ltk_text_line_pos_to_rect(entry->tl, entry->pos, &x, &y, &w, &h);
+ if (text_w < lrect.w - 2 * (bw + theme.pad) && ltk_text_line_get_softline_direction(entry->tl, 0) == LTK_TEXT_RTL)
+ x_offset = lrect.w - 2 * (bw + theme.pad) - text_w;
+ int text_x = x + bw + theme.pad + x_offset;
+ int text_y = y + (lrect.h - text_h) / 2;
+ ltk_rect clip_rect = (ltk_rect){text_x, text_y, lrect.w - 2 * bw - 2 * theme.pad, text_h};
+ ltk_text_line_draw_clipped(entry->tl, draw_surf, theme.text_color, text_x - entry->cur_offset, text_y, clip_rect);
+ if ((self->state & LTK_FOCUSED) && entry->sel_start == entry->sel_end) {
+ int cx, cy, cw, ch;
+ ltk_text_line_pos_to_rect(entry->tl, entry->pos, &cx, &cy, &cw, &ch);
+ ltk_rect line_rect = {cx - entry->cur_offset + text_x, cy + text_y, 1, ch};
/* FIXME: configure line width */
- ltk_surface_draw_rect(s, &theme.text_color, (ltk_rect){x - entry->cur_offset + text_x, y + text_y, 1, h}, 1);
+ ltk_surface_fill_rect(
+ draw_surf, theme.text_color,
+ ltk_rect_intersect(draw_clip, line_rect)
+ );
}
- entry->widget.dirty = 0;
+ self->dirty = 0;
}
static size_t
@@ -327,7 +318,7 @@ set_selection(ltk_entry *entry, size_t start, size_t end) {
entry->sel_end = end;
ltk_text_line_clear_attrs(entry->tl);
if (start != end)
- ltk_text_line_add_attr_bg(entry->tl, entry->sel_start, entry->sel_end, &theme.selection_color);
+ ltk_text_line_add_attr_bg(entry->tl, entry->sel_start, entry->sel_end, theme.selection_color);
entry->widget.dirty = 1;
ltk_window_invalidate_widget_rect(entry->widget.window, &entry->widget);
}
@@ -404,10 +395,10 @@ selection_to_primary(ltk_entry *entry, ltk_key_event *event) {
(void)event;
if (entry->sel_end == entry->sel_start)
return;
- txtbuf *primary = ltk_clipboard_get_primary_buffer(entry->widget.window->clipboard);
+ txtbuf *primary = ltk_clipboard_get_primary_buffer(ltk_get_clipboard());
txtbuf_clear(primary);
txtbuf_appendn(primary, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
- ltk_clipboard_set_primary_selection_owner(entry->widget.window->clipboard);
+ ltk_clipboard_set_primary_selection_owner(ltk_get_clipboard());
}
static void
@@ -415,10 +406,10 @@ selection_to_clipboard(ltk_entry *entry, ltk_key_event *event) {
(void)event;
if (entry->sel_end == entry->sel_start)
return;
- txtbuf *clip = ltk_clipboard_get_clipboard_buffer(entry->widget.window->clipboard);
+ txtbuf *clip = ltk_clipboard_get_clipboard_buffer(ltk_get_clipboard());
txtbuf_clear(clip);
txtbuf_appendn(clip, entry->text + entry->sel_start, entry->sel_end - entry->sel_start);
- ltk_clipboard_set_clipboard_selection_owner(entry->widget.window->clipboard);
+ ltk_clipboard_set_clipboard_selection_owner(ltk_get_clipboard());
}
static void
switch_selection_side(ltk_entry *entry, ltk_key_event *event) {
@@ -429,7 +420,7 @@ switch_selection_side(ltk_entry *entry, ltk_key_event *event) {
static void
paste_primary(ltk_entry *entry, ltk_key_event *event) {
(void)event;
- txtbuf *buf = ltk_clipboard_get_primary_text(entry->widget.window->clipboard);
+ txtbuf *buf = ltk_clipboard_get_primary_text(ltk_get_clipboard());
if (buf)
insert_text(entry, buf->text, buf->len, 1);
}
@@ -437,7 +428,7 @@ paste_primary(ltk_entry *entry, ltk_key_event *event) {
static void
paste_clipboard(ltk_entry *entry, ltk_key_event *event) {
(void)event;
- txtbuf *buf = ltk_clipboard_get_clipboard_text(entry->widget.window->clipboard);
+ txtbuf *buf = ltk_clipboard_get_clipboard_text(ltk_get_clipboard());
if (buf)
insert_text(entry, buf->text, buf->len, 1);
}
@@ -576,7 +567,7 @@ insert_text(ltk_entry *entry, char *text, size_t len, int move_cursor) {
static void
ltk_entry_cmd_return(ltk_widget *self, char *text, size_t len) {
- ltk_entry *e = (ltk_entry *)self;
+ ltk_entry *e = LTK_CAST_ENTRY(self);
wipe_selection(e);
e->len = e->pos = 0;
insert_text(e, text, len, 0);
@@ -593,15 +584,15 @@ edit_external(ltk_entry *entry, ltk_key_event *event) {
} else {
/* FIXME: somehow show that there was an error if this returns 1? */
/* FIXME: change interface to not require length of cmd */
- ltk_window_call_cmd(entry->widget.window, &entry->widget, config->general.line_editor, strlen(config->general.line_editor), entry->text, entry->len);
+ ltk_call_cmd(LTK_CAST_WIDGET(entry), config->general.line_editor, strlen(config->general.line_editor), entry->text, entry->len);
}
}
static int
ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) {
- ltk_entry *entry = (ltk_entry *)self;
+ ltk_entry *entry = LTK_CAST_ENTRY(self);
ltk_keypress_binding b;
- for (size_t i = 0; i < ltk_array_length(keypresses); i++) {
+ for (size_t i = 0; i < ltk_array_len(keypresses); i++) {
b = ltk_array_get(keypresses, i).b;
/* FIXME: change naming (rawtext, text, mapped...) */
/* FIXME: a bit weird to mask out shift, but if that isn't done, it
@@ -611,7 +602,7 @@ ltk_entry_key_press(ltk_widget *self, ltk_key_event *event) {
((b.text && event->mapped && !strcmp(b.text, event->mapped)) ||
(b.rawtext && event->text && !strcmp(b.rawtext, event->text))))) {
ltk_array_get(keypresses, i).cb.func(entry, event);
- entry->widget.dirty = 1;
+ self->dirty = 1;
ltk_window_invalidate_widget_rect(self->window, self);
return 1;
}
@@ -634,7 +625,7 @@ ltk_entry_key_release(ltk_widget *self, ltk_key_event *event) {
static int
ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
- ltk_entry *e = (ltk_entry *)self;
+ ltk_entry *e = LTK_CAST_ENTRY(self);
int side = theme.border_width + theme.pad;
if (event->x < side || event->x > self->lrect.w - side ||
event->y < side || event->y > self->lrect.h - side) {
@@ -703,7 +694,7 @@ ltk_entry_mouse_press(ltk_widget *self, ltk_button_event *event) {
static int
ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) {
- ltk_entry *e = (ltk_entry *)self;
+ ltk_entry *e = LTK_CAST_ENTRY(self);
if (event->button == LTK_BUTTONL) {
e->selecting = 0;
selection_to_primary(e, NULL);
@@ -713,7 +704,7 @@ ltk_entry_mouse_release(ltk_widget *self, ltk_button_event *event) {
static int
ltk_entry_motion_notify(ltk_widget *self, ltk_motion_event *event) {
- ltk_entry *e = (ltk_entry *)self;
+ ltk_entry *e = LTK_CAST_ENTRY(self);
if (e->selecting) {
/* this occurs when something like deletion happens while text
is being selected (FIXME: a bit weird) */
@@ -749,18 +740,17 @@ ltk_entry_mouse_leave(ltk_widget *self, ltk_motion_event *event) {
return 0;
}
-static ltk_entry *
-ltk_entry_create(ltk_window *window, const char *id, char *text) {
+ltk_entry *
+ltk_entry_create(ltk_window *window, char *text) {
ltk_entry *entry = ltk_malloc(sizeof(ltk_entry));
uint16_t font_size = window->theme->font_size;
- entry->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1);
+ entry->tl = ltk_text_line_create_default(font_size, text, 0, -1);
int text_w, text_h;
ltk_text_line_get_size(entry->tl, &text_w, &text_h);
- ltk_fill_widget_defaults(&entry->widget, id, window, &vtable, entry->widget.ideal_w, entry->widget.ideal_h);
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(entry), window, &vtable, entry->widget.ideal_w, entry->widget.ideal_h);
entry->widget.ideal_w = text_w + theme.border_width * 2 + theme.pad * 2;
entry->widget.ideal_h = text_h + theme.border_width * 2 + theme.pad * 2;
- entry->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, entry->widget.ideal_w, entry->widget.ideal_h);
entry->cur_offset = 0;
entry->text = ltk_strdup(text);
entry->len = strlen(text);
@@ -776,52 +766,12 @@ ltk_entry_create(ltk_window *window, const char *id, char *text) {
static void
ltk_entry_destroy(ltk_widget *self, int shallow) {
(void)shallow;
- ltk_entry *entry = (ltk_entry *)self;
+ ltk_entry *entry = LTK_CAST_ENTRY(self);
if (!entry) {
ltk_warn("Tried to destroy NULL entry.\n");
return;
}
ltk_free(entry->text);
- ltk_surface_cache_release_key(entry->key);
ltk_text_line_destroy(entry->tl);
ltk_free(entry);
}
-
-/* FIXME: make text optional, command set-text */
-/* entry <entry id> create <text> */
-static int
-ltk_entry_cmd_create(
- ltk_window *window,
- ltk_entry *entry_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)entry_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_entry *entry = ltk_entry_create(window, cmd[1].val.str, cmd[3].val.str);
- ltk_set_widget((ltk_widget *)entry, cmd[1].val.str);
-
- return 0;
-}
-
-static struct entry_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_entry *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} entry_cmds[] = {
- {"create", <k_entry_cmd_create, 1},
-};
-
-GEN_CMD_HELPERS(ltk_entry_cmd, LTK_WIDGET_ENTRY, ltk_entry, entry_cmds, struct entry_cmd)
diff --git a/src/entry.h b/src/entry.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -17,16 +17,18 @@
#ifndef LTK_ENTRY_H
#define LTK_ENTRY_H
-/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
-
-#include "cmd.h"
-#include "err.h"
+#include <stddef.h>
#include "config.h"
+#include "graphics.h"
+#include "widget.h"
+#include "window.h"
+#include "text.h"
+
+#define LTK_ENTRY_SIGNAL_INVALID -1
typedef struct {
ltk_widget widget;
ltk_text_line *tl;
- ltk_surface_cache_key *key;
char *text;
size_t len, alloc, pos, sel_start, sel_end;
int cur_offset;
@@ -36,15 +38,15 @@ typedef struct {
char selecting;
} ltk_entry;
-int ltk_entry_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_entry_fill_theme_defaults(ltk_window *window);
-void ltk_entry_uninitialize_theme(ltk_window *window);
+int ltk_entry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_entry_fill_theme_defaults(ltk_renderdata *data);
+void ltk_entry_uninitialize_theme(ltk_renderdata *data);
/* FIXME: document that pointers inside binding are taken over! */
int ltk_entry_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
int ltk_entry_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
void ltk_entry_cleanup(void);
-GEN_CMD_HELPERS_PROTO(ltk_entry_cmd)
+ltk_entry *ltk_entry_create(ltk_window *window, char *text);
#endif /* LTK_ENTRY_H */
diff --git a/src/err.c b/src/err.c
@@ -1,30 +0,0 @@
-#include "err.h"
-
-static const char *errtable[] = {
- "None",
- "Widget already in container",
- "Widget not in container",
- "Widget id already in use",
- "Invalid number of arguments",
- "Invalid argument",
- "Invalid index",
- "Invalid widget id",
- "Invalid widget type",
- "Invalid command",
- "Unknown error",
- "Menu is not submenu",
- "Menu entry already contains submenu",
- "Invalid grid position",
- "Invalid sequence number",
-};
-
-#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
-
-const char *
-errtype_to_string(ltk_errtype type) {
- if (type < 0 || type >= LENGTH(errtable)) {
- /* that's a funny error, now isn't it? */
- return "Invalid error";
- }
- return errtable[type];
-}
diff --git a/src/err.h b/src/err.h
@@ -1,33 +0,0 @@
-#ifndef LTK_ERR_H
-#define LTK_ERR_H
-
-/* WARNING: THIS NEEDS TO BE KEPT IN SYNC WITH THE TABLE IN err.c! */
-/* (also, the explicit value setting is redundant, but just in case) */
-typedef enum {
- ERR_NONE = 0,
- ERR_WIDGET_IN_CONTAINER = 1,
- ERR_WIDGET_NOT_IN_CONTAINER = 2,
- ERR_WIDGET_ID_IN_USE = 3,
- ERR_INVALID_NUMBER_OF_ARGUMENTS = 4,
- ERR_INVALID_ARGUMENT = 5,
- ERR_INVALID_INDEX = 6,
- ERR_INVALID_WIDGET_ID = 7,
- ERR_INVALID_WIDGET_TYPE = 8,
- ERR_INVALID_COMMAND = 9,
- ERR_UNKNOWN = 10,
- /* widget specific */
- ERR_MENU_NOT_SUBMENU = 11,
- ERR_MENU_ENTRY_CONTAINS_SUBMENU = 12,
- ERR_GRID_INVALID_POSITION = 13,
- ERR_INVALID_SEQNUM = 14,
-} ltk_errtype;
-
-typedef struct {
- ltk_errtype type;
- /* corresponding argument, -1 if none */
- int arg;
-} ltk_error;
-
-const char *errtype_to_string(ltk_errtype type);
-
-#endif /* LTK_ERR_H */
diff --git a/src/event.h b/src/event.h
@@ -1,27 +1,49 @@
+/*
+ * Copyright (c) 2022-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_EVENT_H
#define LTK_EVENT_H
+#include <stddef.h>
+
#include "eventdefs.h"
typedef struct {
ltk_event_type type;
+ size_t window_id;
ltk_button_type button;
int x, y;
} ltk_button_event;
typedef struct {
ltk_event_type type;
+ size_t window_id;
int x, y;
int dx, dy;
} ltk_scroll_event;
typedef struct {
ltk_event_type type;
+ size_t window_id;
int x, y;
} ltk_motion_event;
typedef struct {
ltk_event_type type;
+ size_t window_id;
ltk_mod_type modmask;
ltk_keysym sym;
char *text;
@@ -30,11 +52,13 @@ typedef struct {
typedef struct {
ltk_event_type type;
+ size_t window_id;
char *new_kbd;
} ltk_keyboard_event;
typedef struct {
ltk_event_type type;
+ size_t window_id;
int x, y;
int w, h;
} ltk_configure_event;
@@ -42,12 +66,17 @@ typedef struct {
/* FIXME: should maybe be handled in backend with double buffering */
typedef struct {
ltk_event_type type;
+ size_t window_id;
int x, y;
int w, h;
} ltk_expose_event;
typedef union {
ltk_event_type type;
+ struct {
+ ltk_event_type type;
+ size_t window_id;
+ } any;
ltk_button_event button;
ltk_scroll_event scroll;
ltk_motion_event motion;
@@ -57,12 +86,12 @@ typedef union {
ltk_keyboard_event keyboard;
} ltk_event;
-#include "ltk.h"
+#include "graphics.h"
#include "clipboard.h"
void ltk_events_cleanup(void);
/* WARNING: Text returned in key and keyboard events must be copied before calling this function again! */
-int ltk_next_event(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event);
+int ltk_next_event(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows, ltk_clipboard *clip, size_t lang_index, ltk_event *event);
void ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event);
#endif /* LTK_EVENT_H */
diff --git a/src/event_xlib.c b/src/event_xlib.c
@@ -1,14 +1,41 @@
+/*
+ * Copyright (c) 2022-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 <stdint.h>
#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <X11/X.h>
#include <X11/XKBlib.h>
-#include <X11/extensions/XKBrules.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/extensions/XKB.h>
+#include <X11/extensions/XKBstr.h>
+#include <X11/keysym.h>
-#include "util.h"
-#include "memory.h"
-#include "graphics.h"
-#include "xlib_shared.h"
-#include "config.h"
+#include "clipboard.h"
#include "clipboard_xlib.h"
+#include "config.h"
+#include "event.h"
+#include "eventdefs.h"
+#include "graphics.h"
+#include "graphics_xlib.h"
+#include "memory.h"
+#include "util.h"
#define TEXT_INITIAL_SIZE 128
@@ -87,7 +114,7 @@ get_modmask(unsigned int state) {
#endif
static ltk_event
-process_key(ltk_renderdata *renderdata, size_t lang_index, XEvent *event, ltk_event_type type) {
+process_key(ltk_renderwindow *renderwindow, size_t lang_index, XEvent *event, ltk_event_type type) {
ltk_language_mapping *map = ltk_config_get_language_mapping(lang_index);
/* FIXME: see comment in keys.c in ledit repository */
if (!text) {
@@ -99,12 +126,12 @@ process_key(ltk_renderdata *renderdata, size_t lang_index, XEvent *event, ltk_ev
KeySym sym;
int len = 0;
Status status;
- if (renderdata->xic && type == LTK_KEYPRESS_EVENT) {
- len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
+ if (renderwindow->xic && type == LTK_KEYPRESS_EVENT) {
+ len = LOOKUP_STRING_FUNC(renderwindow->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
if (status == XBufferOverflow) {
text_alloc = ideal_array_size(text_alloc, len + 1);
text = ltk_realloc(text, text_alloc);
- len = LOOKUP_STRING_FUNC(renderdata->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
+ len = LOOKUP_STRING_FUNC(renderwindow->xic, &event->xkey, text, text_alloc - 1, &sym, &status);
}
} else {
/* FIXME: anything equivalent to XBufferOverflow here? */
@@ -159,7 +186,9 @@ ltk_generate_keyboard_event(ltk_renderdata *renderdata, ltk_event *event) {
1 means no events pending,
2 means event discarded (need to call again) */
static int
-next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
+next_event_base(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
+ /* FIXME: I guess it's technically possible that a window is destroyed between two calls
+ to this function, meaning that the window_id in next_event isn't correct anymore. */
if (next_event_valid) {
next_event_valid = 0;
*event = (ltk_event){.button = next_event};
@@ -169,18 +198,27 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
if (!XPending(renderdata->dpy))
return 1;
XNextEvent(renderdata->dpy, &xevent);
+ /* FIXME: support different keyboard mappings for different windows */
if (renderdata->xkb_supported && xevent.type == renderdata->xkb_event_type) {
ltk_generate_keyboard_event(renderdata, event);
return 0;
}
- *event = (ltk_event){.type = LTK_UNKNOWN_EVENT};
+ *event = (ltk_event){.any = {.type = LTK_UNKNOWN_EVENT, .window_id = SIZE_MAX}};
if (XFilterEvent(&xevent, None))
return 2;
if (clip && ltk_clipboard_filter_event(clip, &xevent))
return 2;
int button = 0;
+ size_t window_id = SIZE_MAX;
+ for (size_t i = 0; i < num_windows; i++) {
+ if (xevent.xany.window == windows[i]->xwindow) {
+ window_id = i;
+ break;
+ }
+ }
switch (xevent.type) {
case ButtonPress:
+ ltk_assert(window_id < num_windows);
button = xevent.xbutton.button;
/* FIXME: are the buttons really always defined as exactly these values? */
if (button >= 1 && button <= 3) {
@@ -192,6 +230,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
last_button_press[button] = 0;
next_event = (ltk_button_event){
.type = LTK_3BUTTONPRESS_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -201,6 +240,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
last_button_press[button] = xevent.xbutton.time;
next_event = (ltk_button_event){
.type = LTK_2BUTTONPRESS_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -213,6 +253,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}
*event = (ltk_event){.button = {
.type = LTK_BUTTONPRESS_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -223,6 +264,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
/* FIXME: compress multiple scroll events into one */
*event = (ltk_event){.scroll = {
.type = LTK_SCROLL_EVENT,
+ .window_id = window_id,
.x = xevent.xbutton.x,
.y = xevent.xbutton.y,
.dx = 0,
@@ -247,6 +289,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}
break;
case ButtonRelease:
+ ltk_assert(window_id < num_windows);
button = xevent.xbutton.button;
if (button >= 1 && button <= 3) {
if (xevent.xbutton.time - last_button_release[button] <= DOUBLECLICK_TIME &&
@@ -257,6 +300,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
last_button_release[button] = 0;
next_event = (ltk_button_event){
.type = LTK_3BUTTONRELEASE_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -266,6 +310,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
last_button_release[button] = xevent.xbutton.time;
next_event = (ltk_button_event){
.type = LTK_2BUTTONRELEASE_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -278,6 +323,7 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}
*event = (ltk_event){.button = {
.type = LTK_BUTTONRELEASE_EVENT,
+ .window_id = window_id,
.button = get_button(button),
.x = xevent.xbutton.x,
.y = xevent.xbutton.y
@@ -289,22 +335,30 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}
break;
case MotionNotify:
+ ltk_assert(window_id < num_windows);
/* FIXME: compress motion events */
*event = (ltk_event){.motion = {
.type = LTK_MOTION_EVENT,
+ .window_id = window_id,
.x = xevent.xmotion.x,
.y = xevent.xmotion.y
}};
break;
case KeyPress:
- *event = process_key(renderdata, lang_index, &xevent, LTK_KEYPRESS_EVENT);
+ ltk_assert(window_id < num_windows);
+ *event = process_key(windows[window_id], lang_index, &xevent, LTK_KEYPRESS_EVENT);
+ event->any.window_id = window_id;
break;
case KeyRelease:
- *event = process_key(renderdata, lang_index, &xevent, LTK_KEYRELEASE_EVENT);
+ ltk_assert(window_id < num_windows);
+ *event = process_key(windows[window_id], lang_index, &xevent, LTK_KEYRELEASE_EVENT);
+ event->any.window_id = window_id;
break;
case ConfigureNotify:
+ ltk_assert(window_id < num_windows);
*event = (ltk_event){.configure = {
.type = LTK_CONFIGURE_EVENT,
+ .window_id = window_id,
.x = xevent.xconfigure.x,
.y = xevent.xconfigure.y,
.w = xevent.xconfigure.width,
@@ -312,12 +366,14 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}};
break;
case Expose:
+ ltk_assert(window_id < num_windows);
/* FIXME: ignoring all of these events would make it not
work anymore if the actual rectangle wasn't ignored
later anyways */
if (xevent.xexpose.count == 0) {
*event = (ltk_event){.expose = {
.type = LTK_EXPOSE_EVENT,
+ .window_id = window_id,
.x = xevent.xexpose.x,
.y = xevent.xexpose.y,
.w = xevent.xexpose.width,
@@ -328,21 +384,24 @@ next_event_base(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_ind
}
break;
case ClientMessage:
+ ltk_assert(window_id < num_windows);
if ((Atom)xevent.xclient.data.l[0] == renderdata->wm_delete_msg)
- *event = (ltk_event){.type = LTK_WINDOWCLOSE_EVENT};
+ *event = (ltk_event){.any = {.type = LTK_WINDOWCLOSE_EVENT, .window_id = window_id}};
else
return 2;
break;
default:
break;
}
+ /* just in case... */
+ event->any.window_id = window_id;
return 0;
}
int
-ltk_next_event(ltk_renderdata *renderdata, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
+ltk_next_event(ltk_renderdata *renderdata, ltk_renderwindow **windows, size_t num_windows, ltk_clipboard *clip, size_t lang_index, ltk_event *event) {
int ret = 0;
- while ((ret = next_event_base(renderdata, clip, lang_index, event)) == 2) {
+ while ((ret = next_event_base(renderdata, windows, num_windows, clip, lang_index, event)) == 2) {
/* NOP */
}
return ret;
diff --git a/src/eventdefs.h b/src/eventdefs.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022-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_EVENTDEFS_H
#define LTK_EVENTDEFS_H
diff --git a/src/graphics.h b/src/graphics.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -17,18 +17,13 @@
#ifndef LTK_GRAPHICS_H
#define LTK_GRAPHICS_H
-/* FIXME: make this global and use it elsewhere */
-#define USE_XLIB_GRAPHICS 1
-
typedef struct ltk_renderdata ltk_renderdata;
+typedef struct ltk_renderwindow ltk_renderwindow;
-/* FIXME: Is it faster to take ltk_color* or ltk_color? */
+#include <stddef.h>
-#include <X11/Xft/Xft.h>
#include "rect.h"
#include "color.h"
-#include "ltk.h"
-#include "compat.h"
typedef enum {
LTK_BORDER_NONE = 0,
@@ -39,21 +34,21 @@ typedef enum {
LTK_BORDER_ALL = 0xF
} ltk_border_sides;
-/* typedef struct ltk_surface ltk_surface; */
+typedef struct ltk_surface ltk_surface;
/* FIXME: graphics context */
-ltk_surface *ltk_surface_create(ltk_renderdata *renderdata, int w, int h);
+ltk_surface *ltk_surface_create(ltk_renderwindow *window, int w, int h);
void ltk_surface_destroy(ltk_surface *s);
/* returns 0 if successful, 1 if not resizable */
int ltk_surface_resize(ltk_surface *s, int w, int h);
/* FIXME: kind of hacky */
void ltk_surface_update_size(ltk_surface *s, int w, int h);
-ltk_surface *ltk_surface_from_window(ltk_renderdata *renderdata, int w, int h);
+ltk_surface *ltk_surface_from_window(ltk_renderwindow *window, int w, int h);
void ltk_surface_get_size(ltk_surface *s, int *w, int *h);
void ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y);
void ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width);
void ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect);
-/* FIXME: document properly, especiall difference to draw_rect with offsets and line_width */
+/* FIXME: document properly, especially difference to draw_rect with offsets and line_width */
void ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides);
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);
@@ -67,21 +62,15 @@ void ltk_surface_draw_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r
void ltk_surface_fill_circle(ltk_surface *s, ltk_color *c, int xc, int yc, int r);
*/
-/* FIXME: only generate draw if needed */
-#if USE_XFT == 1
-XftDraw *ltk_surface_get_xft_draw(ltk_surface *s);
-#endif
-#if USE_XLIB_GRAPHICS == 1
-Drawable ltk_surface_get_drawable(ltk_surface *s);
-#endif
-
-void renderer_set_imspot(ltk_renderdata *renderdata, int x, int y);
-ltk_renderdata *renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h);
-void renderer_destroy_window(ltk_renderdata *renderdata);
-void renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg);
+void renderer_set_imspot(ltk_renderwindow *window, int x, int y);
+ltk_renderdata *renderer_create(void);
+ltk_renderwindow *renderer_create_window(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h);
+void renderer_destroy_window(ltk_renderwindow *window);
+void renderer_destroy(ltk_renderdata *data);
+void renderer_set_window_properties(ltk_renderwindow *window, ltk_color *bg);
/* FIXME: this is kind of out of place */
-void renderer_swap_buffers(ltk_renderdata *renderdata);
-/* FIXME: this is just for the socket name and is a bit weird */
-unsigned long renderer_get_window_id(ltk_renderdata *renderdata);
+void renderer_swap_buffers(ltk_renderwindow *window);
+/* FIXME: this is just for the socket name in ltkd and is a bit weird */
+unsigned long renderer_get_window_id(ltk_renderwindow *window);
#endif /* LTK_GRAPHICS_H */
diff --git a/src/graphics_xlib.c b/src/graphics_xlib.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -15,24 +15,24 @@
*/
#include <stdio.h>
-#include <stdint.h>
+#include <string.h>
+#include <X11/XKBlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#include <X11/extensions/XKB.h>
#include <X11/extensions/Xdbe.h>
-#include <X11/XKBlib.h>
-#include <X11/extensions/XKBrules.h>
+#include <X11/extensions/dbe.h>
+#include "graphics_xlib.h"
#include "color.h"
+#include "memory.h"
#include "rect.h"
#include "util.h"
-#include "memory.h"
-#include "compat.h"
-#include "xlib_shared.h"
struct ltk_surface {
int w, h;
- ltk_renderdata *renderdata;
+ ltk_renderwindow *window;
Drawable d;
#if USE_XFT == 1
XftDraw *xftdraw;
@@ -41,7 +41,7 @@ struct ltk_surface {
};
ltk_surface *
-ltk_surface_create(ltk_renderdata *renderdata, int w, int h) {
+ltk_surface_create(ltk_renderwindow *window, int w, int h) {
ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
if (w <= 0)
w = 1;
@@ -49,24 +49,24 @@ ltk_surface_create(ltk_renderdata *renderdata, int w, int h) {
h = 1;
s->w = w;
s->h = h;
- s->renderdata = renderdata;
- s->d = XCreatePixmap(renderdata->dpy, renderdata->xwindow, w, h, renderdata->depth);
+ s->window = window;
+ s->d = XCreatePixmap(window->renderdata->dpy, window->xwindow, w, h, window->renderdata->depth);
#if USE_XFT == 1
- s->xftdraw = XftDrawCreate(renderdata->dpy, s->d, renderdata->vis, renderdata->cm);
+ s->xftdraw = XftDrawCreate(window->renderdata->dpy, s->d, window->renderdata->vis, window->renderdata->cm);
#endif
s->resizable = 1;
return s;
}
ltk_surface *
-ltk_surface_from_window(ltk_renderdata *renderdata, int w, int h) {
+ltk_surface_from_window(ltk_renderwindow *window, int w, int h) {
ltk_surface *s = ltk_malloc(sizeof(ltk_surface));
s->w = w;
s->h = h;
- s->renderdata = renderdata;
- s->d = renderdata->drawable;
+ s->window = window;
+ s->d = window->drawable;
#if USE_XFT == 1
- s->xftdraw = XftDrawCreate(renderdata->dpy, s->d, renderdata->vis, renderdata->cm);
+ s->xftdraw = XftDrawCreate(window->renderdata->dpy, s->d, window->renderdata->vis, window->renderdata->cm);
#endif
s->resizable = 0;
return s;
@@ -78,7 +78,7 @@ ltk_surface_destroy(ltk_surface *s) {
XftDrawDestroy(s->xftdraw);
#endif
if (s->resizable)
- XFreePixmap(s->renderdata->dpy, (Pixmap)s->d);
+ XFreePixmap(s->window->renderdata->dpy, (Pixmap)s->d);
ltk_free(s);
}
@@ -87,6 +87,10 @@ ltk_surface_update_size(ltk_surface *s, int w, int h) {
/* FIXME: maybe return directly if surface is resizable? */
s->w = w;
s->h = h;
+ /* FIXME: sort of hacky (this is only used by window surface) */
+ #if USE_XFT == 1
+ XftDrawChange(s->xftdraw, s->d);
+ #endif
}
int
@@ -95,8 +99,8 @@ ltk_surface_resize(ltk_surface *s, int w, int h) {
return 1;
s->w = w;
s->h = h;
- XFreePixmap(s->renderdata->dpy, (Pixmap)s->d);
- s->d = XCreatePixmap(s->renderdata->dpy, s->renderdata->xwindow, w, h, s->renderdata->depth);
+ XFreePixmap(s->window->renderdata->dpy, (Pixmap)s->d);
+ s->d = XCreatePixmap(s->window->renderdata->dpy, s->window->xwindow, w, h, s->window->renderdata->depth);
#if USE_XFT == 1
XftDrawChange(s->xftdraw, s->d);
#endif
@@ -111,67 +115,67 @@ ltk_surface_get_size(ltk_surface *s, int *w, int *h) {
void
ltk_surface_draw_rect(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width) {
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
- XSetLineAttributes(s->renderdata->dpy, s->renderdata->gc, line_width, LineSolid, CapButt, JoinMiter);
- XDrawRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, rect.h);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
+ XSetLineAttributes(s->window->renderdata->dpy, s->window->gc, line_width, LineSolid, CapButt, JoinMiter);
+ XDrawRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h);
}
void
ltk_surface_draw_border(ltk_surface *s, ltk_color *c, ltk_rect rect, int line_width, ltk_border_sides border_sides) {
/* drawn as rectangles to have proper control over line width - I'm not sure how exactly
XDrawLine handles even line widths (i.e. on which side the extra pixel will be) */
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
if (border_sides & LTK_BORDER_TOP)
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, line_width);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, line_width);
if (border_sides & LTK_BORDER_BOTTOM)
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y + rect.h - line_width, rect.w, line_width);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y + rect.h - line_width, rect.w, line_width);
if (border_sides & LTK_BORDER_LEFT)
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, line_width, rect.h);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, line_width, rect.h);
if (border_sides & LTK_BORDER_RIGHT)
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x + rect.w - line_width, rect.y, line_width, rect.h);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x + rect.w - line_width, rect.y, line_width, rect.h);
}
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) {
if (line_width <= 0)
return;
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
int width;
ltk_rect final_rect = ltk_rect_intersect(rect, clip_rect);
if (border_sides & LTK_BORDER_TOP) {
width = rect.y - final_rect.y;
if (width > -line_width) {
width = line_width + width;
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, final_rect.w, width);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, final_rect.x, final_rect.y, final_rect.w, width);
}
}
if (border_sides & LTK_BORDER_BOTTOM) {
width = (final_rect.y + final_rect.h) - (rect.y + rect.h);
if (width > -line_width) {
width = line_width + width;
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y + final_rect.h - width, final_rect.w, width);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, final_rect.x, final_rect.y + final_rect.h - width, final_rect.w, width);
}
}
if (border_sides & LTK_BORDER_LEFT) {
width = rect.x - final_rect.x;
if (width > -line_width) {
width = line_width + width;
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x, final_rect.y, width, final_rect.h);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, final_rect.x, final_rect.y, width, final_rect.h);
}
}
if (border_sides & LTK_BORDER_RIGHT) {
width = (final_rect.x + final_rect.w) - (rect.x + rect.w);
if (width > -line_width) {
width = line_width + width;
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, final_rect.x + final_rect.w - width, final_rect.y, width, final_rect.h);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, final_rect.x + final_rect.w - width, final_rect.y, width, final_rect.h);
}
}
}
void
ltk_surface_fill_rect(ltk_surface *s, ltk_color *c, ltk_rect rect) {
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
- XFillRectangle(s->renderdata->dpy, s->d, s->renderdata->gc, rect.x, rect.y, rect.w, rect.h);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
+ XFillRectangle(s->window->renderdata->dpy, s->d, s->window->gc, rect.x, rect.y, rect.w, rect.h);
}
void
@@ -190,8 +194,8 @@ ltk_surface_fill_polygon(ltk_surface *s, ltk_color *c, ltk_point *points, size_t
final_points[i].x = (short)points[i].x;
final_points[i].y = (short)points[i].y;
}
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
- XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, final_points, (int)npoints, Complex, CoordModeOrigin);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
+ XFillPolygon(s->window->renderdata->dpy, s->d, s->window->gc, final_points, (int)npoints, Complex, CoordModeOrigin);
if (npoints > 6)
ltk_free(final_points);
}
@@ -205,6 +209,7 @@ swap_ptr(void **ptr1, void **ptr2) {
#define check_size(cond) if (!(cond)) ltk_fatal("Unable to perform polygon clipping. This is a bug, tell lumidify about it.\n")
+/* FIXME: xlib already includes clipping... */
/* FIXME: this can probably be optimized */
/* This is basically Sutherland-Hodgman, but only the special case for clipping rectangles. */
void
@@ -307,8 +312,8 @@ ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points
}
if (num2 > 0) {
- XSetForeground(s->renderdata->dpy, s->renderdata->gc, c->xcolor.pixel);
- XFillPolygon(s->renderdata->dpy, s->d, s->renderdata->gc, points2, (int)num2, Complex, CoordModeOrigin);
+ XSetForeground(s->window->renderdata->dpy, s->window->gc, c->xcolor.pixel);
+ XFillPolygon(s->window->renderdata->dpy, s->d, s->window->gc, points2, (int)num2, Complex, CoordModeOrigin);
}
if (npoints > 6) {
ltk_free(points1);
@@ -319,7 +324,7 @@ ltk_surface_fill_polygon_clipped(ltk_surface *s, ltk_color *c, ltk_point *points
void
ltk_surface_copy(ltk_surface *src, ltk_surface *dst, ltk_rect src_rect, int dst_x, int dst_y) {
XCopyArea(
- src->renderdata->dpy, src->d, dst->d, src->renderdata->gc,
+ src->window->renderdata->dpy, src->d, dst->d, src->window->gc,
src_rect.x, src_rect.y, src_rect.w, src_rect.h, dst_x, dst_y
);
}
@@ -360,52 +365,52 @@ ltk_surface_get_drawable(ltk_surface *s) {
static void ximinstantiate(Display *dpy, XPointer client, XPointer call);
static void ximdestroy(XIM xim, XPointer client, XPointer call);
static int xicdestroy(XIC xim, XPointer client, XPointer call);
-static int ximopen(ltk_renderdata *renderdata, Display *dpy);
+static int ximopen(ltk_renderwindow *window);
static void
ximdestroy(XIM xim, XPointer client, XPointer call) {
(void)xim;
(void)call;
- ltk_renderdata *renderdata = (ltk_renderdata *)client;
- renderdata->xim = NULL;
+ ltk_renderwindow *window = (ltk_renderwindow *)client;
+ window->xim = NULL;
XRegisterIMInstantiateCallback(
- renderdata->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata
+ window->renderdata->dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
);
- XFree(renderdata->spotlist);
+ XFree(window->spotlist);
}
static int
xicdestroy(XIC xim, XPointer client, XPointer call) {
(void)xim;
(void)call;
- ltk_renderdata *renderdata = (ltk_renderdata *)client;
- renderdata->xic = NULL;
+ ltk_renderwindow *window = (ltk_renderwindow *)client;
+ window->xic = NULL;
return 1;
}
static int
-ximopen(ltk_renderdata *renderdata, Display *dpy) {
- XIMCallback imdestroy = { .client_data = (XPointer)renderdata, .callback = ximdestroy };
- XICCallback icdestroy = { .client_data = (XPointer)renderdata, .callback = xicdestroy };
+ximopen(ltk_renderwindow *window) {
+ XIMCallback imdestroy = { .client_data = (XPointer)window, .callback = ximdestroy };
+ XICCallback icdestroy = { .client_data = (XPointer)window, .callback = xicdestroy };
- renderdata->xim = XOpenIM(dpy, NULL, NULL, NULL);
- if (renderdata->xim == NULL)
+ window->xim = XOpenIM(window->renderdata->dpy, NULL, NULL, NULL);
+ if (window->xim == NULL)
return 0;
- if (XSetIMValues(renderdata->xim, XNDestroyCallback, &imdestroy, NULL))
+ if (XSetIMValues(window->xim, XNDestroyCallback, &imdestroy, NULL))
ltk_warn("XSetIMValues: Could not set XNDestroyCallback.\n");
- renderdata->spotlist = XVaCreateNestedList(0, XNSpotLocation, &renderdata->spot, NULL);
+ window->spotlist = XVaCreateNestedList(0, XNSpotLocation, &window->spot, NULL);
- if (renderdata->xic == NULL) {
- renderdata->xic = XCreateIC(
- renderdata->xim, XNInputStyle,
+ if (window->xic == NULL) {
+ window->xic = XCreateIC(
+ window->xim, XNInputStyle,
XIMPreeditNothing | XIMStatusNothing,
- XNClientWindow, renderdata->xwindow,
+ XNClientWindow, window->xwindow,
XNDestroyCallback, &icdestroy, NULL
);
}
- if (renderdata->xic == NULL)
+ if (window->xic == NULL)
ltk_warn("XCreateIC: Could not create input context.\n");
return 1;
@@ -414,33 +419,33 @@ ximopen(ltk_renderdata *renderdata, Display *dpy) {
static void
ximinstantiate(Display *dpy, XPointer client, XPointer call) {
(void)call;
- ltk_renderdata *renderdata = (ltk_renderdata *)client;
- if (ximopen(renderdata, dpy)) {
+ ltk_renderwindow *window = (ltk_renderwindow *)client;
+ if (ximopen(window)) {
XUnregisterIMInstantiateCallback(
- dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)renderdata
+ dpy, NULL, NULL, NULL, ximinstantiate, (XPointer)window
);
}
}
void
-renderer_set_imspot(ltk_renderdata *renderdata, int x, int y) {
- if (renderdata->xic == NULL)
+renderer_set_imspot(ltk_renderwindow *window, int x, int y) {
+ if (window->xic == NULL)
return;
- /* FIXME! */
- renderdata->spot.x = x;
- renderdata->spot.y = y;
- XSetICValues(renderdata->xic, XNPreeditAttributes, renderdata->spotlist, NULL);
+ window->spot.x = x;
+ window->spot.y = y;
+ XSetICValues(window->xic, XNPreeditAttributes, window->spotlist, NULL);
}
ltk_renderdata *
-renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
- XSetWindowAttributes attrs;
+renderer_create(void) {
+ /* FIXME: this might not be the best place for this */
+ XSetLocaleModifiers("");
ltk_renderdata *renderdata = ltk_malloc(sizeof(ltk_renderdata));
-
renderdata->dpy = XOpenDisplay(NULL);
renderdata->screen = DefaultScreen(renderdata->dpy);
+ renderdata->db_enabled = 0;
/* based on http://wili.cc/blog/xdbe.html */
- int major, minor, found = 0;
+ int major, minor;
if (XdbeQueryExtension(renderdata->dpy, &major, &minor)) {
int num_screens = 1;
Drawable screens[] = {DefaultRootWindow(renderdata->dpy)};
@@ -469,18 +474,46 @@ renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned
/* FIXME: is it legal to free this while keeping the visual? */
XFree(xvisinfo_match);
XdbeFreeVisualInfo(info);
- found = 1;
+ renderdata->db_enabled = 1;
} else {
renderdata->vis = DefaultVisual(renderdata->dpy, renderdata->screen);
ltk_warn("No Xdbe support.\n");
}
renderdata->cm = DefaultColormap(renderdata->dpy, renderdata->screen);
renderdata->wm_delete_msg = XInternAtom(renderdata->dpy, "WM_DELETE_WINDOW", False);
+ renderdata->depth = DefaultDepth(renderdata->dpy, renderdata->screen);
+ renderdata->xkb_supported = 1;
+ renderdata->xkb_event_type = 0;
+ if (!XkbQueryExtension(renderdata->dpy, 0, &renderdata->xkb_event_type, NULL, &major, &minor)) {
+ ltk_warn("XKB not supported.\n");
+ renderdata->xkb_supported = 0;
+ } else {
+ /* This should select the events when the keyboard mapping changes.
+ * When e.g. 'setxkbmap us' is executed, two events are sent, but I
+ * haven't figured out how to change that. When the xkb layout
+ * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
+ * this issue does not occur because only a state event is sent. */
+ XkbSelectEvents(
+ renderdata->dpy, XkbUseCoreKbd,
+ XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask
+ );
+ XkbSelectEventDetails(
+ renderdata->dpy, XkbUseCoreKbd, XkbStateNotify,
+ XkbAllStateComponentsMask, XkbGroupStateMask
+ );
+ }
+ return renderdata;
+}
+ltk_renderwindow *
+renderer_create_window(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h) {
+ XSetWindowAttributes attrs;
+ ltk_renderwindow *window = ltk_malloc(sizeof(ltk_renderwindow));
+ window->renderdata = data;
memset(&attrs, 0, sizeof(attrs));
- attrs.background_pixel = BlackPixel(renderdata->dpy, renderdata->screen);
- attrs.colormap = renderdata->cm;
- attrs.border_pixel = WhitePixel(renderdata->dpy, renderdata->screen);
+ attrs.background_pixel = BlackPixel(data->dpy, data->screen);
+ attrs.colormap = data->cm;
+ attrs.border_pixel = WhitePixel(data->dpy, data->screen);
/* this causes the window contents to be kept
* when it is resized, leading to less flicker */
attrs.bit_gravity = NorthWestGravity;
@@ -488,94 +521,80 @@ renderer_create_window(const char *title, int x, int y, unsigned int w, unsigned
ExposureMask | KeyPressMask | KeyReleaseMask |
ButtonPressMask | ButtonReleaseMask |
StructureNotifyMask | PointerMotionMask;
- renderdata->depth = DefaultDepth(renderdata->dpy, renderdata->screen);
/* FIXME: set border width */
- renderdata->xwindow = XCreateWindow(
- renderdata->dpy, DefaultRootWindow(renderdata->dpy), x, y,
- w, h, 0, renderdata->depth,
- InputOutput, renderdata->vis,
+ window->xwindow = XCreateWindow(
+ data->dpy, DefaultRootWindow(data->dpy), x, y,
+ w, h, 0, data->depth,
+ InputOutput, data->vis,
CWBackPixel | CWColormap | CWBitGravity | CWEventMask | CWBorderPixel, &attrs
);
- if (found) {
- renderdata->back_buf = XdbeAllocateBackBufferName(
- renderdata->dpy, renderdata->xwindow, XdbeBackground
+ if (data->db_enabled) {
+ window->back_buf = XdbeAllocateBackBufferName(
+ data->dpy, window->xwindow, XdbeBackground
);
} else {
- renderdata->back_buf = renderdata->xwindow;
+ window->back_buf = window->xwindow;
}
- renderdata->drawable = renderdata->back_buf;
- renderdata->gc = XCreateGC(renderdata->dpy, renderdata->xwindow, 0, 0);
+ window->drawable = window->back_buf;
+ window->gc = XCreateGC(data->dpy, window->xwindow, 0, 0);
XSetStandardProperties(
- renderdata->dpy, renderdata->xwindow,
+ data->dpy, window->xwindow,
title, NULL, None, NULL, 0, NULL
);
- XSetWMProtocols(renderdata->dpy, renderdata->xwindow, &renderdata->wm_delete_msg, 1);
+ /* FIXME: check return value */
+ XSetWMProtocols(data->dpy, window->xwindow, &data->wm_delete_msg, 1);
- renderdata->xim = NULL;
- renderdata->xic = NULL;
- if (!ximopen(renderdata, renderdata->dpy)) {
+ window->xic = NULL;
+ window->xim = NULL;
+ if (!ximopen(window)) {
XRegisterIMInstantiateCallback(
- renderdata->dpy, NULL, NULL, NULL,
- ximinstantiate, (XPointer)renderdata
+ window->renderdata->dpy, NULL, NULL, NULL,
+ ximinstantiate, (XPointer)window
);
}
- XClearWindow(renderdata->dpy, renderdata->xwindow);
- XMapRaised(renderdata->dpy, renderdata->xwindow);
+ XClearWindow(window->renderdata->dpy, window->xwindow);
+ XMapRaised(window->renderdata->dpy, window->xwindow);
- renderdata->xkb_supported = 1;
- renderdata->xkb_event_type = 0;
- if (!XkbQueryExtension(renderdata->dpy, 0, &renderdata->xkb_event_type, NULL, &major, &minor)) {
- ltk_warn("XKB not supported.\n");
- renderdata->xkb_supported = 0;
- } else {
- /* This should select the events when the keyboard mapping changes.
- * When e.g. 'setxkbmap us' is executed, two events are sent, but I
- * haven't figured out how to change that. When the xkb layout
- * switching is used (e.g. 'setxkbmap -option grp:shifts_toggle'),
- * this issue does not occur because only a state event is sent. */
- XkbSelectEvents(
- renderdata->dpy, XkbUseCoreKbd,
- XkbNewKeyboardNotifyMask, XkbNewKeyboardNotifyMask
- );
- XkbSelectEventDetails(
- renderdata->dpy, XkbUseCoreKbd, XkbStateNotify,
- XkbAllStateComponentsMask, XkbGroupStateMask
- );
- }
+ return window;
+}
- return renderdata;
+void
+renderer_destroy_window(ltk_renderwindow *window) {
+ XFreeGC(window->renderdata->dpy, window->gc);
+ if (window->spotlist)
+ XFree(window->spotlist);
+ /* FIXME: destroy xim/xic? */
+ XDestroyWindow(window->renderdata->dpy, window->xwindow);
+ ltk_free(window);
}
void
-renderer_destroy_window(ltk_renderdata *renderdata) {
- XFreeGC(renderdata->dpy, renderdata->gc);
- if (renderdata->spotlist)
- XFree(renderdata->spotlist);
- XDestroyWindow(renderdata->dpy, renderdata->xwindow);
+renderer_destroy(ltk_renderdata *renderdata) {
XCloseDisplay(renderdata->dpy);
+ /* FIXME: destroy visual, wm_delete_msg, etc.? */
ltk_free(renderdata);
}
/* FIXME: this is a completely random collection of properties and should be
changed to a more sensible list */
void
-renderer_set_window_properties(ltk_renderdata *renderdata, ltk_color *bg) {
- XSetWindowBackground(renderdata->dpy, renderdata->xwindow, bg->xcolor.pixel);
+renderer_set_window_properties(ltk_renderwindow *window, ltk_color *bg) {
+ XSetWindowBackground(window->renderdata->dpy, window->xwindow, bg->xcolor.pixel);
}
void
-renderer_swap_buffers(ltk_renderdata *renderdata) {
+renderer_swap_buffers(ltk_renderwindow *window) {
XdbeSwapInfo swap_info;
- swap_info.swap_window = renderdata->xwindow;
+ swap_info.swap_window = window->xwindow;
swap_info.swap_action = XdbeBackground;
- if (!XdbeSwapBuffers(renderdata->dpy, &swap_info, 1))
+ if (!XdbeSwapBuffers(window->renderdata->dpy, &swap_info, 1))
ltk_fatal("Unable to swap buffers.\n");
- XFlush(renderdata->dpy);
+ XFlush(window->renderdata->dpy);
}
unsigned long
-renderer_get_window_id(ltk_renderdata *renderdata) {
- return (unsigned long)renderdata->xwindow;
+renderer_get_window_id(ltk_renderwindow *window) {
+ return (unsigned long)window->xwindow;
}
diff --git a/src/graphics_xlib.h b/src/graphics_xlib.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2022-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_GRAPHICS_XLIB_H
+#define LTK_GRAPHICS_XLIB_H
+
+/* FIXME: make this a setting somewhere sensible */
+#define USE_XLIB_GRAPHICS 1
+
+#if USE_PANGO == 1
+#undef USE_XFT
+#define USE_XFT 1
+#endif
+
+#include <X11/Xlib.h>
+#include <X11/extensions/Xdbe.h>
+
+#include "graphics.h"
+
+#if USE_XFT == 1
+ #include <X11/Xft/Xft.h>
+#endif
+
+/* FIXME: figure out which of these items might make more
+ sense in ltk_renderwindow */
+struct ltk_renderdata {
+ Display *dpy;
+ Visual *vis;
+ Colormap cm;
+ Atom wm_delete_msg;
+ int screen;
+ int depth;
+ /* double buffering enabled */
+ int db_enabled;
+ int xkb_event_type;
+ int xkb_supported;
+};
+
+struct ltk_renderwindow {
+ struct ltk_renderdata *renderdata;
+ GC gc;
+ Window xwindow;
+ XdbeBackBuffer back_buf;
+ Drawable drawable;
+ XIM xim;
+ XIC xic;
+ XPoint spot;
+ XVaNestedList spotlist;
+};
+
+/* FIXME: only generate draw if needed */
+#if USE_XFT == 1
+#include <X11/Xft/Xft.h>
+XftDraw *ltk_surface_get_xft_draw(ltk_surface *s);
+#endif
+
+#if USE_XLIB_GRAPHICS == 1
+#include <X11/X.h>
+Drawable ltk_surface_get_drawable(ltk_surface *s);
+#endif
+
+struct ltk_color {
+ XColor xcolor;
+ #if USE_XFT == 1
+ XftColor xftcolor;
+ #endif
+};
+
+#endif /* LTK_GRAPHICS_XLIB_H */
diff --git a/src/grid.c b/src/grid.c
@@ -1,7 +1,7 @@
/* FIXME: sometimes, resizing doesn't work properly when running test.sh */
/*
- * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016-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
@@ -23,33 +23,32 @@
what should happen if some rows/columns under the span do have a
positive weight? */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stdint.h>
+#include <stddef.h>
+#include <limits.h>
-#include "event.h"
#include "memory.h"
-#include "color.h"
#include "rect.h"
#include "widget.h"
-#include "ltk.h"
#include "util.h"
#include "grid.h"
-#include "cmd.h"
+#include "graphics.h"
+
+void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
+void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
+ltk_grid *ltk_grid_create(ltk_window *window, int rows, int columns);
+int ltk_grid_add(
+ ltk_grid *grid, ltk_widget *widget,
+ int row, int column, int row_span, int column_span,
+ ltk_sticky_mask sticky
+);
+/* just a wrapper around ltk_grid_remove to make types match */
+static int ltk_grid_remove_child(ltk_widget *self, ltk_widget *widget);
+int ltk_grid_remove(ltk_grid *grid, ltk_widget *widget);
-static void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
-static void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
static void ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
-static ltk_grid *ltk_grid_create(ltk_window *window, const char *id,
- int rows, int columns);
static void ltk_grid_destroy(ltk_widget *self, int shallow);
static void ltk_recalculate_grid(ltk_widget *self);
static void ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget);
-static int ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
- int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err);
-static int ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err);
static int ltk_grid_find_nearest_column(ltk_grid *grid, int x);
static int ltk_grid_find_nearest_row(ltk_grid *grid, int y);
static ltk_widget *ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y);
@@ -72,7 +71,7 @@ static struct ltk_widget_vtable vtable = {
.hide = NULL,
.change_state = NULL,
.child_size_change = <k_grid_child_size_change,
- .remove_child = <k_grid_ungrid,
+ .remove_child = <k_grid_remove_child,
.mouse_press = NULL,
.mouse_scroll = NULL,
.mouse_release = NULL,
@@ -93,54 +92,28 @@ static struct ltk_widget_vtable vtable = {
.nearest_child_below = <k_grid_nearest_child_below,
.type = LTK_WIDGET_GRID,
.flags = 0,
+ .invalid_signal = LTK_GRID_SIGNAL_INVALID,
};
-static int ltk_grid_cmd_add(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-static int ltk_grid_cmd_ungrid(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-static int ltk_grid_cmd_create(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-static int ltk_grid_cmd_set_row_weight(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-static int ltk_grid_cmd_set_column_weight(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err);
-
-static void
+/* FIXME: only set "dirty" bit to avoid constand recalculation when
+ setting multiple row/column weights? */
+void
ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight) {
+ ltk_assert(row < grid->rows);
grid->row_weights[row] = weight;
- ltk_recalculate_grid((ltk_widget *)grid);
+ ltk_recalculate_grid(LTK_CAST_WIDGET(grid));
}
-static void
+void
ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight) {
+ ltk_assert(column < grid->columns);
grid->column_weights[column] = weight;
- ltk_recalculate_grid((ltk_widget *)grid);
+ ltk_recalculate_grid(LTK_CAST_WIDGET(grid));
}
static void
ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
int i;
ltk_rect real_clip = ltk_rect_intersect((ltk_rect){0, 0, self->lrect.w, self->lrect.h}, clip);
for (i = 0; i < grid->rows * grid->columns; i++) {
@@ -152,15 +125,16 @@ ltk_grid_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
ltk_rect r = ltk_rect_intersect(
(ltk_rect){grid->column_pos[ptr->column], grid->row_pos[ptr->row], max_w, max_h}, real_clip
);
- ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r));
+ if (ptr->vtable->draw)
+ ptr->vtable->draw(ptr, s, x + ptr->lrect.x, y + ptr->lrect.y, ltk_rect_relative(ptr->lrect, r));
}
}
-static ltk_grid *
-ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
+ltk_grid *
+ltk_grid_create(ltk_window *window, int rows, int columns) {
ltk_grid *grid = ltk_malloc(sizeof(ltk_grid));
- ltk_fill_widget_defaults(&grid->widget, id, window, &vtable, 0, 0);
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(grid), window, &vtable, 0, 0);
grid->rows = rows;
grid->columns = columns;
@@ -190,15 +164,14 @@ ltk_grid_create(ltk_window *window, const char *id, int rows, int columns) {
grid->widget_grid[i] = NULL;
}
- ltk_recalculate_grid((ltk_widget *)grid);
+ ltk_recalculate_grid(LTK_CAST_WIDGET(grid));
return grid;
}
static void
ltk_grid_destroy(ltk_widget *self, int shallow) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
ltk_widget *ptr;
- ltk_error err;
for (int i = 0; i < grid->rows * grid->columns; i++) {
if (grid->widget_grid[i]) {
ptr = grid->widget_grid[i];
@@ -211,7 +184,7 @@ ltk_grid_destroy(ltk_widget *self, int shallow) {
grid->widget_grid[r * grid->columns + c] = NULL;
}
}
- ltk_widget_destroy(ptr, shallow, &err);
+ ltk_widget_destroy(ptr, shallow);
}
}
}
@@ -227,7 +200,7 @@ ltk_grid_destroy(ltk_widget *self, int shallow) {
static void
ltk_recalculate_grid(ltk_widget *self) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
unsigned int height_static = 0, width_static = 0;
unsigned int total_row_weight = 0, total_column_weight = 0;
float height_unit = 0, width_unit = 0;
@@ -247,10 +220,10 @@ ltk_recalculate_grid(ltk_widget *self) {
}
/* FIXME: what should be done when static height or width is larger than grid? */
if (total_row_weight > 0) {
- height_unit = (float) (grid->widget.lrect.h - height_static) / (float) total_row_weight;
+ height_unit = (float) (self->lrect.h - height_static) / (float) total_row_weight;
}
if (total_column_weight > 0) {
- width_unit = (float) (grid->widget.lrect.w - width_static) / (float) total_column_weight;
+ width_unit = (float) (self->lrect.w - width_static) / (float) total_column_weight;
}
for (i = 0; i < grid->rows; i++) {
grid->row_pos[i] = currenty;
@@ -344,7 +317,7 @@ ltk_recalculate_grid(ltk_widget *self) {
/* FIXME: Maybe add debug stuff to check that grid is actually parent of widget */
static void
ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
short size_changed = 0;
int orig_w = widget->lrect.w;
int orig_h = widget->lrect.h;
@@ -352,36 +325,38 @@ ltk_grid_child_size_change(ltk_widget *self, ltk_widget *widget) {
widget->lrect.h = widget->ideal_h;
if (grid->column_weights[widget->column] == 0 &&
widget->lrect.w > grid->column_widths[widget->column]) {
- grid->widget.ideal_w += widget->lrect.w - grid->column_widths[widget->column];
+ self->ideal_w += widget->lrect.w - grid->column_widths[widget->column];
grid->column_widths[widget->column] = widget->lrect.w;
size_changed = 1;
}
if (grid->row_weights[widget->row] == 0 &&
widget->lrect.h > grid->row_heights[widget->row]) {
- grid->widget.ideal_h += widget->lrect.h - grid->row_heights[widget->row];
+ self->ideal_h += widget->lrect.h - grid->row_heights[widget->row];
grid->row_heights[widget->row] = widget->lrect.h;
size_changed = 1;
}
- if (size_changed && grid->widget.parent && grid->widget.parent->vtable->child_size_change)
- grid->widget.parent->vtable->child_size_change(grid->widget.parent, (ltk_widget *)grid);
+ if (size_changed && self->parent && self->parent->vtable->child_size_change)
+ self->parent->vtable->child_size_change(self->parent, LTK_CAST_WIDGET(grid));
else
- ltk_recalculate_grid((ltk_widget *)grid);
+ ltk_recalculate_grid(LTK_CAST_WIDGET(grid));
if (widget->lrect.w != orig_w || widget->lrect.h != orig_h)
ltk_widget_resize(widget);
}
/* FIXME: Check if widget already exists at position */
-static int
-ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
- int row, int column, int row_span, int column_span, ltk_sticky_mask sticky, ltk_error *err) {
- if (widget->parent) {
- err->type = ERR_WIDGET_IN_CONTAINER;
+int
+ltk_grid_add(
+ ltk_grid *grid, ltk_widget *widget,
+ int row, int column, int row_span, int column_span,
+ ltk_sticky_mask sticky
+) {
+ if (widget->parent)
return 1;
- }
- if (row + row_span > grid->rows || column + column_span > grid->columns) {
- err->type = ERR_GRID_INVALID_POSITION;
- return 1;
- }
+ /* FIXME: decide which checks should be asserts and which should be error returns */
+ /* the client-server version of ltk shouldn't abort on errors like these */
+ ltk_assert(row >= 0 && row + row_span <= grid->rows);
+ ltk_assert(column >= 0 && column + column_span <= grid->columns);
+
widget->sticky = sticky;
widget->row = row;
widget->column = column;
@@ -392,32 +367,34 @@ ltk_grid_add(ltk_window *window, ltk_widget *widget, ltk_grid *grid,
grid->widget_grid[i * grid->columns + j] = widget;
}
}
- widget->parent = (ltk_widget *)grid;
- ltk_grid_child_size_change((ltk_widget *)grid, widget);
- ltk_window_invalidate_widget_rect(window, &grid->widget);
+ widget->parent = LTK_CAST_WIDGET(grid);
+ ltk_grid_child_size_change(LTK_CAST_WIDGET(grid), widget);
+ ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(grid)->window, LTK_CAST_WIDGET(grid));
return 0;
}
-static int
-ltk_grid_ungrid(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
- ltk_grid *grid = (ltk_grid *)self;
- if (widget->parent != (ltk_widget *)grid) {
- err->type = ERR_WIDGET_NOT_IN_CONTAINER;
+int
+ltk_grid_remove(ltk_grid *grid, ltk_widget *widget) {
+ if (widget->parent != LTK_CAST_WIDGET(grid))
return 1;
- }
widget->parent = NULL;
for (int i = widget->row; i < widget->row + widget->row_span; i++) {
for (int j = widget->column; j < widget->column + widget->column_span; j++) {
grid->widget_grid[i * grid->columns + j] = NULL;
}
}
- ltk_window_invalidate_widget_rect(self->window, &grid->widget);
+ ltk_window_invalidate_widget_rect(LTK_CAST_WIDGET(grid)->window, LTK_CAST_WIDGET(grid));
return 0;
}
static int
+ltk_grid_remove_child(ltk_widget *self, ltk_widget *widget) {
+ return ltk_grid_remove(LTK_CAST_GRID(self), widget);
+}
+
+static int
ltk_grid_find_nearest_column(ltk_grid *grid, int x) {
int i;
for (i = 0; i < grid->columns; i++) {
@@ -442,20 +419,16 @@ ltk_grid_find_nearest_row(ltk_grid *grid, int y) {
/* FIXME: maybe come up with a more efficient method */
static ltk_widget *
ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
ltk_widget *minw = NULL;
int min_dist = INT_MAX;
- int cx = rect.x + rect.w / 2;
- int cy = rect.y + rect.h / 2;
- ltk_rect r;
- int dist;
/* FIXME: rows and columns shouldn't be int */
for (size_t i = 0; i < (size_t)(grid->rows * grid->columns); i++) {
if (!grid->widget_grid[i])
continue;
/* FIXME: this checks widgets with row/columnspan > 1 multiple times */
- r = grid->widget_grid[i]->lrect;
- dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy);
+ ltk_rect r = grid->widget_grid[i]->lrect;
+ int dist = ltk_rect_fakedist(rect, r);
if (dist < min_dist) {
min_dist = dist;
minw = grid->widget_grid[i];
@@ -467,7 +440,7 @@ ltk_grid_nearest_child(ltk_widget *self, ltk_rect rect) {
/* FIXME: assertions to check that widget row/column are legal */
static ltk_widget *
ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
unsigned int col = widget->column;
ltk_widget *cur = NULL;
while (col-- > 0) {
@@ -480,7 +453,7 @@ ltk_grid_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
ltk_widget *cur = NULL;
for (int col = widget->column + 1; col < grid->columns; col++) {
cur = grid->widget_grid[widget->row * grid->columns + col];
@@ -490,9 +463,11 @@ ltk_grid_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
return NULL;
}
+/* FIXME: maybe these should also fall back to widgets in other columns if those
+ exist but no widgets exist in the same column */
static ltk_widget *
ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
unsigned int row = widget->row;
ltk_widget *cur = NULL;
while (row-- > 0) {
@@ -505,7 +480,7 @@ ltk_grid_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
ltk_widget *cur = NULL;
for (int row = widget->row + 1; row < grid->rows; row++) {
cur = grid->widget_grid[row * grid->columns + widget->column];
@@ -517,7 +492,7 @@ ltk_grid_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
int row = ltk_grid_find_nearest_row(grid, y);
int column = ltk_grid_find_nearest_column(grid, x);
if (row == -1 || column == -1)
@@ -530,7 +505,7 @@ ltk_grid_get_child_at_pos(ltk_widget *self, int x, int y) {
static ltk_widget *
ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
unsigned int start = child->row * grid->columns + child->column;
while (start-- > 0) {
if (grid->widget_grid[start])
@@ -541,7 +516,7 @@ ltk_grid_prev_child(ltk_widget *self, ltk_widget *child) {
static ltk_widget *
ltk_grid_next_child(ltk_widget *self, ltk_widget *child) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
unsigned int start = child->row * grid->columns + child->column;
while (++start < (unsigned int)(grid->rows * grid->columns)) {
if (grid->widget_grid[start] && grid->widget_grid[start] != child)
@@ -552,7 +527,7 @@ ltk_grid_next_child(ltk_widget *self, ltk_widget *child) {
static ltk_widget *
ltk_grid_first_child(ltk_widget *self) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
for (unsigned int i = 0; i < (unsigned int)(grid->rows * grid->columns); i++) {
if (grid->widget_grid[i])
return grid->widget_grid[i];
@@ -562,147 +537,10 @@ ltk_grid_first_child(ltk_widget *self) {
static ltk_widget *
ltk_grid_last_child(ltk_widget *self) {
- ltk_grid *grid = (ltk_grid *)self;
+ ltk_grid *grid = LTK_CAST_GRID(self);
for (unsigned int i = grid->rows * grid->columns; i-- > 0;) {
if (grid->widget_grid[i])
return grid->widget_grid[i];
}
return NULL;
}
-
-/* grid <grid id> add <widget id> <row> <column> <row_span> <column_span> [sticky] */
-static int
-ltk_grid_cmd_add(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = grid->rows, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = grid->columns, .optional = 0},
- {.type = CMDARG_STICKY, .optional = 1},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- /* FIXME: better error reporting for invalid grid position */
- /* FIXME: check if recalculation deals properly with rowspan/columnspan
- that goes over the edge of the grid */
- if (ltk_grid_add(
- window, cmd[0].val.widget, grid,
- cmd[1].val.i, cmd[2].val.i, cmd[3].val.i, cmd[4].val.i,
- cmd[5].initialized ? cmd[5].val.sticky : 0, err)) {
- err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : -1;
- return 1;
- }
- return 0;
-}
-
-/* grid <grid id> remove <widget id> */
-static int
-ltk_grid_cmd_ungrid(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_ANY, .optional = 0}
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_grid_ungrid(cmd[0].val.widget, (ltk_widget *)grid, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* FIXME: max size of 64 is completely arbitrary! */
-/* grid <grid id> create <rows> <columns> */
-static int
-ltk_grid_cmd_create(
- ltk_window *window,
- ltk_grid *grid_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)grid_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_grid *grid = ltk_grid_create(window, cmd[1].val.str, cmd[3].val.i, cmd[4].val.i);
- ltk_set_widget((ltk_widget *)grid, cmd[1].val.str);
-
- return 0;
-}
-
-/* FIXME: 64 is completely arbitrary */
-/* grid <grid id> set-row-weight <row> <weight> */
-static int
-ltk_grid_cmd_set_row_weight(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_INT, .min = 0, .max = grid->rows - 1, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- ltk_grid_set_row_weight(grid, cmd[0].val.i, cmd[1].val.i);
-
- return 0;
-}
-
-
-/* FIXME: 64 is completely arbitrary */
-/* FIXME: check for overflows in various grid calculations (at least when larger values are allowed) */
-/* grid <grid id> set-column-weight <column> <weight> */
-static int
-ltk_grid_cmd_set_column_weight(
- ltk_window *window,
- ltk_grid *grid,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_INT, .min = 0, .max = grid->columns - 1, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = 64, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- ltk_grid_set_column_weight(grid, cmd[0].val.i, cmd[1].val.i);
-
- return 0;
-}
-
-static struct grid_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_grid *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} grid_cmds[] = {
- {"add", <k_grid_cmd_add, 0},
- {"create", <k_grid_cmd_create, 1},
- {"remove", <k_grid_cmd_ungrid, 0},
- {"set-column-weight", <k_grid_cmd_set_column_weight, 0},
- {"set-row-weight", <k_grid_cmd_set_row_weight, 0},
-};
-
-GEN_CMD_HELPERS(ltk_grid_cmd, LTK_WIDGET_GRID, ltk_grid, grid_cmds, struct grid_cmd)
diff --git a/src/grid.h b/src/grid.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016-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
@@ -17,10 +17,10 @@
#ifndef LTK_GRID_H
#define LTK_GRID_H
-/* Requires the following includes: "rect.h", "widget.h", "ltk.h" */
+#include "widget.h"
+#include "window.h"
-#include "cmd.h"
-#include "err.h"
+#define LTK_GRID_SIGNAL_INVALID -1
/*
* Struct to represent a grid widget.
@@ -38,6 +38,15 @@ typedef struct {
int columns;
} ltk_grid;
-GEN_CMD_HELPERS_PROTO(ltk_grid_cmd)
+/* FIXME: proper error handling for different errors in add/remove */
+ltk_grid *ltk_grid_create(ltk_window *window, int rows, int columns);
+int ltk_grid_add(
+ ltk_grid *grid, ltk_widget *widget,
+ int row, int column, int row_span, int column_span,
+ ltk_sticky_mask sticky
+);
+int ltk_grid_remove(ltk_grid *grid, ltk_widget *widget);
+void ltk_grid_set_row_weight(ltk_grid *grid, int row, int weight);
+void ltk_grid_set_column_weight(ltk_grid *grid, int column, int weight);
#endif /* LTK_GRID_H */
diff --git a/src/image.c b/src/image.c
@@ -1,112 +0,0 @@
-/*
- * Copyright (c) 2023 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 <Imlib2.h>
-
-#include "ltk.h"
-#include "image.h"
-#include "memory.h"
-#include "graphics.h"
-#include "xlib_shared.h"
-
-struct ltk_image {
- Imlib_Image img;
-};
-
-void
-ltk_image_init(ltk_renderdata *data, size_t cache_bytes) {
- /* FIXME: overflow */
- imlib_set_cache_size((int)cache_bytes);
- imlib_set_color_usage(128);
- imlib_context_set_blend(0);
- imlib_context_set_dither(1);
- imlib_context_set_display(data->dpy);
- imlib_context_set_visual(data->vis);
- imlib_context_set_colormap(data->cm);
-}
-
-ltk_image *
-ltk_image_create_from_mem(const char *path, const char *data, size_t sz) {
- Imlib_Image img = imlib_load_image_mem(path, data, sz);
- if (!img)
- return NULL;
- ltk_image *image = ltk_malloc(sizeof(ltk_image));
- image->img = img;
- return image;
-}
-
-ltk_image *
-ltk_image_create_cropped_scaled(
- ltk_image *image, int src_x, int src_y,
- int src_w, int src_h, int dst_w, int dst_h) {
- imlib_context_set_image(image->img);
- /* FIXME: check since when supported */
- Imlib_Image img = imlib_create_cropped_scaled_image(
- src_x, src_y, src_w, src_h, dst_w, dst_h
- );
- if (!img)
- return NULL;
- ltk_image *new_image = ltk_malloc(sizeof(ltk_image));
- new_image->img = img;
- return new_image;
-}
-
-void
-ltk_image_draw(ltk_image *image, ltk_surface *draw_surf, int x, int y) {
- imlib_context_set_image(image->img);
- imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
- imlib_render_image_on_drawable(x, y);
-}
-
-void
-ltk_image_draw_scaled(
- ltk_image *image, ltk_surface *draw_surf,
- int x, int y, int w, int h) {
- imlib_context_set_image(image->img);
- imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
- imlib_render_image_on_drawable_at_size(x, y, w, h);
-}
-
-void
-ltk_image_draw_cropped_scaled(
- ltk_image *image, ltk_surface *draw_surf,
- int src_x, int src_y, int src_w, int src_h,
- int dst_x, int dst_y, int dst_w, int dst_h) {
- imlib_context_set_image(image->img);
- imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
- imlib_render_image_part_on_drawable_at_size(
- src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h
- );
-}
-
-int
-ltk_image_get_width(ltk_image *image) {
- imlib_context_set_image(image->img);
- return imlib_image_get_width();
-}
-
-int
-ltk_image_get_height(ltk_image *image) {
- imlib_context_set_image(image->img);
- return imlib_image_get_height();
-}
-
-void
-ltk_image_destroy(ltk_image *image) {
- imlib_context_set_image(image->img);
- imlib_free_image();
- ltk_free(image);
-}
diff --git a/src/image.h b/src/image.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2023-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
@@ -17,8 +17,7 @@
#ifndef LTK_IMAGE_H
#define LTK_IMAGE_H
-/* FIXME: Files including this need to include ltk.h because that's
- currently where the typedef for ltk_surface is. That's really ugly. */
+#include <stddef.h>
#include "graphics.h"
typedef struct ltk_image ltk_image;
@@ -27,6 +26,7 @@ void ltk_image_init(ltk_renderdata *data, size_t cache_bytes);
/* path is only used to guess file type */
/* data is not taken over! */
ltk_image *ltk_image_create_from_mem(const char *path, const char *data, size_t sz);
+ltk_image *ltk_image_create_from_path(const char *path);
ltk_image *ltk_image_create_cropped_scaled(
ltk_image *image, int src_x, int src_y,
int src_w, int src_h, int dst_w, int dst_h
diff --git a/src/image_imlib.c b/src/image_imlib.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2023-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 <stddef.h>
+
+#include <Imlib2.h>
+
+#include "image.h"
+#include "memory.h"
+#include "graphics.h"
+#include "graphics_xlib.h"
+
+struct ltk_image {
+ Imlib_Image img;
+};
+
+void
+ltk_image_init(ltk_renderdata *data, size_t cache_bytes) {
+ /* FIXME: overflow */
+ imlib_set_cache_size((int)cache_bytes);
+ imlib_set_color_usage(128);
+ imlib_context_set_blend(0);
+ imlib_context_set_dither(1);
+ imlib_context_set_display(data->dpy);
+ imlib_context_set_visual(data->vis);
+ imlib_context_set_colormap(data->cm);
+}
+
+ltk_image *
+ltk_image_create_from_mem(const char *path, const char *data, size_t sz) {
+ Imlib_Image img = imlib_load_image_mem(path, data, sz);
+ if (!img)
+ return NULL;
+ ltk_image *image = ltk_malloc(sizeof(ltk_image));
+ image->img = img;
+ return image;
+}
+
+ltk_image *
+ltk_image_create_from_path(const char *path) {
+ Imlib_Image img = imlib_load_image_immediately(path);
+ if (!img)
+ return NULL;
+ ltk_image *image = ltk_malloc(sizeof(ltk_image));
+ image->img = img;
+ return image;
+}
+
+ltk_image *
+ltk_image_create_cropped_scaled(
+ ltk_image *image, int src_x, int src_y,
+ int src_w, int src_h, int dst_w, int dst_h) {
+ imlib_context_set_image(image->img);
+ /* FIXME: check since when supported */
+ Imlib_Image img = imlib_create_cropped_scaled_image(
+ src_x, src_y, src_w, src_h, dst_w, dst_h
+ );
+ if (!img)
+ return NULL;
+ ltk_image *new_image = ltk_malloc(sizeof(ltk_image));
+ new_image->img = img;
+ return new_image;
+}
+
+void
+ltk_image_draw(ltk_image *image, ltk_surface *draw_surf, int x, int y) {
+ imlib_context_set_image(image->img);
+ imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
+ imlib_render_image_on_drawable(x, y);
+}
+
+void
+ltk_image_draw_scaled(
+ ltk_image *image, ltk_surface *draw_surf,
+ int x, int y, int w, int h) {
+ imlib_context_set_image(image->img);
+ imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
+ imlib_render_image_on_drawable_at_size(x, y, w, h);
+}
+
+void
+ltk_image_draw_cropped_scaled(
+ ltk_image *image, ltk_surface *draw_surf,
+ int src_x, int src_y, int src_w, int src_h,
+ int dst_x, int dst_y, int dst_w, int dst_h) {
+ imlib_context_set_image(image->img);
+ imlib_context_set_drawable(ltk_surface_get_drawable(draw_surf));
+ imlib_render_image_part_on_drawable_at_size(
+ src_x, src_y, src_w, src_h, dst_x, dst_y, dst_w, dst_h
+ );
+}
+
+int
+ltk_image_get_width(ltk_image *image) {
+ imlib_context_set_image(image->img);
+ return imlib_image_get_width();
+}
+
+int
+ltk_image_get_height(ltk_image *image) {
+ imlib_context_set_image(image->img);
+ return imlib_image_get_height();
+}
+
+void
+ltk_image_destroy(ltk_image *image) {
+ imlib_context_set_image(image->img);
+ imlib_free_image();
+ ltk_free(image);
+}
diff --git a/src/image_widget.c b/src/image_widget.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2023-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
@@ -14,31 +14,20 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <stdarg.h>
+#include <stddef.h>
-#include "event.h"
+#include "image_widget.h"
+#include "graphics.h"
#include "memory.h"
-#include "color.h"
#include "rect.h"
-#include "widget.h"
-#include "ltk.h"
#include "util.h"
-#include "image_widget.h"
-#include "graphics.h"
-#include "cmd.h"
+#include "widget.h"
/* FIXME: add padding, etc. */
static void ltk_image_widget_draw(
ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip
);
-static ltk_image_widget *ltk_image_widget_create(
- ltk_window *window, const char *id, const char *path, const char *data, size_t len
-);
static void ltk_image_widget_destroy(ltk_widget *self, int shallow);
static struct ltk_widget_vtable vtable = {
@@ -58,12 +47,13 @@ static struct ltk_widget_vtable vtable = {
.mouse_leave = NULL,
.mouse_enter = NULL,
.type = LTK_WIDGET_IMAGE,
+ /* FIXME: need to show that it is activated somehow in special mode */
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_SPECIAL,
};
static void
ltk_image_widget_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
- ltk_image_widget *img = (ltk_image_widget *)self;
+ ltk_image_widget *img = LTK_CAST_IMAGE_WIDGET(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)
@@ -81,25 +71,24 @@ ltk_image_widget_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, lt
);
}
-static ltk_image_widget *
-ltk_image_widget_create(ltk_window *window, const char *id, const char *path, const char *data, size_t len) {
- ltk_image_widget *img = ltk_malloc(sizeof(ltk_image_widget));
- img->img = ltk_image_create_from_mem(path, data, len);
- if (!img->img)
- return NULL;
- int w = ltk_image_get_width(img->img);
- int h = ltk_image_get_height(img->img);
- ltk_fill_widget_defaults(&img->widget, id, window, &vtable, w, h);
- img->widget.ideal_w = w;
- img->widget.ideal_h = h;
+ltk_image_widget *
+ltk_image_widget_create(ltk_window *window, ltk_image *img) {
+ ltk_image_widget *imw = ltk_malloc(sizeof(ltk_image_widget));
+ imw->img = img;
+ int w = ltk_image_get_width(imw->img);
+ int h = ltk_image_get_height(imw->img);
+ ltk_fill_widget_defaults(&imw->widget, window, &vtable, w, h);
+ imw->widget.ideal_w = w;
+ imw->widget.ideal_h = h;
- return img;
+ return imw;
}
static void
ltk_image_widget_destroy(ltk_widget *self, int shallow) {
(void)shallow;
- ltk_image_widget *img = (ltk_image_widget *)self;
+ ltk_image_widget *img = LTK_CAST_IMAGE_WIDGET(self);
+ /* FIXME: make warnings like this consistent across widgets */
if (!img) {
ltk_warn("Tried to destroy NULL image widget.\n");
return;
@@ -107,48 +96,3 @@ ltk_image_widget_destroy(ltk_widget *self, int shallow) {
ltk_image_destroy(img->img);
ltk_free(img);
}
-
-/* image <image id> create <filename> <data> */
-static int
-ltk_image_widget_cmd_create(
- ltk_window *window,
- ltk_image_widget *img_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)img_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_DATA, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_image_widget *img = ltk_image_widget_create(window, cmd[1].val.str, cmd[3].val.str, cmd[4].val.data, cmd[4].len);
- if (!img) {
- /* FIXME: more sensible error name */
- err->type = ERR_UNKNOWN;
- err->arg = -1;
- return 1;
- }
- ltk_set_widget((ltk_widget *)img, cmd[1].val.str);
-
- return 0;
-}
-
-static struct image_widget_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_image_widget *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} image_widget_cmds[] = {
- {"create", <k_image_widget_cmd_create, 1},
-};
-
-GEN_CMD_HELPERS(ltk_image_widget_cmd, LTK_WIDGET_IMAGE, ltk_image_widget, image_widget_cmds, struct image_widget_cmd)
diff --git a/src/image_widget.h b/src/image_widget.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2023-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
@@ -17,15 +17,16 @@
#ifndef LTK_IMAGE_WIDGET_H
#define LTK_IMAGE_WIDGET_H
-#include "cmd.h"
-#include "err.h"
#include "image.h"
+#include "widget.h"
+#include "window.h"
typedef struct {
ltk_widget widget;
ltk_image *img;
} ltk_image_widget;
-GEN_CMD_HELPERS_PROTO(ltk_image_widget_cmd)
+/* WARNING: takes over img! */
+ltk_image_widget *ltk_image_widget_create(ltk_window *window, ltk_image *img);
#endif /* LTK_IMAGE_WIDGET_H */
diff --git a/src/keys.h b/src/keys.h
@@ -1,5 +1,23 @@
-#ifndef _KEYS_H_
-#define _KEYS_H_
+/*
+ * Copyright (c) 2023-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_KEYS_H
+#define LTK_KEYS_H
+
+#include <stddef.h>
#include "util.h"
@@ -57,4 +75,4 @@ name##_get_entry(const char *text, size_t len) {
); \
}
-#endif
+#endif /* LTK_KEYS_H */
diff --git a/src/khash.h b/src/khash.h
@@ -1,627 +0,0 @@
-/* The MIT License
-
- Copyright (c) 2008, 2009, 2011 by Attractive Chaos <attractor@live.co.uk>
-
- 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.
-*/
-
-/*
- An example:
-
-#include "khash.h"
-KHASH_MAP_INIT_INT(32, char)
-int main() {
- int ret, is_missing;
- khiter_t k;
- khash_t(32) *h = kh_init(32);
- k = kh_put(32, h, 5, &ret);
- kh_value(h, k) = 10;
- k = kh_get(32, h, 10);
- is_missing = (k == kh_end(h));
- k = kh_get(32, h, 5);
- kh_del(32, h, k);
- for (k = kh_begin(h); k != kh_end(h); ++k)
- if (kh_exist(h, k)) kh_value(h, k) = 1;
- kh_destroy(32, h);
- return 0;
-}
-*/
-
-/*
- 2013-05-02 (0.2.8):
-
- * Use quadratic probing. When the capacity is power of 2, stepping function
- i*(i+1)/2 guarantees to traverse each bucket. It is better than double
- hashing on cache performance and is more robust than linear probing.
-
- In theory, double hashing should be more robust than quadratic probing.
- However, my implementation is probably not for large hash tables, because
- the second hash function is closely tied to the first hash function,
- which reduce the effectiveness of double hashing.
-
- Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php
-
- 2011-12-29 (0.2.7):
-
- * Minor code clean up; no actual effect.
-
- 2011-09-16 (0.2.6):
-
- * The capacity is a power of 2. This seems to dramatically improve the
- speed for simple keys. Thank Zilong Tan for the suggestion. Reference:
-
- - http://code.google.com/p/ulib/
- - http://nothings.org/computer/judy/
-
- * Allow to optionally use linear probing which usually has better
- performance for random input. Double hashing is still the default as it
- is more robust to certain non-random input.
-
- * Added Wang's integer hash function (not used by default). This hash
- function is more robust to certain non-random input.
-
- 2011-02-14 (0.2.5):
-
- * Allow to declare global functions.
-
- 2009-09-26 (0.2.4):
-
- * Improve portability
-
- 2008-09-19 (0.2.3):
-
- * Corrected the example
- * Improved interfaces
-
- 2008-09-11 (0.2.2):
-
- * Improved speed a little in kh_put()
-
- 2008-09-10 (0.2.1):
-
- * Added kh_clear()
- * Fixed a compiling error
-
- 2008-09-02 (0.2.0):
-
- * Changed to token concatenation which increases flexibility.
-
- 2008-08-31 (0.1.2):
-
- * Fixed a bug in kh_get(), which has not been tested previously.
-
- 2008-08-31 (0.1.1):
-
- * Added destructor
-*/
-
-
-#ifndef __AC_KHASH_H
-#define __AC_KHASH_H
-
-/*!
- @header
-
- Generic hash table library.
- */
-
-#define AC_VERSION_KHASH_H "0.2.8"
-
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-
-/* compiler specific configuration */
-
-#if UINT_MAX == 0xffffffffu
-typedef unsigned int khint32_t;
-#elif ULONG_MAX == 0xffffffffu
-typedef unsigned long khint32_t;
-#endif
-
-#if ULONG_MAX == ULLONG_MAX
-typedef unsigned long khint64_t;
-#else
-typedef unsigned long long khint64_t;
-#endif
-
-#ifndef kh_inline
-#ifdef _MSC_VER
-#define kh_inline __inline
-#else
-#define kh_inline inline
-#endif
-#endif /* kh_inline */
-
-#ifndef klib_unused
-#if (defined __clang__ && __clang_major__ >= 3) || (defined __GNUC__ && __GNUC__ >= 3)
-#define klib_unused __attribute__ ((__unused__))
-#else
-#define klib_unused
-#endif
-#endif /* klib_unused */
-
-typedef khint32_t khint_t;
-typedef khint_t khiter_t;
-
-#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2)
-#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1)
-#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3)
-#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1)))
-#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1)))
-#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1)))
-#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1))
-
-#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4)
-
-#ifndef kroundup32
-#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x))
-#endif
-
-#ifndef kcalloc
-#define kcalloc(N,Z) calloc(N,Z)
-#endif
-#ifndef kmalloc
-#define kmalloc(Z) malloc(Z)
-#endif
-#ifndef krealloc
-#define krealloc(P,Z) realloc(P,Z)
-#endif
-#ifndef kfree
-#define kfree(P) free(P)
-#endif
-
-static const double __ac_HASH_UPPER = 0.77;
-
-#define __KHASH_TYPE(name, khkey_t, khval_t) \
- typedef struct kh_##name##_s { \
- khint_t n_buckets, size, n_occupied, upper_bound; \
- khint32_t *flags; \
- khkey_t *keys; \
- khval_t *vals; \
- } kh_##name##_t;
-
-#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \
- extern kh_##name##_t *kh_init_##name(void); \
- extern void kh_destroy_##name(kh_##name##_t *h); \
- extern void kh_clear_##name(kh_##name##_t *h); \
- extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \
- extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \
- extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \
- extern void kh_del_##name(kh_##name##_t *h, khint_t x);
-
-#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- SCOPE kh_##name##_t *kh_init_##name(void) { \
- return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \
- } \
- SCOPE void kh_destroy_##name(kh_##name##_t *h) \
- { \
- if (h) { \
- kfree((void *)h->keys); kfree(h->flags); \
- kfree((void *)h->vals); \
- kfree(h); \
- } \
- } \
- SCOPE void kh_clear_##name(kh_##name##_t *h) \
- { \
- if (h && h->flags) { \
- memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \
- h->size = h->n_occupied = 0; \
- } \
- } \
- SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \
- { \
- if (h->n_buckets) { \
- khint_t k, i, last, mask, step = 0; \
- mask = h->n_buckets - 1; \
- k = __hash_func(key); i = k & mask; \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- i = (i + (++step)) & mask; \
- if (i == last) return h->n_buckets; \
- } \
- return __ac_iseither(h->flags, i)? h->n_buckets : i; \
- } else return 0; \
- } \
- SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \
- { /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \
- khint32_t *new_flags = 0; \
- khint_t j = 1; \
- { \
- kroundup32(new_n_buckets); \
- if (new_n_buckets < 4) new_n_buckets = 4; \
- if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \
- else { /* hash table size to be changed (shrink or expand); rehash */ \
- new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- if (!new_flags) return -1; \
- memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \
- if (h->n_buckets < new_n_buckets) { /* expand */ \
- khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- if (!new_keys) { kfree(new_flags); return -1; } \
- h->keys = new_keys; \
- if (kh_is_map) { \
- khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- if (!new_vals) { kfree(new_flags); return -1; } \
- h->vals = new_vals; \
- } \
- } /* otherwise shrink */ \
- } \
- } \
- if (j) { /* rehashing is needed */ \
- for (j = 0; j != h->n_buckets; ++j) { \
- if (__ac_iseither(h->flags, j) == 0) { \
- khkey_t key = h->keys[j]; \
- khval_t val; \
- khint_t new_mask; \
- new_mask = new_n_buckets - 1; \
- if (kh_is_map) val = h->vals[j]; \
- __ac_set_isdel_true(h->flags, j); \
- while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \
- khint_t k, i, step = 0; \
- k = __hash_func(key); \
- i = k & new_mask; \
- while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \
- __ac_set_isempty_false(new_flags, i); \
- if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \
- { khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \
- if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \
- __ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \
- } else { /* write the element and jump out of the loop */ \
- h->keys[i] = key; \
- if (kh_is_map) h->vals[i] = val; \
- break; \
- } \
- } \
- } \
- } \
- if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \
- h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \
- if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \
- } \
- kfree(h->flags); /* free the working space */ \
- h->flags = new_flags; \
- h->n_buckets = new_n_buckets; \
- h->n_occupied = h->size; \
- h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \
- } \
- return 0; \
- } \
- SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \
- { \
- khint_t x; \
- if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \
- if (h->n_buckets > (h->size<<1)) { \
- if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \
- *ret = -1; return h->n_buckets; \
- } \
- } else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \
- *ret = -1; return h->n_buckets; \
- } \
- } /* TODO: to implement automatically shrinking; resize() already support shrinking */ \
- { \
- khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \
- x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \
- if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \
- else { \
- last = i; \
- while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \
- if (__ac_isdel(h->flags, i)) site = i; \
- i = (i + (++step)) & mask; \
- if (i == last) { x = site; break; } \
- } \
- if (x == h->n_buckets) { \
- if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \
- else x = i; \
- } \
- } \
- } \
- if (__ac_isempty(h->flags, x)) { /* not present at all */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; ++h->n_occupied; \
- *ret = 1; \
- } else if (__ac_isdel(h->flags, x)) { /* deleted */ \
- h->keys[x] = key; \
- __ac_set_isboth_false(h->flags, x); \
- ++h->size; \
- *ret = 2; \
- } else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \
- return x; \
- } \
- SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \
- { \
- if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \
- __ac_set_isdel_true(h->flags, x); \
- --h->size; \
- } \
- }
-
-#define KHASH_DECLARE(name, khkey_t, khval_t) \
- __KHASH_TYPE(name, khkey_t, khval_t) \
- __KHASH_PROTOTYPES(name, khkey_t, khval_t)
-
-#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- __KHASH_TYPE(name, khkey_t, khval_t) \
- __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \
- KHASH_INIT2(name, static kh_inline klib_unused, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal)
-
-/* --- BEGIN OF HASH FUNCTIONS --- */
-
-/*! @function
- @abstract Integer hash function
- @param key The integer [khint32_t]
- @return The hash value [khint_t]
- */
-#define kh_int_hash_func(key) (khint32_t)(key)
-/*! @function
- @abstract Integer comparison function
- */
-#define kh_int_hash_equal(a, b) ((a) == (b))
-/*! @function
- @abstract 64-bit integer hash function
- @param key The integer [khint64_t]
- @return The hash value [khint_t]
- */
-#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11)
-/*! @function
- @abstract 64-bit integer comparison function
- */
-#define kh_int64_hash_equal(a, b) ((a) == (b))
-/*! @function
- @abstract const char* hash function
- @param s Pointer to a null terminated string
- @return The hash value
- */
-static kh_inline khint_t __ac_X31_hash_string(const char *s)
-{
- khint_t h = (khint_t)*s;
- if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s;
- return h;
-}
-/*! @function
- @abstract Another interface to const char* hash function
- @param key Pointer to a null terminated string [const char*]
- @return The hash value [khint_t]
- */
-#define kh_str_hash_func(key) __ac_X31_hash_string(key)
-/*! @function
- @abstract Const char* comparison function
- */
-#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0)
-
-static kh_inline khint_t __ac_Wang_hash(khint_t key)
-{
- key += ~(key << 15);
- key ^= (key >> 10);
- key += (key << 3);
- key ^= (key >> 6);
- key += ~(key << 11);
- key ^= (key >> 16);
- return key;
-}
-#define kh_int_hash_func2(key) __ac_Wang_hash((khint_t)key)
-
-/* --- END OF HASH FUNCTIONS --- */
-
-/* Other convenient macros... */
-
-/*!
- @abstract Type of the hash table.
- @param name Name of the hash table [symbol]
- */
-#define khash_t(name) kh_##name##_t
-
-/*! @function
- @abstract Initiate a hash table.
- @param name Name of the hash table [symbol]
- @return Pointer to the hash table [khash_t(name)*]
- */
-#define kh_init(name) kh_init_##name()
-
-/*! @function
- @abstract Destroy a hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- */
-#define kh_destroy(name, h) kh_destroy_##name(h)
-
-/*! @function
- @abstract Reset a hash table without deallocating memory.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- */
-#define kh_clear(name, h) kh_clear_##name(h)
-
-/*! @function
- @abstract Resize a hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param s New size [khint_t]
- */
-#define kh_resize(name, h, s) kh_resize_##name(h, s)
-
-/*! @function
- @abstract Insert a key to the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Key [type of keys]
- @param r Extra return code: -1 if the operation failed;
- 0 if the key is present in the hash table;
- 1 if the bucket is empty (never used); 2 if the element in
- the bucket has been deleted [int*]
- @return Iterator to the inserted element [khint_t]
- */
-#define kh_put(name, h, k, r) kh_put_##name(h, k, r)
-
-/*! @function
- @abstract Retrieve a key from the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Key [type of keys]
- @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t]
- */
-#define kh_get(name, h, k) kh_get_##name(h, k)
-
-/*! @function
- @abstract Remove a key from the hash table.
- @param name Name of the hash table [symbol]
- @param h Pointer to the hash table [khash_t(name)*]
- @param k Iterator to the element to be deleted [khint_t]
- */
-#define kh_del(name, h, k) kh_del_##name(h, k)
-
-/*! @function
- @abstract Test whether a bucket contains data.
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return 1 if containing data; 0 otherwise [int]
- */
-#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x)))
-
-/*! @function
- @abstract Get key given an iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return Key [type of keys]
- */
-#define kh_key(h, x) ((h)->keys[x])
-
-/*! @function
- @abstract Get value given an iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @param x Iterator to the bucket [khint_t]
- @return Value [type of values]
- @discussion For hash sets, calling this results in segfault.
- */
-#define kh_val(h, x) ((h)->vals[x])
-
-/*! @function
- @abstract Alias of kh_val()
- */
-#define kh_value(h, x) ((h)->vals[x])
-
-/*! @function
- @abstract Get the start iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @return The start iterator [khint_t]
- */
-#define kh_begin(h) (khint_t)(0)
-
-/*! @function
- @abstract Get the end iterator
- @param h Pointer to the hash table [khash_t(name)*]
- @return The end iterator [khint_t]
- */
-#define kh_end(h) ((h)->n_buckets)
-
-/*! @function
- @abstract Get the number of elements in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @return Number of elements in the hash table [khint_t]
- */
-#define kh_size(h) ((h)->size)
-
-/*! @function
- @abstract Get the number of buckets in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @return Number of buckets in the hash table [khint_t]
- */
-#define kh_n_buckets(h) ((h)->n_buckets)
-
-/*! @function
- @abstract Iterate over the entries in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @param kvar Variable to which key will be assigned
- @param vvar Variable to which value will be assigned
- @param code Block of code to execute
- */
-#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \
- for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
- if (!kh_exist(h,__i)) continue; \
- (kvar) = kh_key(h,__i); \
- (vvar) = kh_val(h,__i); \
- code; \
- } }
-
-/*! @function
- @abstract Iterate over the values in the hash table
- @param h Pointer to the hash table [khash_t(name)*]
- @param vvar Variable to which value will be assigned
- @param code Block of code to execute
- */
-#define kh_foreach_value(h, vvar, code) { khint_t __i; \
- for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \
- if (!kh_exist(h,__i)) continue; \
- (vvar) = kh_val(h,__i); \
- code; \
- } }
-
-/* More conenient interfaces */
-
-/*! @function
- @abstract Instantiate a hash set containing integer keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_INT(name) \
- KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing integer keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_INT(name, khval_t) \
- KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing 64-bit integer keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_INT64(name) \
- KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing 64-bit integer keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_INT64(name, khval_t) \
- KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal)
-
-typedef const char *kh_cstr_t;
-/*! @function
- @abstract Instantiate a hash map containing const char* keys
- @param name Name of the hash table [symbol]
- */
-#define KHASH_SET_INIT_STR(name) \
- KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal)
-
-/*! @function
- @abstract Instantiate a hash map containing const char* keys
- @param name Name of the hash table [symbol]
- @param khval_t Type of values [type]
- */
-#define KHASH_MAP_INIT_STR(name, khval_t) \
- KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal)
-
-#endif /* __AC_KHASH_H */
diff --git a/src/label.c b/src/label.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,13 +14,9 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdio.h>
-#include <stdlib.h>
+#include <stddef.h>
#include <stdint.h>
-#include <string.h>
-#include <stdarg.h>
-#include "event.h"
#include "memory.h"
#include "color.h"
#include "rect.h"
@@ -30,17 +26,12 @@
#include "text.h"
#include "label.h"
#include "graphics.h"
-#include "surface_cache.h"
#include "theme.h"
-#include "cmd.h"
#define MAX_LABEL_PADDING 500
static void ltk_label_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
-static ltk_label *ltk_label_create(ltk_window *window,
- const char *id, char *text);
static void ltk_label_destroy(ltk_widget *self, int shallow);
-static void ltk_label_redraw_surface(ltk_label *label, ltk_surface *s);
static struct ltk_widget_vtable vtable = {
.draw = <k_label_draw,
@@ -60,12 +51,13 @@ static struct ltk_widget_vtable vtable = {
.mouse_enter = NULL,
.type = LTK_WIDGET_LABEL,
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_SPECIAL,
+ .invalid_signal = LTK_LABEL_SIGNAL_INVALID
};
static struct {
- ltk_color text_color;
- ltk_color bg_color;
- ltk_color bg_color_active;
+ ltk_color *text_color;
+ ltk_color *bg_color;
+ ltk_color *bg_color_active;
int pad;
} theme;
@@ -79,61 +71,49 @@ static ltk_theme_parseinfo parseinfo[] = {
};
int
-ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "label", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
+ltk_label_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "label", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
}
int
-ltk_label_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "label", parseinfo, LENGTH(parseinfo));
+ltk_label_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "label", parseinfo, LENGTH(parseinfo));
}
void
-ltk_label_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
+ltk_label_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
}
static void
ltk_label_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
- ltk_label *label = (ltk_label *)self;
+ ltk_label *label = LTK_CAST_LABEL(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;
- ltk_surface *s;
- ltk_surface_cache_request_surface_size(label->key, lrect.w, lrect.h);
- if (!ltk_surface_cache_get_surface(label->key, &s) || self->dirty)
- ltk_label_redraw_surface(label, s);
- ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
-}
-
-static void
-ltk_label_redraw_surface(ltk_label *label, ltk_surface *s) {
- ltk_rect r = label->widget.lrect;
- r.x = 0;
- r.y = 0;
- ltk_surface_fill_rect(s, (label->widget.state & LTK_ACTIVE) ? &theme.bg_color_active : &theme.bg_color, r);
+ ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
+ ltk_surface_fill_rect(draw_surf, (self->state & LTK_ACTIVE) ? theme.bg_color_active : theme.bg_color, draw_clip);
int text_w, text_h;
ltk_text_line_get_size(label->tl, &text_w, &text_h);
- int text_x = (r.w - text_w) / 2;
- int text_y = (r.h - text_h) / 2;
- ltk_text_line_draw(label->tl, s, &theme.text_color, text_x, text_y);
+ int text_x = x + (lrect.w - text_w) / 2;
+ int text_y = y + (lrect.h - text_h) / 2;
+ ltk_text_line_draw_clipped(label->tl, draw_surf, theme.text_color, text_x, text_y, draw_clip);
}
-static ltk_label *
-ltk_label_create(ltk_window *window, const char *id, char *text) {
+ltk_label *
+ltk_label_create(ltk_window *window, char *text) {
ltk_label *label = ltk_malloc(sizeof(ltk_label));
uint16_t font_size = window->theme->font_size;
- label->tl = ltk_text_line_create(window->text_context, font_size, text, 0, -1);
+ label->tl = ltk_text_line_create_default(font_size, text, 0, -1);
int text_w, text_h;
ltk_text_line_get_size(label->tl, &text_w, &text_h);
/* FIXME: what was I even thinking here? label->widget.ideal_{w,h} isn't even initialized here */
- ltk_fill_widget_defaults(&label->widget, id, window, &vtable, label->widget.ideal_w, label->widget.ideal_h);
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(label), window, &vtable, label->widget.ideal_w, label->widget.ideal_h);
label->widget.ideal_w = text_w + theme.pad * 2;
label->widget.ideal_h = text_h + theme.pad * 2;
- label->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, label->widget.ideal_w, label->widget.ideal_h);
return label;
}
@@ -141,50 +121,11 @@ ltk_label_create(ltk_window *window, const char *id, char *text) {
static void
ltk_label_destroy(ltk_widget *self, int shallow) {
(void)shallow;
- ltk_label *label = (ltk_label *)self;
+ ltk_label *label = LTK_CAST_LABEL(self);
if (!label) {
ltk_warn("Tried to destroy NULL label.\n");
return;
}
- ltk_surface_cache_release_key(label->key);
ltk_text_line_destroy(label->tl);
ltk_free(label);
}
-
-/* label <label id> create <text> */
-static int
-ltk_label_cmd_create(
- ltk_window *window,
- ltk_label *label_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)label_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_label *label = ltk_label_create(window, cmd[1].val.str, cmd[3].val.str);
- ltk_set_widget((ltk_widget *)label, cmd[1].val.str);
-
- return 0;
-}
-
-static struct label_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_label *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} label_cmds[] = {
- {"create", <k_label_cmd_create, 1},
-};
-
-GEN_CMD_HELPERS(ltk_label_cmd, LTK_WIDGET_LABEL, ltk_label, label_cmds, struct label_cmd)
diff --git a/src/label.h b/src/label.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -17,21 +17,22 @@
#ifndef LTK_LABEL_H
#define LTK_LABEL_H
-/* Requires the following includes: <X11/Xlib.h>, "rect.h", "widget.h", "ltk.h", "color.h", "text.h" */
+#include "text.h"
+#include "graphics.h"
+#include "widget.h"
+#include "window.h"
-#include "cmd.h"
-#include "err.h"
+#define LTK_LABEL_SIGNAL_INVALID -1
typedef struct {
ltk_widget widget;
ltk_text_line *tl;
- ltk_surface_cache_key *key;
} ltk_label;
-int ltk_label_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_label_fill_theme_defaults(ltk_window *window);
-void ltk_label_uninitialize_theme(ltk_window *window);
+int ltk_label_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_label_fill_theme_defaults(ltk_renderdata *data);
+void ltk_label_uninitialize_theme(ltk_renderdata *data);
-GEN_CMD_HELPERS_PROTO(ltk_label_cmd)
+ltk_label *ltk_label_create(ltk_window *window, char *text);
#endif /* LTK_LABEL_H */
diff --git a/src/ltk.c b/src/ltk.c
@@ -0,0 +1,672 @@
+/*
+ * Copyright (c) 2016-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 <locale.h>
+#include <pwd.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <sys/wait.h>
+
+#include "ltk.h"
+#include "array.h"
+#include "button.h"
+#include "config.h"
+#include "entry.h"
+#include "event.h"
+#include "eventdefs.h"
+#include "graphics.h"
+#include "image.h"
+#include "ini.h"
+#include "label.h"
+#include "macros.h"
+#include "memory.h"
+#include "menu.h"
+#include "rect.h"
+#include "scrollbar.h"
+#include "text.h"
+#include "util.h"
+#include "widget.h"
+
+#define MAX_WINDOW_FONT_SIZE 200
+
+typedef struct {
+ char *tmpfile;
+ ltk_widget *caller;
+ int pid;
+} ltk_cmdinfo;
+
+LTK_ARRAY_INIT_DECL_STATIC(window, ltk_window *)
+LTK_ARRAY_INIT_IMPL_STATIC(window, ltk_window *)
+LTK_ARRAY_INIT_DECL_STATIC(rwindow, ltk_renderwindow *)
+LTK_ARRAY_INIT_IMPL_STATIC(rwindow, ltk_renderwindow *)
+LTK_ARRAY_INIT_DECL_STATIC(cmd, ltk_cmdinfo)
+LTK_ARRAY_INIT_IMPL_STATIC(cmd, ltk_cmdinfo)
+
+static struct {
+ ltk_renderdata *renderdata;
+ ltk_text_context *text_context;
+ ltk_clipboard *clipboard;
+ ltk_array(window) *windows;
+ ltk_array(rwindow) *rwindows;
+ /* PID of external command called e.g. by text widget to edit text.
+ ON exit, cmd_caller->vtable->cmd_return is called with the text
+ the external command wrote to a file. */
+ /*IMPORTANT: this needs to be checked whenever a widget is destroyed!
+ FIXME: allow option to instead return output of command */
+ ltk_array(cmd) *cmds;
+ size_t cur_kbd;
+} shared_data = {NULL, NULL, NULL, NULL, NULL, NULL, 0};
+
+typedef struct {
+ void (*callback)(ltk_callback_arg data);
+ ltk_callback_arg data;
+ struct timespec repeat;
+ struct timespec remaining;
+ int id;
+} ltk_timer;
+
+static ltk_timer *timers = NULL;
+static size_t timers_num = 0;
+static size_t timers_alloc = 0;
+
+static void ltk_handle_event(ltk_event *event);
+static void ltk_load_theme(const char *path);
+static void ltk_uninitialize_theme(void);
+static int ltk_ini_handler(void *renderdata, const char *widget, const char *prop, const char *value);
+static int handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b);
+static int handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b);
+
+static short running = 1;
+
+typedef struct {
+ char *name;
+ int (*ini_handler)(ltk_renderdata *, const char *, const char *);
+ int (*fill_theme_defaults)(ltk_renderdata *);
+ void (*uninitialize_theme)(ltk_renderdata *);
+ int (*register_keypress)(const char *, size_t, ltk_keypress_binding);
+ int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding);
+ void (*cleanup)(void);
+} ltk_widget_funcs;
+
+/* FIXME: use binary search when searching for the widget */
+ltk_widget_funcs widget_funcs[] = {
+ {
+ .name = "box",
+ .ini_handler = NULL,
+ .fill_theme_defaults = NULL,
+ .uninitialize_theme = NULL,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "button",
+ .ini_handler = <k_button_ini_handler,
+ .fill_theme_defaults = <k_button_fill_theme_defaults,
+ .uninitialize_theme = <k_button_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "entry",
+ .ini_handler = <k_entry_ini_handler,
+ .fill_theme_defaults = <k_entry_fill_theme_defaults,
+ .uninitialize_theme = <k_entry_uninitialize_theme,
+ .register_keypress = <k_entry_register_keypress,
+ .register_keyrelease = <k_entry_register_keyrelease,
+ .cleanup = <k_entry_cleanup,
+ },
+ {
+ .name = "grid",
+ .ini_handler = NULL,
+ .fill_theme_defaults = NULL,
+ .uninitialize_theme = NULL,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "label",
+ .ini_handler = <k_label_ini_handler,
+ .fill_theme_defaults = <k_label_fill_theme_defaults,
+ .uninitialize_theme = <k_label_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ /* FIXME: this is actually image_widget */
+ .name = "image",
+ .ini_handler = NULL,
+ .fill_theme_defaults = NULL,
+ .uninitialize_theme = NULL,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "menu",
+ .ini_handler = <k_menu_ini_handler,
+ .fill_theme_defaults = <k_menu_fill_theme_defaults,
+ .uninitialize_theme = <k_menu_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "menuentry",
+ .ini_handler = <k_menuentry_ini_handler,
+ .fill_theme_defaults = <k_menuentry_fill_theme_defaults,
+ .uninitialize_theme = <k_menuentry_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "submenu",
+ .ini_handler = <k_submenu_ini_handler,
+ .fill_theme_defaults = <k_submenu_fill_theme_defaults,
+ .uninitialize_theme = <k_submenu_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ .name = "submenuentry",
+ .ini_handler = <k_submenuentry_ini_handler,
+ .fill_theme_defaults = <k_submenuentry_fill_theme_defaults,
+ .uninitialize_theme = <k_submenuentry_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ /*
+ This "widget" is only needed to have separate styles for regular
+ menu entries and submenu entries. "submenu" is just an alias for
+ "menu" in most cases - it's just needed when creating a menu to
+ decide if it's a submenu or not.
+ FIXME: is that even necessary? Why can't it just decide if it's
+ a submenu based on whether it has a parent or not?
+ -> I guess right-click menus are also just submenus, so they
+ need to set it explicitly, but wasn't there another reason?
+ */
+ },
+ {
+ .name = "scrollbar",
+ .ini_handler = <k_scrollbar_ini_handler,
+ .fill_theme_defaults = <k_scrollbar_fill_theme_defaults,
+ .uninitialize_theme = <k_scrollbar_uninitialize_theme,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ /* Handler for general widget key bindings. */
+ .name = "widget",
+ .ini_handler = NULL,
+ .fill_theme_defaults = NULL,
+ .uninitialize_theme = NULL,
+ .register_keypress = NULL,
+ .register_keyrelease = NULL,
+ .cleanup = NULL,
+ },
+ {
+ /* Handler for window theme. */
+ .name = "window",
+ .ini_handler = <k_window_ini_handler,
+ .fill_theme_defaults = <k_window_fill_theme_defaults,
+ .uninitialize_theme = <k_window_uninitialize_theme,
+ .register_keypress = <k_window_register_keypress,
+ .register_keyrelease = <k_window_register_keyrelease,
+ .cleanup = <k_window_cleanup,
+ }
+};
+
+/* Get the directory to search for ltk.cfg in.
+ This first checks the environment variable LTKDIR and,
+ if that doesn't exist, the home directory with "/.ltk" appended.
+ Returns NULL on error. */
+static char *
+ltk_get_dir(void) {
+ char *dir, *dir_orig;
+ struct passwd *pw;
+ uid_t uid;
+ int len;
+
+ dir_orig = getenv("LTKDIR");
+ if (dir_orig) {
+ dir = ltk_strdup(dir_orig);
+ } else {
+ uid = getuid();
+ pw = getpwuid(uid);
+ if (!pw)
+ return NULL;
+ len = strlen(pw->pw_dir);
+ dir = ltk_malloc(len + 6);
+ if (!dir)
+ return NULL;
+ strcpy(dir, pw->pw_dir);
+ strcpy(dir + len, "/.ltk");
+ }
+
+ return dir;
+}
+
+int
+ltk_init(void) {
+ /* FIXME: should ltk set this? probably not */
+ setlocale(LC_CTYPE, "");
+ char *ltk_dir = ltk_get_dir();
+ /* FIXME: return error instead of dying */
+ if (!ltk_dir)
+ ltk_fatal_errno("Unable to setup ltk directory.\n");
+ shared_data.cur_kbd = 0;
+
+ /* FIXME: search different directories for config */
+ char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
+ char *theme_path;
+ char *errstr = NULL;
+ if (ltk_config_parsefile(config_path, &handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
+ if (errstr) {
+ ltk_warn("Unable to load config: %s\n", errstr);
+ ltk_free0(errstr);
+ }
+ if (ltk_config_load_default(&handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
+ /* FIXME: I guess errstr isn't freed here, but whatever */
+ /* FIXME: return error instead of dying */
+ ltk_fatal("Unable to load default config: %s\n", errstr);
+ }
+ }
+ ltk_free0(config_path);
+ theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
+ ltk_free0(ltk_dir);
+ shared_data.renderdata = renderer_create();
+ if (!shared_data.renderdata)
+ return 1; /* FIXME: clean up */
+ ltk_load_theme(theme_path);
+ ltk_free0(theme_path);
+ /* FIXME: maybe "general" theme instead of window theme? */
+ ltk_window_theme *window_theme = ltk_window_get_theme();
+ shared_data.text_context = ltk_text_context_create(shared_data.renderdata, window_theme->font);
+ shared_data.clipboard = ltk_clipboard_create(shared_data.renderdata);
+ /* FIXME: configure cache size; check for overflow */
+ ltk_image_init(shared_data.renderdata, 1024 * 1024 * 4);
+ shared_data.windows = ltk_array_create(window, 1);
+ shared_data.rwindows = ltk_array_create(rwindow, 1);
+ shared_data.cmds = ltk_array_create(cmd, 1);
+ return 0; /* FIXME: or maybe 1? */
+}
+
+/* FIXME: need to remove event masks from all widgets when removing client */
+int
+ltk_mainloop(void) {
+ ltk_event event;
+
+ /* FIXME: make time management smarter - maybe always figure out how long
+ it will take until the next timer is due and then sleep if no other events
+ are happening */
+ struct timespec now, elapsed, last, lasttimer, sleep_time;
+ clock_gettime(CLOCK_MONOTONIC, &last);
+ lasttimer = last;
+ sleep_time.tv_sec = 0;
+
+ /* initialize keyboard mapping */
+ ltk_generate_keyboard_event(shared_data.renderdata, &event);
+ ltk_handle_event(&event);
+
+ int pid = -1;
+ int wstatus = 0;
+ /* FIXME: kill all children on exit? */
+ while (running) {
+ if ((pid = waitpid(-1, &wstatus, WNOHANG)) > 0) {
+ //ltk_error err;
+ ltk_cmdinfo *info;
+ /* FIXME: should commands be split into read/write and block write commands during external editing? */
+ for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) {
+ info = &(ltk_array_get(shared_data.cmds, i));
+ if (info->pid == pid) {
+ if (!info->caller) {
+ ltk_warn("Widget disappeared while text was being edited in external program\n");
+ /* FIXME: call overwritten cmd_return! */
+ } else if (info->caller->vtable->cmd_return) {
+ size_t file_len = 0;
+ char *errstr = NULL;
+ char *contents = ltk_read_file(info->tmpfile, &file_len, &errstr);
+ if (!contents) {
+ ltk_warn("Unable to read file '%s' written by external command: %s\n", info->tmpfile, errstr);
+ } else {
+ info->caller->vtable->cmd_return(info->caller, contents, file_len);
+ ltk_free0(contents);
+ }
+ }
+ ltk_free0(info->tmpfile);
+ ltk_array_delete(cmd, shared_data.cmds, i, 1);
+ break;
+ }
+ }
+ }
+ while (!ltk_next_event(
+ shared_data.renderdata,
+ ltk_array_get_buf(shared_data.rwindows),
+ ltk_array_len(shared_data.rwindows),
+ shared_data.clipboard, shared_data.cur_kbd, &event)) {
+ ltk_handle_event(&event);
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ ltk_timespecsub(&now, &lasttimer, &elapsed);
+ /* Note: it should be safe to give the same pointer as the first and
+ last argument, as long as ltk_timespecsub/add isn't changed incompatibly */
+ size_t i = 0;
+ while (i < timers_num) {
+ ltk_timespecsub(&timers[i].remaining, &elapsed, &timers[i].remaining);
+ if (timers[i].remaining.tv_sec < 0 ||
+ (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) {
+ timers[i].callback(timers[i].data);
+ if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) {
+ /* remove timer because it has no repeat */
+ memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1));
+ } else {
+ ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining);
+ i++;
+ }
+ } else {
+ i++;
+ }
+ }
+ lasttimer = now;
+
+ for (size_t i = 0; i < shared_data.windows->len; i++) {
+ ltk_window *window = shared_data.windows->buf[i];
+ if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
+ window->widget.vtable->draw(LTK_CAST_WIDGET(window), NULL, 0, 0, (ltk_rect){0, 0, 0, 0});
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ ltk_timespecsub(&now, &last, &elapsed);
+ /* FIXME: configure framerate */
+ if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
+ sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
+ nanosleep(&sleep_time, NULL);
+ }
+ last = now;
+ }
+
+ ltk_deinit();
+
+ return 0;
+}
+
+void
+ltk_deinit(void) {
+ if (running)
+ return;
+ if (shared_data.cmds) {
+ for (size_t i = 0; i < ltk_array_len(shared_data.cmds); i++) {
+ /* FIXME: maybe kill child processes? */
+ ltk_free((ltk_array_get(shared_data.cmds, i)).tmpfile);
+ }
+ ltk_array_destroy(cmd, shared_data.cmds);
+ }
+ shared_data.cmds = NULL;
+ /* FIXME: also check for overwritten methods everywhere! */
+ if (shared_data.windows) {
+ for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) {
+ ltk_window *window = ltk_array_get(shared_data.windows, i);
+ ltk_widget_destroy(LTK_CAST_WIDGET(window), 0);
+ }
+ ltk_array_destroy(window, shared_data.windows);
+ }
+ shared_data.windows = NULL;
+ if (shared_data.rwindows)
+ ltk_array_destroy(rwindow, shared_data.rwindows);
+ shared_data.rwindows = NULL;
+ ltk_config_cleanup();
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (widget_funcs[i].cleanup)
+ widget_funcs[i].cleanup();
+ }
+ if (shared_data.text_context)
+ ltk_text_context_destroy(shared_data.text_context);
+ shared_data.text_context = NULL;
+ if (shared_data.clipboard)
+ ltk_clipboard_destroy(shared_data.clipboard);
+ shared_data.clipboard = NULL;
+ ltk_events_cleanup();
+ if (shared_data.renderdata) {
+ ltk_uninitialize_theme();
+ renderer_destroy(shared_data.renderdata);
+ }
+ shared_data.renderdata = NULL;
+}
+
+void
+ltk_quit(void) {
+ /* FIXME: maybe prevent other events from running? */
+ running = 0;
+}
+
+/* FIXME: check everywhere if initialized already */
+ltk_window *
+ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h) {
+ /* FIXME: more asserts, or maybe global "initialized" flag */
+ ltk_assert(shared_data.renderdata != NULL);
+ ltk_assert(shared_data.windows != NULL);
+ ltk_assert(shared_data.rwindows != NULL);
+ ltk_window *window = ltk_window_create_intern(shared_data.renderdata, title, x, y, w, h);
+ ltk_array_append(window, shared_data.windows, window);
+ ltk_array_append(rwindow, shared_data.rwindows, window->renderwindow);
+ return window;
+}
+
+void
+ltk_window_destroy(ltk_widget *self, int shallow) {
+ /* FIXME: would it make sense to do something with 'shallow' here? */
+ (void)shallow;
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ for (size_t i = 0; i < ltk_array_len(shared_data.windows); i++) {
+ if (ltk_array_get(shared_data.windows, i) == window) {
+ ltk_array_delete(window, shared_data.windows, i, 1);
+ ltk_array_delete(rwindow, shared_data.rwindows, i, 1);
+ break;
+ }
+ }
+ ltk_window_destroy_intern(window);
+}
+
+ltk_clipboard *
+ltk_get_clipboard(void) {
+ /* FIXME: what to do when not initialized? */
+ return shared_data.clipboard;
+}
+
+/* FIXME: optimize timer handling - maybe also a sort of priority queue */
+/* FIXME: JUST USE A GENERIC DYNAMIC ARRAY ALREADY!!!!! */
+void
+ltk_unregister_timer(int timer_id) {
+ for (size_t i = 0; i < timers_num; i++) {
+ if (timers[i].id == timer_id) {
+ memmove(
+ timers + i,
+ timers + i + 1,
+ sizeof(ltk_timer) * (timers_num - i - 1)
+ );
+ timers_num--;
+ size_t sz = ideal_array_size(timers_alloc, timers_num);
+ if (sz != timers_alloc) {
+ timers_alloc = sz;
+ timers = ltk_reallocarray(
+ timers, sz, sizeof(ltk_timer)
+ );
+ }
+ return;
+ }
+ }
+}
+
+/* repeat <= 0 means no repeat, first <= 0 means run as soon as possible */
+int
+ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg data), ltk_callback_arg data) {
+ if (first < 0)
+ first = 0;
+ if (repeat < 0)
+ repeat = 0;
+ if (timers_num == timers_alloc) {
+ timers_alloc = ideal_array_size(timers_alloc, timers_num + 1);
+ timers = ltk_reallocarray(
+ timers, timers_alloc, sizeof(ltk_timer)
+ );
+ }
+ /* FIXME: better finding of id */
+ /* FIXME: maybe store sorted by id */
+ int id = 0;
+ for (size_t i = 0; i < timers_num; i++) {
+ if (timers[i].id >= id)
+ id = timers[i].id + 1;
+ }
+ ltk_timer *t = &timers[timers_num++];
+ t->callback = callback;
+ t->data = data;
+ t->repeat.tv_sec = repeat / 1000;
+ t->repeat.tv_nsec = (repeat % 1000) * 1000;
+ t->remaining.tv_sec = first / 1000;
+ t->remaining.tv_nsec = (first % 1000) * 1000;
+ t->id = id;
+ return id;
+}
+
+/* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h
+ uses 1 on success, so this is all a bit confusing */
+/* FIXME: switch away from ini.h */
+static int
+ltk_ini_handler(void *renderdata, const char *widget, const char *prop, const char *value) {
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (widget_funcs[i].ini_handler && !strcmp(widget, widget_funcs[i].name)) {
+ widget_funcs[i].ini_handler(renderdata, prop, value);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* FIXME: don't call ltk_fatal, instead return error from ltk_init */
+static void
+ltk_load_theme(const char *path) {
+ /* FIXME: give line number in error message */
+ if (ini_parse(path, ltk_ini_handler, shared_data.renderdata) != 0) {
+ ltk_warn("Unable to load theme.\n");
+ }
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (widget_funcs[i].fill_theme_defaults) {
+ if (widget_funcs[i].fill_theme_defaults(shared_data.renderdata)) {
+ ltk_uninitialize_theme();
+ ltk_fatal("Unable to load theme defaults.\n");
+ }
+ }
+ }
+}
+
+static void
+ltk_uninitialize_theme(void) {
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (widget_funcs[i].uninitialize_theme)
+ widget_funcs[i].uninitialize_theme(shared_data.renderdata);
+ }
+}
+
+static int
+handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) {
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
+ if (!widget_funcs[i].register_keypress)
+ return 1;
+ return widget_funcs[i].register_keypress(name, nlen, b);
+ }
+ }
+ return 1;
+}
+
+static int
+handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) {
+ for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
+ if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
+ if (!widget_funcs[i].register_keyrelease)
+ return 1;
+ return widget_funcs[i].register_keyrelease(name, nlen, b);
+ }
+ }
+ return 1;
+}
+
+int
+ltk_call_cmd(ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) {
+ /* FIXME: support environment variable $TMPDIR */
+ ltk_cmdinfo info = {NULL, NULL, -1};
+ info.tmpfile = ltk_strdup("/tmp/ltk.XXXXXX");
+ int fd = mkstemp(info.tmpfile);
+ if (fd == -1) {
+ ltk_warn_errno("Unable to create temporary file while trying to run command '%.*s'\n", (int)cmdlen, cmd);
+ ltk_free0(info.tmpfile);
+ return 1;
+ }
+ close(fd);
+ /* FIXME: give file descriptor directly to modified version of ltk_write_file */
+ char *errstr = NULL;
+ if (ltk_write_file(info.tmpfile, text, textlen, &errstr)) {
+ ltk_warn("Unable to write to file '%s' while trying to run command '%.*s': %s\n", info.tmpfile, (int)cmdlen, cmd, errstr);
+ unlink(info.tmpfile);
+ ltk_free0(info.tmpfile);
+ return 1;
+ }
+ int pid = -1;
+ if ((pid = ltk_parse_run_cmd(cmd, cmdlen, info.tmpfile)) <= 0) {
+ /* FIXME: errno */
+ ltk_warn("Unable to run command '%.*s'\n", (int)cmdlen, cmd);
+ unlink(info.tmpfile);
+ ltk_free0(info.tmpfile);
+ return 1;
+ }
+ info.pid = pid;
+ info.caller = caller;
+ ltk_array_append(cmd, shared_data.cmds, info);
+ return 0;
+}
+
+static void
+ltk_handle_event(ltk_event *event) {
+ size_t kbd_idx;
+ if (event->type == LTK_KEYBOARDCHANGE_EVENT) {
+ /* FIXME: emit event */
+ if (ltk_config_get_language_index(event->keyboard.new_kbd, &kbd_idx))
+ ltk_warn("No language mapping for language \"%s\".\n", event->keyboard.new_kbd);
+ else
+ shared_data.cur_kbd = kbd_idx;
+ } else {
+ if (event->any.window_id < ltk_array_len(shared_data.windows)) {
+ ltk_window_handle_event(ltk_array_get(shared_data.windows, event->any.window_id), event);
+ }
+ }
+}
+
+ltk_text_line *
+ltk_text_line_create_default(uint16_t font_size, char *text, int take_over_text, int width) {
+ return ltk_text_line_create(shared_data.text_context, font_size, text, take_over_text, width);
+}
diff --git a/src/ltk.h b/src/ltk.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2016-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2016-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
@@ -14,110 +14,38 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _LTK_H_
-#define _LTK_H_
+#ifndef LTK_H
+#define LTK_H
+#include <stddef.h>
#include <stdint.h>
-#include <stdlib.h>
-#include "proto_types.h"
-typedef struct {
- char *text;
- size_t len;
- int contains_nul;
-} ltk_cmd_token;
-
-typedef enum {
- LTK_EVENT_RESIZE = 1 << 0,
- LTK_EVENT_BUTTON = 1 << 1,
- LTK_EVENT_KEY = 1 << 2,
- LTK_EVENT_MENU = 1 << 3
-} ltk_userevent_type;
-
-/*
- Historical note concerning ltk_window: This code was originally copied
- from my previous attempt at creating a GUI library, which was meant to
- be a regular C library and support multiple windows. Since this version
- of LTK doesn't support multiple windows (well, the calling script can
- just run more instances of ltkd if needed), having an ltk_window struct
- passed around is kind of unnecessary. I'm too lazy to change that now,
- though, so it's just going to stay that way.
-*/
-
-/* FIXME: fix this ugliness; remove circular dependencies */
-typedef struct ltk_window ltk_window;
-typedef struct ltk_text_context ltk_text_context;
-typedef struct ltk_surface ltk_surface;
-typedef struct ltk_window_theme ltk_window_theme;
-
-#include "widget.h"
-#include "surface_cache.h"
#include "clipboard.h"
-#include "event.h"
-
-struct ltk_window {
- ltk_renderdata *renderdata;
- ltk_surface_cache *surface_cache;
- ltk_text_context *text_context;
- ltk_clipboard *clipboard;
- ltk_surface *surface;
- ltk_widget *root_widget;
- ltk_widget *hover_widget;
- ltk_widget *active_widget;
- ltk_widget *pressed_widget;
- void (*other_event) (struct ltk_window *, ltk_event *event);
-
- /* PID of external command called e.g. by text widget to edit text.
- ON exit, cmd_caller->vtable->cmd_return is called with the text
- the external command wrote to a file. */
- int cmd_pid;
- char *cmd_tmpfile;
- char *cmd_caller;
+#include "widget.h"
+#include "window.h"
+#include "text.h"
- ltk_rect rect;
- ltk_window_theme *theme;
- ltk_rect dirty_rect;
- size_t cur_kbd;
- /* FIXME: generic array */
- ltk_widget **popups;
- size_t popups_num;
- size_t popups_alloc;
- /* This is a hack so ltk_window_unregister_all_popups can
- call hide for all popup widgets even if the hide function
- already calls ltk_window_unregister_popup */
- char popups_locked;
-};
+int ltk_init(void);
+void ltk_deinit(void);
+void ltk_quit(void);
+int ltk_mainloop(void);
-#include "color.h"
+void ltk_unregister_timer(int timer_id);
+int ltk_register_timer(long first, long repeat, void (*callback)(ltk_callback_arg data), ltk_callback_arg data);
-struct ltk_window_theme {
- int border_width;
- int font_size;
- char *font;
- ltk_color fg;
- ltk_color bg;
-};
+/* These are here so they can be added to the global array in ltk.c */
+ltk_window *ltk_window_create(const char *title, int x, int y, unsigned int w, unsigned int h);
+void ltk_window_destroy(ltk_widget *self, int shallow);
-int ltk_window_call_cmd(ltk_window *window, ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen);
-void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
-void ltk_queue_event(ltk_window *window, ltk_userevent_type type, const char *id, const char *data);
-void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event);
-void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget);
-void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release);
-void ltk_quit(ltk_window *window);
+/* FIXME: allow piping text instead of writing to temporary file */
+/* FIXME: how to avoid bad things happening while external program open? maybe store cmd widget somewhere (but could be multiple!) and check if widget to destroy is one of those
+-> alternative: store all widgets in array and only give out IDs, then when returning from cmd, widget is already destroyed and can be ignored
+-> first option maybe just set callback, etc. of current cmd to NULL so widget can still be destroyed */
+int ltk_call_cmd(ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen);
-void ltk_unregister_timer(int timer_id);
-int ltk_register_timer(long first, long repeat, void (*callback)(void *), void *data);
-void ltk_window_register_popup(ltk_window *window, ltk_widget *popup);
-void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
-void ltk_window_unregister_all_popups(ltk_window *window);
-int ltk_handle_lock_client(ltk_window *window, int client);
-int ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data);
-int ltk_queue_sock_write(int client, const char *str, int len);
-int ltk_queue_sock_write_fmt(int client, const char *fmt, ...);
+/* convenience function to use the default text context */
+ltk_text_line *ltk_text_line_create_default(uint16_t font_size, char *text, int take_over_text, int width);
-ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y);
-ltk_point ltk_global_to_widget_pos(ltk_widget *widget, int x, int y);
-void ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget);
+ltk_clipboard *ltk_get_clipboard(void);
-#endif
+#endif /* LTK_H */
diff --git a/src/ltkc.c b/src/ltkc.c
@@ -1,268 +0,0 @@
-/*
- * Copyright (c) 2021-2023 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 <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <stddef.h>
-#include <unistd.h>
-#include <time.h>
-#include <errno.h>
-#include <inttypes.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-#include "util.h"
-#include "memory.h"
-#include "macros.h"
-
-#define BLK_SIZE 128
-char tmp_buf[BLK_SIZE];
-
-static struct {
- char *in_buffer; /* text that is read from stdin and written to the socket */
- int in_len;
- int in_alloc;
- char *out_buffer; /* text that is read from the socket and written to stdout */
- int out_len;
- int out_alloc;
-} io_buffers;
-
-static char *ltk_dir = NULL;
-static char *sock_path = NULL;
-static int sockfd = -1;
-
-int main(int argc, char *argv[]) {
- char num[12];
- int bs = 0;
- int last_newline = 1;
- int in_str = 0;
- uint32_t seq = 0;
- int maxfd;
- int infd = fileno(stdin);
- int outfd = fileno(stdout);
- struct sockaddr_un un;
- fd_set rfds, wfds, rallfds, wallfds;
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 20000;
- size_t path_size;
-
- if (argc != 2) {
- (void)fprintf(stderr, "USAGE: ltkc <socket id>\n");
- return 1;
- }
-
- ltk_dir = ltk_setup_directory();
- if (!ltk_dir) {
- (void)fprintf(stderr, "Unable to setup ltk directory.\n");
- return 1;
- }
-
- /* 7 because of "/", ".sock", and '\0' */
- path_size = strlen(ltk_dir) + strlen(argv[1]) + 7;
- sock_path = ltk_malloc(path_size);
- snprintf(sock_path, path_size, "%s/%s.sock", ltk_dir, argv[1]);
-
- if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
- perror("Socket error");
- return -1;
- }
- memset(&un, 0, sizeof(un));
- un.sun_family = AF_UNIX;
- if (path_size > sizeof(un.sun_path)) {
- (void)fprintf(stderr, "Socket path too long.\n");
- return 1;
- }
- strcpy(un.sun_path, sock_path);
- if (connect(sockfd, (struct sockaddr *)&un, offsetof(struct sockaddr_un, sun_path) + path_size) < 0) {
- perror("Socket error");
- return -2;
- }
- if (set_nonblock(sockfd)) {
- (void)fprintf(stderr, "Unable to set socket to non-blocking mode.\n");
- return 1;
- } else if (set_nonblock(infd)) {
- (void)fprintf(stderr, "Unable to set stdin to non-blocking mode.\n");
- return 1;
- } else if (set_nonblock(outfd)) {
- (void)fprintf(stderr, "Unable to set stdout to non-blocking mode.\n");
- return 1;
- }
-
- io_buffers.in_buffer = ltk_malloc(BLK_SIZE);
- io_buffers.in_alloc = BLK_SIZE;
-
- io_buffers.out_buffer = ltk_malloc(BLK_SIZE);
- io_buffers.out_alloc = BLK_SIZE;
-
- FD_ZERO(&rallfds);
- FD_ZERO(&wallfds);
-
- FD_SET(sockfd, &rallfds);
- FD_SET(infd, &rallfds);
- FD_SET(sockfd, &wallfds);
- FD_SET(outfd, &wallfds);
- maxfd = sockfd > infd ? sockfd : infd;
- if (maxfd < outfd)
- maxfd = outfd;
-
- struct timespec now, elapsed, last, sleep_time;
- clock_gettime(CLOCK_MONOTONIC, &last);
- sleep_time.tv_sec = 0;
-
- while (1) {
- if (!FD_ISSET(sockfd, &rallfds) && io_buffers.out_len == 0)
- break;
- rfds = rallfds;
- wfds = wallfds;
- select(maxfd + 1, &rfds, &wfds, NULL, &tv);
-
- /* FIXME: make all this buffer handling a bit more intelligent */
- if (FD_ISSET(sockfd, &rfds)) {
- while (1) {
- ltk_grow_string(&io_buffers.out_buffer,
- &io_buffers.out_alloc,
- io_buffers.out_len + BLK_SIZE);
- int nread = read(sockfd,
- io_buffers.out_buffer + io_buffers.out_len,
- BLK_SIZE);
- if (nread < 0) {
- /* FIXME: distinguish errors */
- break;
- } else if (nread == 0) {
- FD_CLR(sockfd, &rallfds);
- FD_CLR(sockfd, &wallfds);
- break;
- } else {
- io_buffers.out_len += nread;
- }
- }
- }
-
- if (FD_ISSET(infd, &rfds)) {
- while (1) {
- int nread = read(infd, tmp_buf, BLK_SIZE);
- if (nread < 0) {
- break;
- } else if (nread == 0) {
- FD_CLR(infd, &rallfds);
- break;
- } else {
- for (int i = 0; i < nread; i++) {
- if (last_newline) {
- int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", seq);
- if (numlen < 0 || (unsigned)numlen >= sizeof(num))
- ltk_fatal("There's a bug in the universe.\n");
- ltk_grow_string(
- &io_buffers.in_buffer,
- &io_buffers.in_alloc,
- io_buffers.in_len + numlen
- );
- memcpy(io_buffers.in_buffer + io_buffers.in_len, num, numlen);
- io_buffers.in_len += numlen;
- last_newline = 0;
- seq++;
- }
- if (tmp_buf[i] == '\\') {
- bs++;
- bs %= 2;
- } else if (tmp_buf[i] == '"' && !bs) {
- in_str = !in_str;
- } else if (tmp_buf[i] == '\n' && !in_str) {
- last_newline = 1;
- } else {
- bs = 0;
- }
- if (io_buffers.in_len == io_buffers.in_alloc) {
- ltk_grow_string(
- &io_buffers.in_buffer,
- &io_buffers.in_alloc,
- io_buffers.in_len + 1
- );
- }
- io_buffers.in_buffer[io_buffers.in_len++] = tmp_buf[i];
- }
- }
- }
- }
-
- if (FD_ISSET(sockfd, &wfds)) {
- while (io_buffers.in_len > 0) {
- int maxwrite = BLK_SIZE > io_buffers.in_len ?
- io_buffers.in_len : BLK_SIZE;
- int nwritten = write(sockfd, io_buffers.in_buffer, maxwrite);
- if (nwritten <= 0) {
- break;
- } else {
- memmove(io_buffers.in_buffer,
- io_buffers.in_buffer + nwritten,
- io_buffers.in_len - nwritten);
- io_buffers.in_len -= nwritten;
- }
- }
- }
-
- if (FD_ISSET(outfd, &wfds)) {
- while (io_buffers.out_len > 0) {
- int maxwrite = BLK_SIZE > io_buffers.out_len ?
- io_buffers.out_len : BLK_SIZE;
- int nwritten = write(outfd, io_buffers.out_buffer, maxwrite);
- if (nwritten <= 0) {
- break;
- } else {
- memmove(io_buffers.out_buffer,
- io_buffers.out_buffer + nwritten,
- io_buffers.out_len - nwritten);
- io_buffers.out_len -= nwritten;
- }
- }
- }
- clock_gettime(CLOCK_MONOTONIC, &now);
- ltk_timespecsub(&now, &last, &elapsed);
- /* FIXME: configure framerate */
- if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
- sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
- nanosleep(&sleep_time, NULL);
- }
- last = now;
- }
-
- ltk_cleanup();
-
- return 0;
-}
-
-void
-ltk_log_msg(const char *mode, const char *format, va_list args) {
- fprintf(stderr, "ltkc %s: ", mode);
- vfprintf(stderr, format, args);
-}
-
-void
-ltk_cleanup() {
- if (sockfd >= 0)
- close(sockfd);
- if (ltk_dir)
- ltk_free(ltk_dir);
- if (sock_path)
- ltk_free(sock_path);
- if (io_buffers.in_buffer)
- ltk_free(io_buffers.in_buffer);
- if (io_buffers.out_buffer)
- ltk_free(io_buffers.out_buffer);
-}
diff --git a/src/ltkc_img.c b/src/ltkc_img.c
@@ -1,26 +0,0 @@
-/* This is just a temporary hack to preprocess an image for sending it to
- ltkd. The nicer way for this would be to have a special case for the
- "image create" command in ltkc, but I was too lazy to implement that
- right now. */
-
-#include <stdio.h>
-
-int main(int argc, char *argv[]) {
- (void)argc;
- (void)argv;
- int c;
- while ((c = getchar()) != EOF) {
- switch (c) {
- case '\\':
- fputs("\\\\", stdout);
- break;
- case '"':
- fputs("\\\"", stdout);
- break;
- default:
- putchar(c);
- }
- }
-
- return 0;
-}
diff --git a/src/ltkd.c b/src/ltkd.c
@@ -1,1914 +0,0 @@
-/* FIXME: Figure out how to properly print window id */
-/* FIXME: error checking in tokenizer (is this necessary?) */
-/* FIXME: strip whitespace at end of lines in socket format */
-/*
- * Copyright (c) 2016-2023 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 <time.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include <unistd.h>
-#include <signal.h>
-#include <stdint.h>
-#include <locale.h>
-#include <inttypes.h>
-
-#include <sys/un.h>
-#include <sys/stat.h>
-#include <sys/wait.h>
-#include <sys/types.h>
-#include <sys/select.h>
-#include <sys/socket.h>
-
-#include "ini.h"
-#include "khash.h"
-
-#include "graphics.h"
-#include "surface_cache.h"
-#include "theme.h"
-#include "memory.h"
-#include "color.h"
-#include "rect.h"
-#include "widget.h"
-#include "ltk.h"
-#include "util.h"
-#include "text.h"
-#include "grid.h"
-/* #include "draw.h" */
-#include "button.h"
-#include "entry.h"
-#include "label.h"
-#include "scrollbar.h"
-#include "box.h"
-#include "menu.h"
-#include "image.h"
-#include "image_widget.h"
-#include "macros.h"
-#include "config.h"
-
-#define MAX_WINDOW_FONT_SIZE 200
-
-#define MAX_SOCK_CONNS 20
-#define READ_BLK_SIZE 128
-#define WRITE_BLK_SIZE 128
-
-struct token_list {
- ltk_cmd_token *tokens;
- /* FIXME: size_t everywhere */
- int num_tokens;
- int num_alloc;
-};
-
-/* FIXME: switch to size_t */
-static struct ltk_sock_info {
- int fd; /* file descriptor for socket connection */
- int event_mask; /* events to send to socket */
- char *read; /* text read from socket */
- int read_len; /* length of text in read buffer */
- int read_alloc; /* size of read buffer */
- char *to_write; /* text to be written to socket */
- int write_len; /* length of text in write buffer */
- int write_cur; /* length of text already written */
- int write_alloc; /* size of write buffer */
- /* stuff for tokenizing */
- int in_token; /* last read char is inside token */
- int offset; /* offset from removing backslashes */
- int in_str; /* last read char is inside string */
- int read_cur; /* length of text already tokenized */
- int bs; /* last char was non-escaped backslash */
- struct token_list tokens; /* current tokens */
- uint32_t last_seq; /* sequence number of last request processed */
-} sockets[MAX_SOCK_CONNS];
-
-typedef struct {
- void (*callback)(void *);
- void *data;
- struct timespec repeat;
- struct timespec remaining;
- int id;
-} ltk_timer;
-
-static ltk_timer *timers = NULL;
-static size_t timers_num = 0;
-static size_t timers_alloc = 0;
-
-static int daemonize_flag = 1;
-
-static int ltk_mainloop(ltk_window *window);
-static char *get_sock_path(char *basedir, Window id);
-static FILE *open_log(char *dir);
-static void daemonize(void);
-static ltk_window *ltk_create_window(const char *title, int x, int y,
- unsigned int w, unsigned int h);
-static void ltk_destroy_window(ltk_window *window);
-static void ltk_redraw_window(ltk_window *window);
-static void ltk_window_other_event(ltk_window *window, ltk_event *event);
-static void ltk_handle_event(ltk_window *window, ltk_event *event);
-
-static void ltk_load_theme(ltk_window *window, const char *path);
-static void ltk_uninitialize_theme(ltk_window *window);
-static int ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value);
-static int ltk_window_fill_theme_defaults(ltk_window *window);
-static int ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value);
-static void ltk_window_uninitialize_theme(ltk_window *window);
-
-static int read_sock(struct ltk_sock_info *sock);
-static int push_token(struct token_list *tl, char *token);
-static int read_sock(struct ltk_sock_info *sock);
-static int write_sock(struct ltk_sock_info *sock);
-static int tokenize_command(struct ltk_sock_info *sock);
-static int ltk_set_root_widget_cmd(ltk_window *window, ltk_cmd_token *tokens, int num_tokens, ltk_error *err);
-static int process_commands(ltk_window *window, int client);
-static int add_client(int fd);
-static int listen_sock(const char *sock_path);
-static int accept_sock(int listenfd);
-
-static short maxsocket = -1;
-static short running = 1;
-static short sock_write_available = 0;
-static char *ltk_dir = NULL;
-static FILE *ltk_logfile = NULL;
-static char *sock_path = NULL;
-/* Note: Most functions still take this explicitly because it wasn't
- global originally, but that's just the way it is. */
-static ltk_window *main_window = NULL;
-
-typedef struct {
- char *name;
- int (*ini_handler)(ltk_window *, const char *, const char *);
- int (*fill_theme_defaults)(ltk_window *);
- void (*uninitialize_theme)(ltk_window *);
- int (*register_keypress)(const char *, size_t, ltk_keypress_binding);
- int (*register_keyrelease)(const char *, size_t, ltk_keyrelease_binding);
- void (*cleanup)(void);
- int (*cmd)(ltk_window *, ltk_cmd_token *, size_t, ltk_error *);
-} ltk_widget_funcs;
-
-/* FIXME: use binary search when searching for the widget */
-ltk_widget_funcs widget_funcs[] = {
- {
- .name = "box",
- .ini_handler = NULL,
- .fill_theme_defaults = NULL,
- .uninitialize_theme = NULL,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_box_cmd
- },
- {
- .name = "button",
- .ini_handler = <k_button_ini_handler,
- .fill_theme_defaults = <k_button_fill_theme_defaults,
- .uninitialize_theme = <k_button_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_button_cmd
- },
- {
- .name = "entry",
- .ini_handler = <k_entry_ini_handler,
- .fill_theme_defaults = <k_entry_fill_theme_defaults,
- .uninitialize_theme = <k_entry_uninitialize_theme,
- .register_keypress = <k_entry_register_keypress,
- .register_keyrelease = <k_entry_register_keyrelease,
- .cleanup = <k_entry_cleanup,
- .cmd = <k_entry_cmd
- },
- {
- .name = "grid",
- .ini_handler = NULL,
- .fill_theme_defaults = NULL,
- .uninitialize_theme = NULL,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_grid_cmd
- },
- {
- .name = "label",
- .ini_handler = <k_label_ini_handler,
- .fill_theme_defaults = <k_label_fill_theme_defaults,
- .uninitialize_theme = <k_label_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_label_cmd
- },
- {
- .name = "image",
- .ini_handler = NULL,
- .fill_theme_defaults = NULL,
- .uninitialize_theme = NULL,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_image_widget_cmd
- },
- {
- .name = "menu",
- .ini_handler = <k_menu_ini_handler,
- .fill_theme_defaults = <k_menu_fill_theme_defaults,
- .uninitialize_theme = <k_menu_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_menu_cmd
- },
- {
- .name = "menuentry",
- .ini_handler = <k_menuentry_ini_handler,
- .fill_theme_defaults = <k_menuentry_fill_theme_defaults,
- .uninitialize_theme = <k_menuentry_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_menuentry_cmd
- },
- {
- .name = "submenu",
- .ini_handler = <k_submenu_ini_handler,
- .fill_theme_defaults = <k_submenu_fill_theme_defaults,
- .uninitialize_theme = <k_submenu_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = <k_menu_cmd
- },
- {
- .name = "submenuentry",
- .ini_handler = <k_submenuentry_ini_handler,
- .fill_theme_defaults = <k_submenuentry_fill_theme_defaults,
- .uninitialize_theme = <k_submenuentry_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- /* This "widget" is only needed to have separate styles for regular
- menu entries and submenu entries. "submenu" is just an alias for
- "menu" in most cases - it's just needed when creating a menu to
- decide if it's a submenu or not.
- FIXME: is that even necessary? Why can't it just decide if it's
- a submenu based on whether it has a parent or not?
- -> I guess right-click menus are also just submenus, so they
- need to set it explicitly, but wasn't there another reaseon? */
- .cmd = NULL
- },
- {
- .name = "scrollbar",
- .ini_handler = <k_scrollbar_ini_handler,
- .fill_theme_defaults = <k_scrollbar_fill_theme_defaults,
- .uninitialize_theme = <k_scrollbar_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = NULL
- },
- {
- /* Handler for general widget key bindings. */
- .name = "widget",
- .ini_handler = NULL,
- .fill_theme_defaults = NULL,
- .uninitialize_theme = NULL,
- .register_keypress = <k_widget_register_keypress,
- .register_keyrelease = <k_widget_register_keyrelease,
- .cleanup = <k_widget_cleanup,
- .cmd = NULL
- },
- {
- /* Handler for window theme. */
- .name = "window",
- .ini_handler = <k_window_ini_handler,
- .fill_theme_defaults = <k_window_fill_theme_defaults,
- .uninitialize_theme = <k_window_uninitialize_theme,
- .register_keypress = NULL,
- .register_keyrelease = NULL,
- .cleanup = NULL,
- .cmd = NULL
- }
-};
-
-int
-main(int argc, char *argv[]) {
- setlocale(LC_CTYPE, "");
- XSetLocaleModifiers("");
- int ch;
- char *title = "LTK Window";
- while ((ch = getopt(argc, argv, "dt:")) != -1) {
- switch (ch) {
- case 't':
- title = optarg;
- break;
- case 'd':
- daemonize_flag = 0;
- break;
- default:
- ltk_fatal("USAGE: ltkd [-t title]\n");
- }
- }
-
- ltk_dir = ltk_setup_directory();
- if (!ltk_dir) ltk_fatal_errno("Unable to setup ltk directory.\n");
- ltk_logfile = open_log(ltk_dir);
- if (!ltk_logfile) ltk_fatal_errno("Unable to open log file.\n");
-
- /* FIXME: move to widget_funcs? */
- ltk_widgets_init();
-
- /* FIXME: set window size properly - I only run it in a tiling WM
- anyways, so it doesn't matter, but still... */
- main_window = ltk_create_window(title, 0, 0, 500, 500);
-
- sock_path = get_sock_path(ltk_dir, renderer_get_window_id(main_window->renderdata));
- if (!sock_path) ltk_fatal_errno("Unable to allocate memory for socket path.\n");
-
- /* Note: sockets should be initialized to 0 because it is static */
- for (int i = 0; i < MAX_SOCK_CONNS; i++) {
- sockets[i].fd = -1; /* socket unused */
- /* initialize these just because I'm paranoid */
- sockets[i].read = NULL;
- sockets[i].to_write = NULL;
- sockets[i].tokens.tokens = NULL;
- }
-
- return ltk_mainloop(main_window);
-}
-
-/* FIXME: need to recalculate maxfd when removing client */
-static struct {
- fd_set rallfds, wallfds;
- int maxfd;
- int listenfd;
-} sock_state;
-
-/* FIXME: this is extremely dangerous right now because pretty much any command
- can be executed, so for instance the widget that caused the lock could also
- be destroyed, causing issues when this function returns */
-int
-ltk_handle_lock_client(ltk_window *window, int client) {
- if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
- return 0;
- fd_set rfds, wfds, rallfds, wallfds;
- int clifd = sockets[client].fd;
- FD_ZERO(&rallfds);
- FD_ZERO(&wallfds);
- FD_SET(clifd, &rallfds);
- FD_SET(clifd, &wallfds);
- int retval;
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 0;
- struct timespec now, elapsed, last, sleep_time;
- clock_gettime(CLOCK_MONOTONIC, &last);
- sleep_time.tv_sec = 0;
- while (1) {
- rfds = rallfds;
- wfds = wallfds;
- retval = select(clifd + 1, &rfds, &wfds, NULL, &tv);
-
- if (retval > 0) {
- if (FD_ISSET(clifd, &rfds)) {
- int ret;
- while ((ret = read_sock(&sockets[client])) == 1) {
- int pret;
- if ((pret = process_commands(window, client)) == 1)
- return 1;
- else if (pret == -1)
- return 0;
- }
- /* FIXME: maybe also return on read error? or would that be dangerous? */
- if (ret == 0) {
- FD_CLR(clifd, &sock_state.rallfds);
- FD_CLR(clifd, &sock_state.wallfds);
- ltk_widget_remove_client(client);
- sockets[clifd].fd = -1;
- close(clifd);
- int newmaxsocket = -1;
- for (int j = 0; j <= maxsocket; j++) {
- if (sockets[j].fd >= 0)
- newmaxsocket = j;
- }
- maxsocket = newmaxsocket;
- if (maxsocket == -1) {
- ltk_quit(window);
- break;
- }
- return 0;
- }
- }
- if (FD_ISSET(clifd, &wfds)) {
- /* FIXME: call in loop like above */
- write_sock(&sockets[client]);
- }
- }
- clock_gettime(CLOCK_MONOTONIC, &now);
- ltk_timespecsub(&now, &last, &elapsed);
- /* FIXME: configure framerate */
- if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
- sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
- nanosleep(&sleep_time, NULL);
- }
- last = now;
- }
- return 0;
-}
-
-static int
-ltk_mainloop(ltk_window *window) {
- ltk_event event;
- fd_set rfds, wfds;
- int retval;
- int clifd;
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec = 0;
-
- FD_ZERO(&sock_state.rallfds);
- FD_ZERO(&sock_state.wallfds);
-
- if ((sock_state.listenfd = listen_sock(sock_path)) < 0)
- ltk_fatal_errno("Error listening on socket.\n");
-
- FD_SET(sock_state.listenfd, &sock_state.rallfds);
- sock_state.maxfd = sock_state.listenfd;
-
- printf("%lu", renderer_get_window_id(main_window->renderdata));
- fflush(stdout);
- if (daemonize_flag)
- daemonize();
-
- /* FIXME: make time management smarter - maybe always figure out how long
- it will take until the next timer is due and then sleep if no other events
- are happening */
- struct timespec now, elapsed, last, lasttimer, sleep_time;
- clock_gettime(CLOCK_MONOTONIC, &last);
- lasttimer = last;
- sleep_time.tv_sec = 0;
-
- /* initialize keyboard mapping */
- ltk_generate_keyboard_event(window->renderdata, &event);
- ltk_handle_event(window, &event);
-
- int pid = -1;
- int wstatus = 0;
- while (running) {
- if (window->cmd_caller && (pid = waitpid(window->cmd_pid, &wstatus, WNOHANG)) > 0) {
- ltk_error err;
- ltk_widget *cmd_caller = ltk_get_widget(window->cmd_caller, LTK_WIDGET_ANY, &err);
- /* FIXME: should commands be split into read/write and block write commands during external editing? */
- /* FIXME: what if a new widget with same id was created in meantime? */
- if (!cmd_caller) {
- ltk_warn("Widget '%s' disappeared while text was being edited in external program\n", window->cmd_caller);
- } else if (cmd_caller->vtable->cmd_return) {
- size_t file_len = 0;
- char *errstr = NULL;
- char *contents = ltk_read_file(window->cmd_tmpfile, &file_len, &errstr);
- if (!contents) {
- ltk_warn("Unable to read file '%s' written by external command: %s\n", window->cmd_tmpfile, errstr);
- } else {
- cmd_caller->vtable->cmd_return(cmd_caller, contents, file_len);
- ltk_free(contents);
- }
- }
- ltk_free(window->cmd_caller);
- window->cmd_caller = NULL;
- window->cmd_pid = -1;
- unlink(window->cmd_tmpfile);
- ltk_free(window->cmd_tmpfile);
- window->cmd_tmpfile = NULL;
- }
- rfds = sock_state.rallfds;
- wfds = sock_state.wallfds;
- retval = select(sock_state.maxfd + 1, &rfds, &wfds, NULL, &tv);
- while (!ltk_next_event(window->renderdata, window->clipboard, window->cur_kbd, &event))
- ltk_handle_event(window, &event);
-
- if (retval > 0) {
- if (FD_ISSET(sock_state.listenfd, &rfds)) {
- if ((clifd = accept_sock(sock_state.listenfd)) < 0) {
- /* FIXME: Just log this! */
- ltk_fatal_errno("Error accepting socket connection.\n");
- }
- int i = add_client(clifd);
- FD_SET(clifd, &sock_state.rallfds);
- FD_SET(clifd, &sock_state.wallfds);
- if (clifd > sock_state.maxfd)
- sock_state.maxfd = clifd;
- if (i > maxsocket)
- maxsocket = i;
- continue;
- }
- for (int i = 0; i <= maxsocket; i++) {
- if ((clifd = sockets[i].fd) < 0)
- continue;
- if (FD_ISSET(clifd, &rfds)) {
- /* FIXME: better error handling - this assumes error
- is always because read would block */
- /* FIXME: maybe maximum number of iterations here to
- avoid choking on a lot of data? although such a
- large amount of data would probably cause other
- problems anyways */
- /* or maybe measure time and break after max time? */
- int ret;
- while ((ret = read_sock(&sockets[i])) == 1) {
- process_commands(window, i);
- }
- if (ret == 0) {
- ltk_widget_remove_client(i);
- FD_CLR(clifd, &sock_state.rallfds);
- FD_CLR(clifd, &sock_state.wallfds);
- sockets[i].fd = -1;
- /* FIXME: what to do on error? */
- close(clifd);
- int newmaxsocket = -1;
- for (int j = 0; j <= maxsocket; j++) {
- if (sockets[j].fd >= 0)
- newmaxsocket = j;
- }
- maxsocket = newmaxsocket;
- if (maxsocket == -1) {
- ltk_quit(window);
- break;
- }
- }
- }
- /* FIXME: maybe ignore SIGPIPE signal - then don't call FD_CLR
- for wallfds above but rather when write fails with EPIPE */
- /* -> this would possibly allow data to be written still in the
- hypothetical scenario that only the writing end of the socket
- is closed (and ltkd wouldn't crash if only the reading end is
- closed) */
- if (FD_ISSET(clifd, &wfds)) {
- /* FIXME: also call in loop like reading above */
- write_sock(&sockets[i]);
- }
- }
- }
-
- clock_gettime(CLOCK_MONOTONIC, &now);
- ltk_timespecsub(&now, &lasttimer, &elapsed);
- /* Note: it should be safe to give the same pointer as the first and
- last argument, as long as ltk_timespecsub/add isn't changed incompatibly */
- size_t i = 0;
- while (i < timers_num) {
- ltk_timespecsub(&timers[i].remaining, &elapsed, &timers[i].remaining);
- if (timers[i].remaining.tv_sec < 0 ||
- (timers[i].remaining.tv_sec == 0 && timers[i].remaining.tv_nsec == 0)) {
- timers[i].callback(timers[i].data);
- if (timers[i].repeat.tv_sec == 0 && timers[i].repeat.tv_nsec == 0) {
- /* remove timer because it has no repeat */
- memmove(timers + i, timers + i + 1, sizeof(ltk_timer) * (timers_num - i - 1));
- } else {
- ltk_timespecadd(&timers[i].remaining, &timers[i].repeat, &timers[i].remaining);
- i++;
- }
- } else {
- i++;
- }
- }
- lasttimer = now;
-
- if (window->dirty_rect.w != 0 && window->dirty_rect.h != 0) {
- ltk_redraw_window(window);
- window->dirty_rect.w = 0;
- window->dirty_rect.h = 0;
- }
-
- clock_gettime(CLOCK_MONOTONIC, &now);
- ltk_timespecsub(&now, &last, &elapsed);
- /* FIXME: configure framerate */
- if (elapsed.tv_sec == 0 && elapsed.tv_nsec < 20000000LL) {
- sleep_time.tv_nsec = 20000000LL - elapsed.tv_nsec;
- nanosleep(&sleep_time, NULL);
- }
- last = now;
- }
-
- ltk_cleanup();
-
- return 0;
-}
-
-/* largely copied from APUE... */
-/* am I breaking copyright here? */
-static void
-daemonize(void) {
- pid_t pid;
- struct sigaction sa;
-
- fflush(stdout);
- fflush(stderr);
- fflush(ltk_logfile);
-
- if ((pid = fork()) < 0)
- ltk_fatal_errno("Can't fork.\n");
- else if (pid != 0)
- exit(0);
- setsid();
-
- sa.sa_handler = SIG_IGN;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = 0;
- if (sigaction(SIGHUP, &sa, NULL) < 0)
- ltk_fatal_errno("Unable to ignore SIGHUP.\n");
- if ((pid = fork()) < 0)
- ltk_fatal_errno("Can't fork.\n");
- else if (pid != 0)
- exit(0);
-
- if (chdir("/") < 0)
- ltk_fatal_errno("Can't change directory to root.\n");
-
- /* FIXME: why didn't I just use fclose() here? */
- /* FIXME: just print log to stdout and let this take care of redirection */
- close(fileno(stdin));
- /*close(fileno(stdout));
- close(fileno(stderr));*/
- open("/dev/null", O_RDWR);
- /*dup(0);
- dup(0);*/
- dup2(fileno(ltk_logfile), fileno(stdout));
- dup2(fileno(ltk_logfile), fileno(stderr));
-
- /* FIXME: Is it guaranteed that this will work? Will these fds
- always be opened on the lowest numbers? */
-}
-
-static char *
-get_sock_path(char *basedir, Window id) {
- int len;
- char *path;
-
- len = strlen(basedir);
- /* FIXME: MAKE SURE THIS IS ACTUALLY BIG ENOUGH! */
- path = ltk_malloc(len + 20);
- /* FIXME: also check for less than 0 */
- if (snprintf(path, len + 20, "%s/%lu.sock", basedir, id) >= len + 20)
- ltk_fatal("Tell lumidify to fix his code.\n");
-
- return path;
-}
-
-static FILE *
-open_log(char *dir) {
- FILE *f;
- char *path;
-
- path = ltk_strcat_useful(dir, "/ltkd.log");
- if (!path)
- return NULL;
- f = fopen(path, "a");
- if (!f) {
- ltk_free(path);
- return NULL;
- }
- ltk_free(path);
-
- return f;
-}
-
-void
-ltk_cleanup(void) {
- if (sock_state.listenfd >= 0)
- close(sock_state.listenfd);
- if (ltk_dir)
- ltk_free(ltk_dir);
- if (ltk_logfile)
- fclose(ltk_logfile);
- if (sock_path) {
- unlink(sock_path);
- ltk_free(sock_path);
- }
-
- for (int i = 0; i < MAX_SOCK_CONNS; i++) {
- if (sockets[i].fd >= 0)
- close(sockets[i].fd);
- if (sockets[i].read)
- ltk_free(sockets[i].read);
- if (sockets[i].to_write)
- ltk_free(sockets[i].to_write);
- if (sockets[i].tokens.tokens)
- ltk_free(sockets[i].tokens.tokens);
- }
-
- ltk_config_cleanup();
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (widget_funcs[i].cleanup)
- widget_funcs[i].cleanup();
- }
- ltk_events_cleanup();
- if (main_window) {
- ltk_uninitialize_theme(main_window);
- ltk_destroy_window(main_window);
- }
- main_window = NULL;
-}
-
-void
-ltk_quit(ltk_window *window) {
- (void)window;
- running = 0;
-}
-
-void
-ltk_log_msg(const char *mode, const char *format, va_list args) {
- char logtime[25]; /* FIXME: This should always be big enough, right? */
- time_t clock;
- struct tm *timeptr;
-
- time(&clock);
- timeptr = localtime(&clock);
- strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr);
-
- if (main_window)
- fprintf(stderr, "%s ltkd(%lu) %s: ", logtime, renderer_get_window_id(main_window->renderdata), mode);
- else
- fprintf(stderr, "%s ltkd(?) %s: ", logtime, mode);
- vfprintf(stderr, format, args);
-}
-
-static int
-ltk_set_root_widget_cmd(
- ltk_window *window,
- ltk_cmd_token *tokens,
- int num_tokens,
- ltk_error *err) {
- ltk_widget *widget;
- if (num_tokens != 2) {
- err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
- err->arg = -1;
- return 1;
- } else if (tokens[1].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 1;
- return 1;
- }
- widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
- if (!widget) {
- err->arg = 1;
- return 1;
- }
- window->root_widget = widget;
- widget->lrect.x = 0;
- widget->lrect.y = 0;
- widget->lrect.w = window->rect.w;
- widget->lrect.h = window->rect.h;
- widget->crect = widget->lrect;
- ltk_window_invalidate_rect(window, widget->lrect);
- ltk_widget_resize(widget);
-
- return 0;
-}
-
-void
-ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
- if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
- window->dirty_rect = rect;
- else
- window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
-}
-
-ltk_point
-ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) {
- ltk_widget *cur = widget;
- while (cur) {
- x += cur->lrect.x;
- y += cur->lrect.y;
- if (cur->popup)
- break;
- cur = cur->parent;
- }
- return (ltk_point){x, y};
-}
-
-ltk_point
-ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) {
- ltk_widget *cur = widget;
- while (cur) {
- x -= cur->lrect.x;
- y -= cur->lrect.y;
- if (cur->popup)
- break;
- cur = cur->parent;
- }
- return (ltk_point){x, y};
-}
-
-void
-ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) {
- ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
- ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h});
-}
-
-/* FIXME: generic event handling functions that take the actual uint32_t event type, etc. */
-int
-ltk_queue_specific_event(ltk_widget *widget, const char *type, uint32_t mask, const char *data) {
- int lock_client = -1;
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].lwmask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "eventl %s %s %s\n", widget->id, type, data
- );
- lock_client = widget->event_masks[i].client;
- } else if (widget->event_masks[i].wmask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "event %s %s %s\n", widget->id, type, data
- );
- }
- }
- if (lock_client >= 0) {
- if (ltk_handle_lock_client(widget->window, lock_client))
- return 1;
- }
- return 0;
-}
-
-static void
-ltk_redraw_window(ltk_window *window) {
- ltk_widget *ptr;
- if (!window) return;
- if (window->dirty_rect.x >= window->rect.w) return;
- if (window->dirty_rect.y >= window->rect.h) return;
- if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
- window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
- if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
- window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
- /* FIXME: this should use window->dirty_rect, but that doesn't work
- properly with double buffering */
- ltk_surface_fill_rect(window->surface, &window->theme->bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
- if (window->root_widget) {
- ptr = window->root_widget;
- ptr->vtable->draw(ptr, window->surface, 0, 0, window->rect);
- }
- /* last popup is the newest one, so draw that last */
- for (size_t i = 0; i < window->popups_num; i++) {
- ptr = window->popups[i];
- ptr->vtable->draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect));
- }
- renderer_swap_buffers(window->renderdata);
-}
-
-static void
-ltk_window_other_event(ltk_window *window, ltk_event *event) {
- ltk_widget *ptr = window->root_widget;
- if (event->type == LTK_CONFIGURE_EVENT) {
- ltk_window_unregister_all_popups(window);
- int w, h;
- w = event->configure.w;
- h = event->configure.h;
- int orig_w = window->rect.w;
- int orig_h = window->rect.h;
- if (orig_w != w || orig_h != h) {
- window->rect.w = w;
- window->rect.h = h;
- ltk_window_invalidate_rect(window, window->rect);
- ltk_surface_update_size(window->surface, w, h);
- if (ptr) {
- ptr->lrect.w = w;
- ptr->lrect.h = h;
- ptr->crect = ptr->lrect;
- ltk_widget_resize(ptr);
- }
- }
- } else if (event->type == LTK_EXPOSE_EVENT) {
- ltk_rect r;
- r.x = event->expose.x;
- r.y = event->expose.y;
- r.w = event->expose.w;
- r.h = event->expose.h;
- ltk_window_invalidate_rect(window, r);
- } else if (event->type == LTK_WINDOWCLOSE_EVENT) {
- ltk_quit(window);
- }
-}
-
-/* FIXME: optimize timer handling - maybe also a sort of priority queue */
-/* FIXME: JUST USE A GENERIC DYNAMIC ARRAY ALREADY!!!!! */
-void
-ltk_unregister_timer(int timer_id) {
- for (size_t i = 0; i < timers_num; i++) {
- if (timers[i].id == timer_id) {
- memmove(
- timers + i,
- timers + i + 1,
- sizeof(ltk_timer) * (timers_num - i - 1)
- );
- timers_num--;
- size_t sz = ideal_array_size(timers_alloc, timers_num);
- if (sz != timers_alloc) {
- timers_alloc = sz;
- timers = ltk_reallocarray(
- timers, sz, sizeof(ltk_timer)
- );
- }
- return;
- }
- }
-}
-
-/* repeat <= 0 means no repeat, first <= 0 means run as soon as possible */
-int
-ltk_register_timer(long first, long repeat, void (*callback)(void *), void *data) {
- if (first < 0)
- first = 0;
- if (repeat < 0)
- repeat = 0;
- if (timers_num == timers_alloc) {
- timers_alloc = ideal_array_size(timers_alloc, timers_num + 1);
- timers = ltk_reallocarray(
- timers, timers_alloc, sizeof(ltk_timer)
- );
- }
- /* FIXME: better finding of id */
- /* FIXME: maybe store sorted by id */
- int id = 0;
- for (size_t i = 0; i < timers_num; i++) {
- if (timers[i].id >= id)
- id = timers[i].id + 1;
- }
- ltk_timer *t = &timers[timers_num++];
- t->callback = callback;
- t->data = data;
- t->repeat.tv_sec = repeat / 1000;
- t->repeat.tv_nsec = (repeat % 1000) * 1000;
- t->remaining.tv_sec = first / 1000;
- t->remaining.tv_nsec = (first % 1000) * 1000;
- t->id = id;
- return id;
-}
-
-/* FIXME: check for duplicates? */
-void
-ltk_window_register_popup(ltk_window *window, ltk_widget *popup) {
- if (window->popups_num == window->popups_alloc) {
- window->popups_alloc = ideal_array_size(
- window->popups_alloc, window->popups_num + 1
- );
- window->popups = ltk_reallocarray(
- window->popups, window->popups_alloc, sizeof(ltk_widget *)
- );
- }
- window->popups[window->popups_num++] = popup;
- popup->popup = 1;
-}
-
-void
-ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) {
- if (window->popups_locked)
- return;
- for (size_t i = 0; i < window->popups_num; i++) {
- if (window->popups[i] == popup) {
- popup->popup = 0;
- memmove(
- window->popups + i,
- window->popups + i + 1,
- sizeof(ltk_widget *) * (window->popups_num - i - 1)
- );
- window->popups_num--;
- size_t sz = ideal_array_size(
- window->popups_alloc, window->popups_num
- );
- if (sz != window->popups_alloc) {
- window->popups_alloc = sz;
- window->popups = ltk_reallocarray(
- window->popups, sz, sizeof(ltk_widget *)
- );
- }
- return;
- }
- }
-}
-
-/* FIXME: where should actual hiding happen? */
-void
-ltk_window_unregister_all_popups(ltk_window *window) {
- window->popups_locked = 1;
- for (size_t i = 0; i < window->popups_num; i++) {
- window->popups[i]->hidden = 1;
- window->popups[i]->popup = 0;
- ltk_widget_hide(window->popups[i]);
- }
- window->popups_num = 0;
- /* somewhat arbitrary, but should be enough for most cases */
- if (window->popups_num > 4) {
- window->popups = ltk_reallocarray(
- window->popups, 4, sizeof(ltk_widget *)
- );
- window->popups_alloc = 4;
- }
- window->popups_locked = 0;
- /* I guess just invalidate everything instead of being smart */
- ltk_window_invalidate_rect(window, window->rect);
-}
-
-ltk_window_theme window_theme;
-static ltk_theme_parseinfo theme_parseinfo[] = {
- {"font-size", THEME_INT, {.i = &window_theme.font_size}, {.i = 15}, 0, MAX_WINDOW_FONT_SIZE, 0},
- {"font", THEME_STRING, {.str = &window_theme.font}, {.str = "Liberation Mono"}, 0, 0, 0},
- {"bg", THEME_COLOR, {.color = &window_theme.bg}, {.color = "#000000"}, 0, 0, 0},
- {"fg", THEME_COLOR, {.color = &window_theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
-};
-static int theme_parseinfo_sorted = 0;
-
-static int
-ltk_window_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "window", theme_parseinfo, LENGTH(theme_parseinfo));
-}
-
-static int
-ltk_window_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "window", prop, value, theme_parseinfo, LENGTH(theme_parseinfo), &theme_parseinfo_sorted);
-}
-
-static void
-ltk_window_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, theme_parseinfo, LENGTH(theme_parseinfo));
-}
-
-/* FIXME: standardize return codes - usually, 0 is returned on success, but ini.h
- uses 1 on success, so this is all a bit confusing */
-/* FIXME: switch away from ini.h */
-static int
-ltk_ini_handler(void *window, const char *widget, const char *prop, const char *value) {
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (widget_funcs[i].ini_handler && !strcmp(widget, widget_funcs[i].name)) {
- widget_funcs[i].ini_handler(window, prop, value);
- return 1;
- }
- }
- return 0;
-}
-
-static void
-ltk_load_theme(ltk_window *window, const char *path) {
- /* FIXME: give line number in error message */
- if (ini_parse(path, ltk_ini_handler, window) != 0) {
- ltk_warn("Unable to load theme.\n");
- }
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (widget_funcs[i].fill_theme_defaults) {
- if (widget_funcs[i].fill_theme_defaults(window)) {
- ltk_uninitialize_theme(window);
- ltk_fatal("Unable to load theme defaults.\n");
- }
- }
- }
-}
-
-static void
-ltk_uninitialize_theme(ltk_window *window) {
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (widget_funcs[i].uninitialize_theme)
- widget_funcs[i].uninitialize_theme(window);
- }
-}
-
-static int
-handle_keypress_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keypress_binding b) {
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
- if (!widget_funcs[i].register_keypress)
- return 1;
- return widget_funcs[i].register_keypress(name, nlen, b);
- }
- }
- return 1;
-}
-
-static int
-handle_keyrelease_binding(const char *widget_name, size_t wlen, const char *name, size_t nlen, ltk_keyrelease_binding b) {
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (str_array_equal(widget_funcs[i].name, widget_name, wlen)) {
- if (!widget_funcs[i].register_keyrelease)
- return 1;
- return widget_funcs[i].register_keyrelease(name, nlen, b);
- }
- }
- return 1;
-}
-
-int
-ltk_window_call_cmd(ltk_window *window, ltk_widget *caller, const char *cmd, size_t cmdlen, const char *text, size_t textlen) {
- if (window->cmd_caller) {
- /* FIXME: allow multiple programs? */
- ltk_warn("External program to edit text is already being run\n");
- return 1;
- }
- /* FIXME: support environment variable $TMPDIR */
- ltk_free(window->cmd_tmpfile);
- window->cmd_tmpfile = ltk_strdup("/tmp/ltk.XXXXXX");
- int fd = mkstemp(window->cmd_tmpfile);
- if (fd == -1) {
- ltk_warn("Unable to create temporary file while trying to run command '%.*s'\n", (int)cmdlen, cmd);
- return 1;
- }
- close(fd);
- /* FIXME: give file descriptor directly to modified version of ltk_write_file */
- char *errstr = NULL;
- if (ltk_write_file(window->cmd_tmpfile, text, textlen, &errstr)) {
- ltk_warn("Unable to write to file '%s' while trying to run command '%.*s': %s\n", window->cmd_tmpfile, (int)cmdlen, cmd, errstr);
- unlink(window->cmd_tmpfile);
- return 1;
- }
- int pid = -1;
- if ((pid = ltk_parse_run_cmd(cmd, cmdlen, window->cmd_tmpfile)) <= 0) {
- ltk_warn("Unable to run command '%.*s'\n", (int)cmdlen, cmd);
- unlink(window->cmd_tmpfile);
- return 1;
- }
- window->cmd_pid = pid;
- window->cmd_caller = ltk_strdup(caller->id);
- return 0;
-}
-
-static ltk_window *
-ltk_create_window(const char *title, int x, int y, unsigned int w, unsigned int h) {
- char *theme_path;
-
- ltk_window *window = ltk_malloc(sizeof(ltk_window));
-
- window->popups = NULL;
- window->popups_num = window->popups_alloc = 0;
- window->popups_locked = 0;
- window->cur_kbd = 0;
-
- window->renderdata = renderer_create_window(title, x, y, w, h);
- /* FIXME: search different directories for config */
- char *config_path = ltk_strcat_useful(ltk_dir, "/ltk.cfg");
- char *errstr = NULL;
- if (ltk_config_parsefile(config_path, &handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
- if (errstr) {
- ltk_warn("Unable to load config: %s\n", errstr);
- ltk_free(errstr);
- }
- if (ltk_config_load_default(&handle_keypress_binding, &handle_keyrelease_binding, &errstr)) {
- /* FIXME: I guess errstr isn't freed here, but whatever */
- ltk_fatal("Unable to load default config: %s\n", errstr);
- }
- }
- ltk_free(config_path);
- theme_path = ltk_strcat_useful(ltk_dir, "/theme.ini");
- window->theme = &window_theme;
- ltk_load_theme(window, theme_path);
- ltk_free(theme_path);
-
- /* FIXME: fix theme memory leaks when exit happens between here and the end of this function */
-
- renderer_set_window_properties(window->renderdata, &window->theme->bg);
-
- window->root_widget = NULL;
- window->hover_widget = NULL;
- window->active_widget = NULL;
- window->pressed_widget = NULL;
-
- window->cmd_pid = -1;
- window->cmd_tmpfile = NULL;
- window->cmd_caller = NULL;
-
- window->surface_cache = ltk_surface_cache_create(window->renderdata);
-
- window->other_event = <k_window_other_event;
-
- window->rect.w = w;
- window->rect.h = h;
- window->rect.x = 0;
- window->rect.y = 0;
- window->dirty_rect.w = 0;
- window->dirty_rect.h = 0;
- window->dirty_rect.x = 0;
- window->dirty_rect.y = 0;
- window->surface = ltk_surface_from_window(window->renderdata, w, h);
-
- window->text_context = ltk_text_context_create(window->renderdata, window->theme->font);
- window->clipboard = ltk_clipboard_create(window->renderdata);
-
- /* This hack is necessary to make the daemonization work properly when using Pango.
- This may not be entirely accurate, but from what I gather, newer versions of Pango
- initialize Fontconfig in a separate thread to avoid startup overhead. This leads
- to non-deterministic behavior because the Fontconfig initialization doesn't work
- properly after daemonization. Creating a text line and getting the size waits until
- Fontconfig is initialized. Getting the size is important because Pango doesn't
- actually do much until you try to use the line for something. */
- /* FIXME: I guess just calling FcInit manually in the text backend could work as well. */
- /* FIXME: Maybe just call this when actually daemonizing. */
- ltk_text_line *tmp = ltk_text_line_create(window->text_context, 10, "hi", 0, -1);
- int tw, th;
- ltk_text_line_get_size(tmp, &tw, &th);
- ltk_text_line_destroy(tmp);
- /* FIXME: cache doesn't really make any sense right now anyways
- since images are only loaded from memory */
- ltk_image_init(window->renderdata, 0);
-
- return window;
-}
-
-static void
-ltk_destroy_window(ltk_window *window) {
- ltk_free(window->cmd_tmpfile);
- ltk_clipboard_destroy(window->clipboard);
- ltk_text_context_destroy(window->text_context);
- if (window->popups)
- ltk_free(window->popups);
- ltk_surface_cache_destroy(window->surface_cache);
- ltk_surface_destroy(window->surface);
- renderer_destroy_window(window->renderdata);
- ltk_free(window);
-}
-
-/* event must have global coordinates! */
-void
-ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) {
- ltk_widget *old = window->hover_widget;
- if (old == widget)
- return;
- int orig_x = event->x, orig_y = event->y;
- if (old) {
- ltk_widget_state old_state = old->state;
- old->state &= ~LTK_HOVER;
- ltk_widget_change_state(old, old_state);
- if (old->vtable->mouse_leave) {
- ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y);
- event->x = local.x;
- event->y = local.y;
- old->vtable->mouse_leave(old, event);
- event->x = orig_x;
- event->y = orig_y;
- }
- }
- window->hover_widget = widget;
- if (widget) {
- if (widget->vtable->mouse_enter) {
- ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
- event->x = local.x;
- event->y = local.y;
- widget->vtable->mouse_enter(widget, event);
- }
- ltk_widget_state old_state = widget->state;
- widget->state |= LTK_HOVER;
- ltk_widget_change_state(widget, old_state);
- if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget)
- ltk_window_set_active_widget(window, widget);
- }
-}
-
-void
-ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
- if (window->active_widget == widget) {
- return;
- }
- ltk_widget *old = window->active_widget;
- /* Note: this has to be set at the beginning to
- avoid infinite recursion in some cases */
- window->active_widget = widget;
- ltk_widget *common_parent = NULL;
- if (widget) {
- ltk_widget *cur = widget;
- while (cur) {
- if (cur->state & LTK_ACTIVE) {
- common_parent = cur;
- break;
- }
- ltk_widget_state old_state = cur->state;
- cur->state |= LTK_ACTIVE;
- /* FIXME: should all be set focused? */
- if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD))
- widget->state |= LTK_FOCUSED;
- ltk_widget_change_state(cur, old_state);
- cur = cur->parent;
- }
- }
- /* FIXME: better variable names; generally make this nicer */
- /* special case if old is parent of new active widget */
- ltk_widget *tmp = common_parent;
- while (tmp) {
- if (tmp == old)
- return;
- tmp = tmp->parent;
- }
- if (old) {
- old->state &= ~LTK_FOCUSED;
- ltk_widget *cur = old;
- while (cur) {
- if (cur == common_parent)
- break;
- ltk_widget_state old_state = cur->state;
- cur->state &= ~LTK_ACTIVE;
- ltk_widget_change_state(cur, old_state);
- cur = cur->parent;
- }
- }
-}
-
-void
-ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) {
- if (window->pressed_widget == widget)
- return;
- if (window->pressed_widget) {
- ltk_widget_state old_state = window->pressed_widget->state;
- window->pressed_widget->state &= ~LTK_PRESSED;
- ltk_widget_change_state(window->pressed_widget, old_state);
- ltk_window_set_active_widget(window, window->pressed_widget);
- /* FIXME: this is a bit weird because the release handler for menuentry
- indirectly calls ltk_widget_hide, which messes with the pressed widget */
- /* FIXME: isn't it redundant to check that state is pressed? */
- if (release && window->pressed_widget->vtable->release && (old_state & LTK_PRESSED)) {
- window->pressed_widget->vtable->release(window->pressed_widget);
- }
- }
- window->pressed_widget = widget;
- if (widget) {
- if (widget->vtable->press)
- widget->vtable->press(widget);
- ltk_widget_state old_state = widget->state;
- widget->state |= LTK_PRESSED;
- ltk_widget_change_state(widget, old_state);
- }
-}
-
-static void
-ltk_handle_event(ltk_window *window, ltk_event *event) {
- size_t kbd_idx;
- switch (event->type) {
- case LTK_KEYPRESS_EVENT:
- ltk_window_key_press_event(window, &event->key);
- break;
- case LTK_KEYRELEASE_EVENT:
- ltk_window_key_release_event(window, &event->key);
- break;
- case LTK_BUTTONPRESS_EVENT:
- case LTK_2BUTTONPRESS_EVENT:
- case LTK_3BUTTONPRESS_EVENT:
- ltk_window_mouse_press_event(window, &event->button);
- break;
- case LTK_SCROLL_EVENT:
- ltk_window_mouse_scroll_event(window, &event->scroll);
- break;
- case LTK_BUTTONRELEASE_EVENT:
- case LTK_2BUTTONRELEASE_EVENT:
- case LTK_3BUTTONRELEASE_EVENT:
- ltk_window_mouse_release_event(window, &event->button);
- break;
- case LTK_MOTION_EVENT:
- ltk_window_motion_notify_event(window, &event->motion);
- break;
- case LTK_KEYBOARDCHANGE_EVENT:
- /* FIXME: emit event */
- if (ltk_config_get_language_index(event->keyboard.new_kbd, &kbd_idx))
- ltk_warn("No language mapping for language \"%s\".\n", event->keyboard.new_kbd);
- else
- window->cur_kbd = kbd_idx;
- break;
- default:
- if (window->other_event)
- window->other_event(window, event);
- }
-}
-
-/* Push a token onto `token_list`, resizing the buffer if necessary.
- Returns -1 on error, 0 otherwise.
- Note: The token is not copied, it is only added directly. */
-static int
-push_token(struct token_list *tl, char *token) {
- int new_size;
- if (tl->num_tokens >= tl->num_alloc) {
- new_size = (tl->num_alloc * 2) > (tl->num_tokens + 1) ?
- (tl->num_alloc * 2) : (tl->num_tokens + 1);
- ltk_cmd_token *new = ltk_reallocarray(tl->tokens, new_size, sizeof(ltk_cmd_token));
- if (!new) return -1;
- tl->tokens = new;
- tl->num_alloc = new_size;
- }
- tl->tokens[tl->num_tokens].text = token;
- tl->tokens[tl->num_tokens].len = 0;
- tl->tokens[tl->num_tokens++].contains_nul = 0;
-
- return 0;
-}
-
-/* Add a new client to the socket list and return the index in `sockets`.
- Returns -1 if there is no space for a new client. */
-static int
-add_client(int fd) {
- for (int i = 0; i < MAX_SOCK_CONNS; i++) {
- if (sockets[i].fd == -1) {
- sockets[i].fd = fd;
- sockets[i].event_mask = ~0; /* FIXME */
- sockets[i].read_len = 0;
- sockets[i].write_len = 0;
- sockets[i].write_cur = 0;
- sockets[i].offset = 0;
- sockets[i].in_str = 0;
- sockets[i].read_cur = 0;
- sockets[i].bs = 0;
- sockets[i].tokens.num_tokens = 0;
- sockets[i].last_seq = 0;
- return i;
- }
- }
-
- return -1;
-}
-
-/* largely copied from APUE */
-/* Listen on the socket at `sock_path`.
- Returns the file descriptor of the opened socket on success.
- Returns -1 if `sock_path` is too long
- -2 if the socket could not be created
- -3 if the socket could not be bound to the path
- -4 if the socket could not be listened on */
-static int
-listen_sock(const char *sock_path) {
- int fd, len, err, rval;
- struct sockaddr_un un;
-
- if (strlen(sock_path) >= sizeof(un.sun_path)) {
- errno = ENAMETOOLONG;
- return -1;
- }
-
- if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
- return -2;
-
- unlink(sock_path);
-
- memset(&un, 0, sizeof(un));
- un.sun_family = AF_UNIX;
- strcpy(un.sun_path, sock_path);
- len = offsetof(struct sockaddr_un, sun_path) + strlen(sock_path);
- if (bind(fd, (struct sockaddr *)&un, len) < 0) {
- rval = -3;
- goto errout;
- }
-
- if (listen(fd, 10) < 0) {
- rval = -4;
- goto errout;
- }
-
- return fd;
-
-errout:
- err = errno;
- close(fd);
- errno = err;
- return rval;
-}
-
-/* Accept a socket connection on the listening socket `listenfd`.
- Returns the file descriptor of the accepted client on success.
- Returns -1 if there was an error. */
-static int
-accept_sock(int listenfd) {
- int clifd;
- socklen_t len;
- struct sockaddr_un un;
-
- len = sizeof(un);
- if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) {
- return -1;
- }
- if (set_nonblock(clifd)) {
- /* FIXME: what could even be done if close fails? */
- close(clifd);
- return -1;
- }
-
- return clifd;
-}
-
-/* Read up to READ_BLK_SIZE bytes from the socket `sock`.
- Returns -1 if an error occurred, 0 if the connection was closed, 1 otherwise.
- Note: Returning 1 on success is weird, but it could also be confusing to
- return 0 on success when `read` returns that to mean that the connection
- was closed. */
-static int
-read_sock(struct ltk_sock_info *sock) {
- int nread;
- char *old = sock->read;
- ltk_grow_string(&sock->read, &sock->read_alloc, sock->read_len + READ_BLK_SIZE);
- /* move tokens to new addresses - this was added as an
- afterthought and really needs to be cleaned up */
- if (sock->read != old) {
- for (int i = 0; i < sock->tokens.num_tokens; i++) {
- sock->tokens.tokens[i].text = sock->read + (sock->tokens.tokens[i].text - old);
- }
- }
- nread = read(sock->fd, sock->read + sock->read_len, READ_BLK_SIZE);
- if (nread == -1 || nread == 0)
- return nread;
- sock->read_len += nread;
-
- return 1;
-}
-
-/* Write up to WRITE_BLK_SIZE bytes to the socket.
- Returns -1 on error, 0 otherwise. */
-static int
-write_sock(struct ltk_sock_info *sock) {
- if (sock->write_len == sock->write_cur)
- return 0;
- int write_len = WRITE_BLK_SIZE > sock->write_len - sock->write_cur ?
- sock->write_len - sock->write_cur : WRITE_BLK_SIZE;
- int nwritten = write(sock->fd, sock->to_write + sock->write_cur, write_len);
- if (nwritten == -1)
- return nwritten;
- sock->write_cur += nwritten;
-
- /* check if any sockets have text to write */
- if (sock->write_cur == sock->write_len) {
- int found = 0;
- for (int i = 0; i < maxsocket; i++) {
- if (sockets[i].fd != -1 &&
- sockets[i].write_cur != sockets[i].write_len) {
- found = 1;
- break;
- }
- }
- if (!found)
- sock_write_available = 0;
- }
-
- return 0;
-}
-
-static void
-move_write_pos(struct ltk_sock_info *sock) {
- /* FIXME: also resize if too large */
- if (sock->write_cur > 0) {
- memmove(sock->to_write, sock->to_write + sock->write_cur,
- sock->write_len - sock->write_cur);
- sock->write_len -= sock->write_cur;
- sock->write_cur = 0;
- }
-}
-
-/* Queue `str` to be written to the socket. If len is < 0, it is set to `strlen(str)`.
- Returns -1 on error, 0 otherwise.
- Note: The string must include all '\n', etc. as defined in the protocol. This
- function just adds the given string verbatim. */
-int
-ltk_queue_sock_write(int client, const char *str, int len) {
- if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
- return 1;
- /* this is always large enough to hold a uint32_t and " \0" */
- char num[12];
- struct ltk_sock_info *sock = &sockets[client];
- move_write_pos(sock);
- if (len < 0)
- len = strlen(str);
-
- int numlen = snprintf(num, sizeof(num), "%"PRIu32" ", sock->last_seq);
- if (numlen < 0 || (unsigned)numlen >= sizeof(num))
- ltk_fatal("There's a bug in the universe.\n");
- if (sock->write_alloc - sock->write_len < len + numlen)
- ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + numlen);
-
- (void)strncpy(sock->to_write + sock->write_len, num, numlen);
- (void)strncpy(sock->to_write + sock->write_len + numlen, str, len);
- sock->write_len += len + numlen;
-
- sock_write_available = 1;
-
- return 0;
-}
-
-int
-ltk_queue_sock_write_fmt(int client, const char *fmt, ...) {
- if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
- return 1;
- struct ltk_sock_info *sock = &sockets[client];
- /* just to print the sequence number */
- ltk_queue_sock_write(client, "", 0);
- va_list args;
- va_start(args, fmt);
- int len = vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
- if (len < 0) {
- ltk_fatal("Unable to print formatted text to socket.\n");
- } else if (len >= sock->write_alloc - sock->write_len) {
- va_end(args);
- va_start(args, fmt);
- /* snprintf always writes '\0', even though we don't actually need it here */
- ltk_grow_string(&sock->to_write, &sock->write_alloc, sock->write_len + len + 1);
- vsnprintf(sock->to_write + sock->write_len, sock->write_alloc - sock->write_len, fmt, args);
- }
- va_end(args);
- sock->write_len += len;
- sock_write_available = 1;
-
- return 0;
-}
-
-/* Tokenize the current read buffer in `sock`.
- Returns 0 immediately if the end of a command was encountered, 1 otherwise. */
-static int
-tokenize_command(struct ltk_sock_info *sock) {
- for (; sock->read_cur < sock->read_len; sock->read_cur++) {
- /* FIXME: strip extra whitespace? Or maybe just have a "hard" protocol where it always has to be one space. */
- if (!sock->in_token) {
- push_token(&sock->tokens, sock->read + sock->read_cur - sock->offset);
- sock->in_token = 1;
- }
- if (sock->read[sock->read_cur] == '\\') {
- sock->bs++;
- if (sock->bs / 2)
- sock->offset++;
- else
- sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
- sock->bs %= 2;
- sock->read[sock->read_cur-sock->offset] = '\\';
- } else if (sock->read[sock->read_cur] == '\n' && !sock->in_str) {
- sock->read[sock->read_cur-sock->offset] = '\0';
- sock->read_cur++;
- sock->offset = 0;
- sock->in_token = 0;
- sock->bs = 0;
- return 0;
- } else if (sock->read[sock->read_cur] == '"') {
- sock->offset++;
- if (sock->bs) {
- sock->read[sock->read_cur-sock->offset] = '"';
- sock->bs = 0;
- } else {
- sock->in_str = !sock->in_str;
- }
- } else if (sock->read[sock->read_cur] == ' ' && !sock->in_str) {
- sock->read[sock->read_cur-sock->offset] = '\0';
- sock->in_token = !sock->in_token;
- sock->bs = 0;
- } else {
- sock->read[sock->read_cur-sock->offset] = sock->read[sock->read_cur];
- /* FIXME: assert that num_tokens > 0 */
- sock->tokens.tokens[sock->tokens.num_tokens - 1].len++;
- if (sock->read[sock->read_cur] == '\0')
- sock->tokens.tokens[sock->tokens.num_tokens - 1].contains_nul = 1;
- sock->bs = 0;
- }
- }
-
- return 1;
-}
-
-/* FIXME: currently no type-checking when setting specific widget mask */
-/* FIXME: this is really ugly and inefficient right now - it will be replaced with something
- more generic at some point (or maybe just with a binary protocol?) */
-static int
-handle_mask_command(int client, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err) {
- if (num_tokens != 4 && num_tokens != 5) {
- err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
- err->arg = -1;
- return 1;
- }
- /* FIXME: make this nicer */
- /* -> use generic cmd handling like the widgets */
- if (tokens[1].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 1;
- return 1;
- } else if (tokens[2].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 2;
- return 1;
- } else if (tokens[3].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 3;
- return 1;
- } else if (num_tokens == 5 && tokens[4].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 4;
- return 1;
- }
- uint32_t mask = 0;
- int lock = 0;
- int special = 0;
- ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
- if (!widget) {
- err->arg = 1;
- return 1;
- }
- if (!strcmp(tokens[2].text, "widget")) {
- if (!strcmp(tokens[3].text, "mousepress")) {
- mask = LTK_PEVENTMASK_MOUSEPRESS;
- } else if (!strcmp(tokens[3].text, "mouserelease")) {
- mask = LTK_PEVENTMASK_MOUSERELEASE;
- } else if (!strcmp(tokens[3].text, "mousemotion")) {
- mask = LTK_PEVENTMASK_MOUSEMOTION;
- } else if (!strcmp(tokens[3].text, "configure")) {
- mask = LTK_PEVENTMASK_CONFIGURE;
- } else if (!strcmp(tokens[3].text, "statechange")) {
- mask = LTK_PEVENTMASK_STATECHANGE;
- } else if (!strcmp(tokens[3].text, "none")) {
- mask = LTK_PEVENTMASK_NONE;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 3;
- return 1;
- }
- } else if (!strcmp(tokens[2].text, "menuentry")) {
- if (!strcmp(tokens[3].text, "press")) {
- mask = LTK_PWEVENTMASK_MENUENTRY_PRESS;
- } else if (!strcmp(tokens[3].text, "none")) {
- mask = LTK_PWEVENTMASK_MENUENTRY_NONE;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 3;
- return 1;
- }
- special = 1;
- } else if (!strcmp(tokens[2].text, "button")) {
- if (!strcmp(tokens[3].text, "press")) {
- mask = LTK_PWEVENTMASK_BUTTON_PRESS;
- } else if (!strcmp(tokens[3].text, "none")) {
- mask = LTK_PWEVENTMASK_BUTTON_NONE;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 3;
- return 1;
- }
- special = 1;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 2;
- return 1;
- }
- if (num_tokens == 5) {
- if (!strcmp(tokens[4].text, "lock")) {
- lock = 1;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 4;
- return 1;
- }
- }
-
- if (!strcmp(tokens[0].text, "mask-add")) {
- if (lock) {
- if (special)
- ltk_widget_add_to_event_lwmask(widget, client, mask);
- else
- ltk_widget_add_to_event_lmask(widget, client, mask);
- } else {
- if (special)
- ltk_widget_add_to_event_wmask(widget, client, mask);
- else
- ltk_widget_add_to_event_mask(widget, client, mask);
- }
- } else if (!strcmp(tokens[0].text, "mask-set")) {
- if (lock) {
- if (special)
- ltk_widget_set_event_lwmask(widget, client, mask);
- else
- ltk_widget_set_event_lmask(widget, client, mask);
- } else {
- if (special)
- ltk_widget_set_event_wmask(widget, client, mask);
- else
- ltk_widget_set_event_mask(widget, client, mask);
- }
- } else if (!strcmp(tokens[0].text, "mask-remove")) {
- if (lock) {
- if (special)
- ltk_widget_remove_from_event_lwmask(widget, client, mask);
- else
- ltk_widget_remove_from_event_lmask(widget, client, mask);
- } else {
- if (special)
- ltk_widget_remove_from_event_wmask(widget, client, mask);
- else
- ltk_widget_remove_from_event_mask(widget, client, mask);
- }
- } else {
- err->type = ERR_INVALID_COMMAND;
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* Process the commands as they are read from the socket. */
-/* Returns 1 if command was 'event-unlock true',
- -1 if command was 'event-unlock false', 0 otherwise. */
-static int
-process_commands(ltk_window *window, int client) {
- if (client < 0 || client >= MAX_SOCK_CONNS || sockets[client].fd == -1)
- return 0;
- struct ltk_sock_info *sock = &sockets[client];
- ltk_cmd_token *tokens;
- int num_tokens;
- ltk_error errdetail = {ERR_NONE, -1};
- int err;
- int retval = 0;
- int last = 0;
- uint32_t seq;
- const char *errstr;
- int contains_nul = 0;
- while (!tokenize_command(sock)) {
- contains_nul = 0;
- err = 0;
- tokens = sock->tokens.tokens;
- num_tokens = sock->tokens.num_tokens;
- if (num_tokens < 2) {
- errdetail.type = ERR_INVALID_COMMAND;
- errdetail.arg = -1;
- err = 1;
- } else {
- contains_nul = tokens[0].contains_nul;
- seq = (uint32_t)ltk_strtonum(tokens[0].text, 0, UINT32_MAX, &errstr);
- tokens++;
- num_tokens--;
- if (errstr || contains_nul) {
- errdetail.type = ERR_INVALID_SEQNUM;
- errdetail.arg = -1;
- err = 1;
- seq = sock->last_seq;
- } else if (tokens[0].contains_nul) {
- errdetail.type = ERR_INVALID_ARGUMENT;
- errdetail.arg = 0;
- err = 1;
- seq = sock->last_seq;
- } else if (strcmp(tokens[0].text, "set-root-widget") == 0) {
- err = ltk_set_root_widget_cmd(window, tokens, num_tokens, &errdetail);
- } else if (strcmp(tokens[0].text, "quit") == 0) {
- ltk_quit(window);
- last = 1;
- } else if (strcmp(tokens[0].text, "destroy") == 0) {
- err = ltk_widget_destroy_cmd(window, tokens, num_tokens, &errdetail);
- } else if (strncmp(tokens[0].text, "mask", 4) == 0) {
- err = handle_mask_command(client, tokens, num_tokens, &errdetail);
- } else if (strcmp(tokens[0].text, "event-unlock") == 0) {
- if (num_tokens != 2) {
- errdetail.type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
- errdetail.arg = -1;
- err = 1;
- } else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "true") == 0) {
- retval = 1;
- } else if (!tokens[1].contains_nul && strcmp(tokens[1].text, "false") == 0) {
- retval = -1;
- } else {
- err = 1;
- errdetail.type = ERR_INVALID_ARGUMENT;
- errdetail.arg = 1;
- }
- last = 1;
- } else {
- int found = 0;
- for (size_t i = 0; i < LENGTH(widget_funcs); i++) {
- if (widget_funcs[i].cmd && !strcmp(tokens[0].text, widget_funcs[i].name)) {
- err = widget_funcs[i].cmd(window, tokens, num_tokens, &errdetail);
- found = 1;
- }
- }
- if (!found) {
- errdetail.type = ERR_INVALID_COMMAND;
- errdetail.arg = -1;
- err = 1;
- }
- }
- sock->tokens.num_tokens = 0;
- sock->last_seq = seq;
- }
- if (err) {
- const char *errmsg = errtype_to_string(errdetail.type);
- if (ltk_queue_sock_write_fmt(client, "err %d %d \"%s\"\n", errdetail.type, errdetail.arg, errmsg))
- ltk_fatal("Unable to queue socket write.\n");
- } else {
- if (ltk_queue_sock_write(client, "res ok\n", -1)) {
- ltk_fatal("Unable to queue socket write.\n");
- }
- }
- if (last)
- break;
- }
- if (sock->tokens.num_tokens > 0 && sock->tokens.tokens[0].text != sock->read) {
- memmove(sock->read, sock->tokens.tokens[0].text, sock->read + sock->read_len - sock->tokens.tokens[0].text);
- ptrdiff_t offset = sock->tokens.tokens[0].text - sock->read;
- /* Hmm, seems a bit ugly... */
- for (int i = 0; i < sock->tokens.num_tokens; i++) {
- sock->tokens.tokens[i].text -= offset;
- }
- sock->read_len -= offset;
- sock->read_cur -= offset;
- } else if (sock->tokens.num_tokens == 0) {
- sock->read_len = 0;
- sock->read_cur = 0;
- }
- return retval;
-}
diff --git a/src/macros.h b/src/macros.h
@@ -1,7 +1,40 @@
-#ifndef _MACROS_H_
-#define _MACROS_H_
+#ifndef LTK_MACROS_H
+#define LTK_MACROS_H
+
+/* ltk_timespecadd and ltk_timespecsub are taken from OpenBSD.
+ The copyright is as follows: */
+
+/*
+ * Copyright (c) 1982, 1986, 1993
+ * The Regents of the University of California. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * @(#)time.h 8.2 (Berkeley) 7/10/94
+ */
-/* stolen from OpenBSD */
/* Note: some code calls these macros with the same first and last
argument, so it is important that that doesn't cause bad behavior. */
#define ltk_timespecadd(tsp, usp, vsp) \
@@ -24,4 +57,4 @@
} \
} while (0)
-#endif
+#endif /* LTK_MACROS_H */
diff --git a/src/memory.c b/src/memory.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -31,18 +31,44 @@ ltk_strdup_impl(const char *s) {
}
char *
-ltk_strdup_debug(const char *s, const char *caller, const char *file, int line) {
- char *str = strdup(s);
- fprintf(stderr, "DEBUG: strdup %p to %p in %s (%s:%d)\n",
- (void *)s, (void *)str, caller, file, line);
+ltk_strndup_impl(const char *s, size_t n) {
+ char *str = strndup(s, n);
if (!str)
ltk_fatal("Out of memory.\n");
return str;
}
+void *
+ltk_malloc_impl(size_t size) {
+ void *ptr = malloc(size);
+ if (!ptr)
+ ltk_fatal("Out of memory.\n");
+ return ptr;
+}
+
+void *
+ltk_calloc_impl(size_t nmemb, size_t size) {
+ void *ptr = calloc(nmemb, size);
+ if (!ptr)
+ ltk_fatal("Out of memory.\n");
+ return ptr;
+}
+
+void *
+ltk_realloc_impl(void *ptr, size_t size) {
+ void *new_ptr = realloc(ptr, size);
+ if (!new_ptr)
+ ltk_fatal("Out of memory.\n");
+ return new_ptr;
+}
+
+#if MEMDEBUG == 1
+
char *
-ltk_strndup_impl(const char *s, size_t n) {
- char *str = strndup(s, n);
+ltk_strdup_debug(const char *s, const char *caller, const char *file, int line) {
+ char *str = strdup(s);
+ fprintf(stderr, "DEBUG: strdup %p to %p in %s (%s:%d)\n",
+ (void *)s, (void *)str, caller, file, line);
if (!str)
ltk_fatal("Out of memory.\n");
return str;
@@ -59,14 +85,6 @@ ltk_strndup_debug(const char *s, size_t n, const char *caller, const char *file,
}
void *
-ltk_malloc_impl(size_t size) {
- void *ptr = malloc(size);
- if (!ptr)
- ltk_fatal("Out of memory.\n");
- return ptr;
-}
-
-void *
ltk_malloc_debug(size_t size, const char *caller, const char *file, int line) {
void *ptr = malloc(size);
fprintf(stderr, "DEBUG: malloc %p, %zu bytes in %s (%s:%d)\n",
@@ -77,14 +95,6 @@ ltk_malloc_debug(size_t size, const char *caller, const char *file, int line) {
}
void *
-ltk_calloc_impl(size_t nmemb, size_t size) {
- void *ptr = calloc(nmemb, size);
- if (!ptr)
- ltk_fatal("Out of memory.\n");
- return ptr;
-}
-
-void *
ltk_calloc_debug(size_t nmemb, size_t size, const char *caller, const char *file, int line) {
void *ptr = calloc(nmemb, size);
fprintf(stderr, "DEBUG: calloc %p, %zu bytes in %s (%s:%d)\n",
@@ -95,14 +105,6 @@ ltk_calloc_debug(size_t nmemb, size_t size, const char *caller, const char *file
}
void *
-ltk_realloc_impl(void *ptr, size_t size) {
- void *new_ptr = realloc(ptr, size);
- if (!new_ptr)
- ltk_fatal("Out of memory.\n");
- return new_ptr;
-}
-
-void *
ltk_realloc_debug(void *ptr, size_t size, const char *caller, const char *file, int line) {
void *new_ptr = realloc(ptr, size);
fprintf(stderr, "DEBUG: realloc %p to %p, %zu bytes in %s (%s:%d)\n",
@@ -118,6 +120,8 @@ ltk_free_debug(void *ptr, const char *caller, const char *file, int line) {
free(ptr);
}
+#endif
+
/*
* This (reallocarray) is from OpenBSD (adapted to exit on error):
* Copyright (c) 2008 Otto Moerbeek <otto@drijf.net>
diff --git a/src/memory.h b/src/memory.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -37,6 +37,8 @@
#define ltk_free(ptr) free(ptr);
#endif
+#define ltk_free0(p) do {ltk_free(p); p = NULL;} while (0)
+
char *ltk_strdup_impl(const char *s);
char *ltk_strndup_impl(const char *s, size_t n);
void *ltk_malloc_impl(size_t size);
diff --git a/src/menu.c b/src/menu.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -20,14 +20,11 @@
/* -> this is because the pressed handling checks if the widget is activatable, then goes to the parent,
but the child isn't geometrically in the parent here, so that's weird */
-#include <stdio.h>
-#include <stdlib.h>
#include <stdint.h>
#include <string.h>
-#include <stdarg.h>
#include <math.h>
+#include <limits.h>
-#include "proto_types.h"
#include "event.h"
#include "memory.h"
#include "color.h"
@@ -38,9 +35,7 @@
#include "text.h"
#include "menu.h"
#include "graphics.h"
-#include "surface_cache.h"
#include "theme.h"
-#include "cmd.h"
#define MAX_MENU_BORDER_WIDTH 100
#define MAX_MENU_PAD 500
@@ -55,10 +50,10 @@ static struct theme {
int border_width;
int compress_borders;
- ltk_color border;
- ltk_color background;
- ltk_color scroll_background;
- ltk_color scroll_arrow_color;
+ ltk_color *border;
+ ltk_color *background;
+ ltk_color *scroll_background;
+ ltk_color *scroll_arrow_color;
} menu_theme, submenu_theme;
static struct entry_theme {
@@ -73,21 +68,21 @@ static struct entry_theme {
/* FIXME: allow different values for different states? */
ltk_border_sides border_sides;
- ltk_color text;
- ltk_color border;
- ltk_color fill;
+ ltk_color *text;
+ ltk_color *border;
+ ltk_color *fill;
- ltk_color text_pressed;
- ltk_color border_pressed;
- ltk_color fill_pressed;
+ ltk_color *text_pressed;
+ ltk_color *border_pressed;
+ ltk_color *fill_pressed;
- ltk_color text_active;
- ltk_color border_active;
- ltk_color fill_active;
+ ltk_color *text_active;
+ ltk_color *border_active;
+ ltk_color *fill_active;
- ltk_color text_disabled;
- ltk_color border_disabled;
- ltk_color fill_disabled;
+ ltk_color *text_disabled;
+ ltk_color *border_disabled;
+ ltk_color *fill_disabled;
} menu_entry_theme, submenu_entry_theme;
static void ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r);
@@ -95,7 +90,7 @@ static void ltk_menu_resize(ltk_widget *self);
static void ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
static void ltk_menu_get_max_scroll_offset(ltk_menu *menu, int *x_ret, int *y_ret);
static void ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step);
-static void ltk_menu_scroll_callback(void *data);
+static void ltk_menu_scroll_callback(ltk_callback_arg data);
static void stop_scrolling(ltk_menu *menu);
static ltk_widget *ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y);
static int set_scroll_timer(ltk_menu *menu, int x, int y);
@@ -106,29 +101,27 @@ static void unpopup_active_entry(ltk_menuentry *e);
static int ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event);
static int ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event);
static int ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event);
-static ltk_menu *ltk_menu_create(ltk_window *window, const char *id, int is_submenu);
static void recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget);
static void shrink_entries(ltk_menu *menu);
-static size_t get_entry_with_id(ltk_menu *menu, const char *id);
+static size_t get_entry(ltk_menu *menu, ltk_menuentry *entry);
static void ltk_menu_destroy(ltk_widget *self, int shallow);
+static ltk_menu *ltk_menu_create_base(ltk_window *window, int is_submenu);
+
+static int ltk_menu_remove_child(ltk_widget *self, ltk_widget *widget);
+static int ltk_menuentry_remove_child(ltk_widget *self, ltk_widget *widget);
+
static ltk_widget *ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect);
static ltk_widget *ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget);
static ltk_widget *ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget);
static ltk_widget *ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget);
static ltk_widget *ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget);
-static ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *id, const char *text);
static void ltk_menuentry_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip);
static void ltk_menuentry_destroy(ltk_widget *self, int shallow);
static void ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state);
static int ltk_menuentry_release(ltk_widget *self);
static void ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry);
-static int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err);
-static void ltk_menuentry_detach_submenu(ltk_menuentry *e);
-
-static int ltk_menu_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err);
-static int ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err);
static ltk_widget *ltk_menu_prev_child(ltk_widget *self, ltk_widget *child);
static ltk_widget *ltk_menu_next_child(ltk_widget *self, ltk_widget *child);
@@ -167,6 +160,7 @@ static struct ltk_widget_vtable vtable = {
.ensure_rect_shown = <k_menu_ensure_rect_shown,
.type = LTK_WIDGET_MENU,
.flags = LTK_NEEDS_REDRAW,
+ .invalid_signal = LTK_MENU_SIGNAL_INVALID,
};
static struct ltk_widget_vtable entry_vtable = {
@@ -190,6 +184,7 @@ static struct ltk_widget_vtable entry_vtable = {
.last_child = <k_menuentry_get_child,
.type = LTK_WIDGET_MENUENTRY,
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS | LTK_HOVER_IS_ACTIVE,
+ .invalid_signal = LTK_MENUENTRY_SIGNAL_INVALID,
};
/* FIXME: standardize menuentry vs. menu_entry */
@@ -208,18 +203,18 @@ static ltk_theme_parseinfo menu_parseinfo[] = {
static int menu_parseinfo_sorted = 0;
int
-ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "menu", prop, value, menu_parseinfo, LENGTH(menu_parseinfo), &menu_parseinfo_sorted);
+ltk_menu_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "menu", prop, value, menu_parseinfo, LENGTH(menu_parseinfo), &menu_parseinfo_sorted);
}
int
-ltk_menu_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "menu", menu_parseinfo, LENGTH(menu_parseinfo));
+ltk_menu_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "menu", menu_parseinfo, LENGTH(menu_parseinfo));
}
void
-ltk_menu_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, menu_parseinfo, LENGTH(menu_parseinfo));
+ltk_menu_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, menu_parseinfo, LENGTH(menu_parseinfo));
}
static ltk_theme_parseinfo menu_entry_parseinfo[] = {
@@ -245,18 +240,18 @@ static ltk_theme_parseinfo menu_entry_parseinfo[] = {
static int menu_entry_parseinfo_sorted = 0;
int
-ltk_menuentry_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "menu-entry", prop, value, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo), &menu_entry_parseinfo_sorted);
+ltk_menuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "menu-entry", prop, value, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo), &menu_entry_parseinfo_sorted);
}
int
-ltk_menuentry_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "menu-entry", menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
+ltk_menuentry_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "menu-entry", menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
}
void
-ltk_menuentry_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
+ltk_menuentry_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, menu_entry_parseinfo, LENGTH(menu_entry_parseinfo));
}
static ltk_theme_parseinfo submenu_parseinfo[] = {
@@ -273,18 +268,18 @@ static ltk_theme_parseinfo submenu_parseinfo[] = {
static int submenu_parseinfo_sorted = 0;
int
-ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "submenu", prop, value, submenu_parseinfo, LENGTH(submenu_parseinfo), &submenu_parseinfo_sorted);
+ltk_submenu_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "submenu", prop, value, submenu_parseinfo, LENGTH(submenu_parseinfo), &submenu_parseinfo_sorted);
}
int
-ltk_submenu_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "submenu", submenu_parseinfo, LENGTH(submenu_parseinfo));
+ltk_submenu_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "submenu", submenu_parseinfo, LENGTH(submenu_parseinfo));
}
void
-ltk_submenu_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, submenu_parseinfo, LENGTH(submenu_parseinfo));
+ltk_submenu_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, submenu_parseinfo, LENGTH(submenu_parseinfo));
}
static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
@@ -310,23 +305,23 @@ static ltk_theme_parseinfo submenu_entry_parseinfo[] = {
static int submenu_entry_parseinfo_sorted = 0;
int
-ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "submenu-entry", prop, value, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo), &submenu_entry_parseinfo_sorted);
+ltk_submenuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "submenu-entry", prop, value, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo), &submenu_entry_parseinfo_sorted);
}
int
-ltk_submenuentry_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "submenu-entry", submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
+ltk_submenuentry_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "submenu-entry", submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
}
void
-ltk_submenuentry_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
+ltk_submenuentry_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, submenu_entry_parseinfo, LENGTH(submenu_entry_parseinfo));
}
static void
ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
- ltk_menuentry *e = (ltk_menuentry *)self;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
int in_submenu = IN_SUBMENU(e);
int submenus_opened = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus;
if (!(self->state & (LTK_ACTIVE | LTK_PRESSED))) {
@@ -347,7 +342,7 @@ ltk_menuentry_change_state(ltk_widget *self, ltk_widget_state old_state) {
static ltk_widget *
ltk_menuentry_get_child(ltk_widget *self) {
- ltk_menuentry *e = (ltk_menuentry *)self;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
if (e->submenu && !e->submenu->widget.hidden)
return &e->submenu->widget;
return NULL;
@@ -358,26 +353,26 @@ ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r
/* FIXME: figure out how hidden should work */
if (self->hidden)
return;
- ltk_menuentry *entry = (ltk_menuentry *)self;
+ ltk_menuentry *entry = LTK_CAST_MENUENTRY(self);
int in_submenu = IN_SUBMENU(entry);
struct entry_theme *t = in_submenu ? &submenu_entry_theme : &menu_entry_theme;
ltk_color *text, *border, *fill;
if (self->state & LTK_DISABLED) {
- text = &t->text_disabled;
- border = &t->border_disabled;
- fill = &t->fill_disabled;
+ text = t->text_disabled;
+ border = t->border_disabled;
+ fill = t->fill_disabled;
} else if (self->state & LTK_PRESSED) {
- text = &t->text_pressed;
- border = &t->border_pressed;
- fill = &t->fill_pressed;
+ text = t->text_pressed;
+ border = t->border_pressed;
+ fill = t->fill_pressed;
} else if (self->state & LTK_HOVERACTIVE) {
- text = &t->text_active;
- border = &t->border_active;
- fill = &t->fill_active;
+ text = t->text_active;
+ border = t->border_active;
+ fill = t->fill_active;
} else {
- text = &t->text;
- border = &t->border;
- fill = &t->fill;
+ text = t->text;
+ border = t->border;
+ fill = t->fill;
}
ltk_rect lrect = self->lrect;
ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
@@ -386,21 +381,12 @@ ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r
ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
ltk_surface_fill_rect(draw_surf, fill, surf_clip);
- ltk_surface *s;
int text_w, text_h;
ltk_text_line_get_size(entry->text_line, &text_w, &text_h);
- if (!ltk_surface_cache_get_surface(entry->text_surface_key, &s) || self->dirty) {
- ltk_surface_fill_rect(s, fill, (ltk_rect){0, 0, text_w, text_h});
- ltk_text_line_draw(entry->text_line, s, text, 0, 0);
- self->dirty = 0;
- }
- int text_x = t->text_pad + t->border_width;
- int text_y = t->text_pad + t->border_width;
- ltk_rect text_clip = ltk_rect_intersect(clip, (ltk_rect){text_x, text_y, text_w, text_h});
- ltk_surface_copy(
- s, draw_surf,
- (ltk_rect){text_clip.x - text_x, text_clip.y - text_y, text_clip.w, text_clip.h}, x + text_clip.x, y + text_clip.y
- );
+ int text_x = x + t->text_pad + t->border_width;
+ int text_y = y + t->text_pad + t->border_width;
+ ltk_rect text_clip = ltk_rect_intersect(surf_clip, (ltk_rect){text_x, text_y, text_w, text_h});
+ ltk_text_line_draw_clipped(entry->text_line, draw_surf, text, text_x, text_y, text_clip);
if (in_submenu && entry->submenu) {
ltk_point arrow_points[] = {
@@ -411,20 +397,21 @@ ltk_menuentry_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r
ltk_surface_fill_polygon_clipped(draw_surf, text, arrow_points, LENGTH(arrow_points), surf_clip);
}
ltk_surface_draw_border_clipped(draw_surf, border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, t->border_width, t->border_sides);
+ self->dirty = 0;
}
static void
ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
if (self->hidden)
return;
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(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;
struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
ltk_rect surf_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
- ltk_surface_fill_rect(s, &t->background, surf_clip);
+ ltk_surface_fill_rect(s, t->background, surf_clip);
ltk_widget *ptr = NULL;
for (size_t i = 0; i < menu->num_entries; i++) {
/* FIXME: I guess it could be improved *slightly* by making the clip rect
@@ -451,34 +438,34 @@ ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
/* FIXME: handle pathological case where rect is so small that this still draws outside */
/* -> this is currently a mess because some parts handle clipping properly, but the scroll arrow drawing doesn't */
if (lrect.w < (int)self->ideal_w) {
- ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2});
- ltk_surface_fill_rect(s, &t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2});
+ ltk_surface_fill_rect(s, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, sz, wh - mbw * 2});
+ ltk_surface_fill_rect(s, t->scroll_background, (ltk_rect){wx + ww - sz - mbw, wy + mbw, sz, wh - mbw * 2});
ltk_point arrow_points[3] = {
{wx + t->arrow_pad + mbw, wy + wh / 2},
{wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 - t->arrow_size / 2},
{wx + t->arrow_pad + mbw + t->arrow_size, wy + wh / 2 + t->arrow_size / 2}
};
- ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3);
+ ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
arrow_points[0] = (ltk_point){wx + ww - t->arrow_pad - mbw, wy + wh / 2};
arrow_points[1] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 - t->arrow_size / 2};
arrow_points[2] = (ltk_point){wx + ww - t->arrow_pad - mbw - t->arrow_size, wy + wh / 2 + t->arrow_size / 2};
- ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3);
+ ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
}
if (lrect.h < (int)self->ideal_h) {
- ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz});
- ltk_surface_fill_rect(self->window->surface, &t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz});
+ ltk_surface_fill_rect(self->window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + mbw, ww - mbw * 2, sz});
+ ltk_surface_fill_rect(self->window->surface, t->scroll_background, (ltk_rect){wx + mbw, wy + wh - sz - mbw, ww - mbw * 2, sz});
ltk_point arrow_points[3] = {
{wx + ww / 2, wy + t->arrow_pad + mbw},
{wx + ww / 2 - t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size},
{wx + ww / 2 + t->arrow_size / 2, wy + t->arrow_pad + mbw + t->arrow_size}
};
- ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3);
+ ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
arrow_points[0] = (ltk_point){wx + ww / 2, wy + wh - t->arrow_pad - mbw};
arrow_points[1] = (ltk_point){wx + ww / 2 - t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size};
arrow_points[2] = (ltk_point){wx + ww / 2 + t->arrow_size / 2, wy + wh - t->arrow_pad - mbw - t->arrow_size};
- ltk_surface_fill_polygon(s, &t->scroll_arrow_color, arrow_points, 3);
+ ltk_surface_fill_polygon(s, t->scroll_arrow_color, arrow_points, 3);
}
- ltk_surface_draw_border_clipped(s, &t->border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, mbw, LTK_BORDER_ALL);
+ ltk_surface_draw_border_clipped(s, t->border, (ltk_rect){x, y, lrect.w, lrect.h}, surf_clip, mbw, LTK_BORDER_ALL);
self->dirty = 0;
}
@@ -486,7 +473,7 @@ ltk_menu_draw(ltk_widget *self, ltk_surface *s, int x, int y, ltk_rect clip) {
static void
ltk_menu_resize(ltk_widget *self) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
int max_x, max_y;
ltk_menu_get_max_scroll_offset(menu, &max_x, &max_y);
if (menu->x_scroll_offset > max_x)
@@ -534,7 +521,7 @@ ltk_menu_resize(ltk_widget *self) {
static void
ltk_menu_ensure_rect_shown(ltk_widget *self, ltk_rect r) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
struct theme *theme = menu->is_submenu ? &submenu_theme : &menu_theme;
int extra_size = theme->arrow_size + theme->arrow_pad * 2 + theme->border_width;
int delta = 0;
@@ -602,8 +589,9 @@ ltk_menu_scroll(ltk_menu *menu, char t, char b, char l, char r, int step) {
/* FIXME: show scroll arrow disabled when nothing further */
static void
-ltk_menu_scroll_callback(void *data) {
- ltk_menu *menu = (ltk_menu *)data;
+ltk_menu_scroll_callback(ltk_callback_arg data) {
+ ltk_widget *self = LTK_CAST_ARG_WIDGET(data);
+ ltk_menu *menu = LTK_CAST_MENU(self);
ltk_menu_scroll(
menu,
menu->scroll_top_hover, menu->scroll_bottom_hover,
@@ -624,7 +612,7 @@ stop_scrolling(ltk_menu *menu) {
/* FIXME: should ideal_w, ideal_h just be int? */
static ltk_widget *
ltk_menu_get_child_at_pos(ltk_widget *self, int x, int y) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
struct theme *t = menu->is_submenu ? &submenu_theme : &menu_theme;
int arrow_size = t->arrow_size + t->arrow_pad * 2;
int mbw = t->border_width;
@@ -680,8 +668,8 @@ set_scroll_timer(ltk_menu *menu, int x, int y) {
menu->scroll_bottom_hover = b;
menu->scroll_left_hover = l;
menu->scroll_right_hover = r;
- ltk_menu_scroll_callback(menu);
- menu->scroll_timer_id = ltk_register_timer(0, 300, <k_menu_scroll_callback, menu);
+ ltk_menu_scroll_callback(LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(menu)));
+ menu->scroll_timer_id = ltk_register_timer(0, 300, <k_menu_scroll_callback, LTK_MAKE_ARG_WIDGET(LTK_CAST_WIDGET(menu)));
return 1;
}
@@ -690,19 +678,19 @@ set_scroll_timer(ltk_menu *menu, int x, int y) {
be hidden when scrolling in a menu. Maybe widgets also need a "visible rect"? */
static int
ltk_menuentry_release(ltk_widget *self) {
- ltk_menuentry *e = (ltk_menuentry *)self;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
int in_submenu = IN_SUBMENU(e);
int keep_popup = self->parent && self->parent->vtable->type == LTK_WIDGET_MENU && ((ltk_menu *)self->parent)->popup_submenus;
if (in_submenu || !keep_popup) {
ltk_window_unregister_all_popups(self->window);
}
- ltk_queue_specific_event(self, "menu", LTK_PWEVENTMASK_MENUENTRY_PRESS, "press");
+ ltk_widget_emit_signal(self, LTK_MENUENTRY_SIGNAL_PRESSED, LTK_EMPTY_ARGLIST);
return 1;
}
static int
ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
ltk_point glob = ltk_widget_pos_to_global(self, event->x, event->y);
/* FIXME: configure scroll step */
/* FIXME: fix the interface for ltk_menu_scroll */
@@ -720,7 +708,7 @@ ltk_menu_mouse_scroll(ltk_widget *self, ltk_scroll_event *event) {
static void
ltk_menu_hide(ltk_widget *self) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
if (menu->scroll_timer_id >= 0)
ltk_unregister_timer(menu->scroll_timer_id);
menu->scroll_bottom_hover = menu->scroll_top_hover = 0;
@@ -870,29 +858,29 @@ unpopup_active_entry(ltk_menuentry *e) {
static int
ltk_menu_motion_notify(ltk_widget *self, ltk_motion_event *event) {
- set_scroll_timer((ltk_menu *)self, event->x, event->y);
+ set_scroll_timer(LTK_CAST_MENU(self), event->x, event->y);
return 1;
}
static int
ltk_menu_mouse_enter(ltk_widget *self, ltk_motion_event *event) {
- set_scroll_timer((ltk_menu *)self, event->x, event->y);
+ set_scroll_timer(LTK_CAST_MENU(self), event->x, event->y);
return 1;
}
static int
ltk_menu_mouse_leave(ltk_widget *self, ltk_motion_event *event) {
(void)event;
- stop_scrolling((ltk_menu *)self);
+ stop_scrolling(LTK_CAST_MENU(self));
return 1;
}
static ltk_menu *
-ltk_menu_create(ltk_window *window, const char *id, int is_submenu) {
+ltk_menu_create_base(ltk_window *window, int is_submenu) {
ltk_menu *menu = ltk_malloc(sizeof(ltk_menu));
menu->widget.ideal_w = menu_theme.pad;
menu->widget.ideal_h = menu_theme.pad;
- ltk_fill_widget_defaults(&menu->widget, id, window, &vtable, menu->widget.ideal_w, menu->widget.ideal_h);
+ ltk_fill_widget_defaults(&menu->widget, window, &vtable, menu->widget.ideal_w, menu->widget.ideal_h);
menu->widget.dirty = 1;
menu->entries = NULL;
@@ -913,6 +901,16 @@ ltk_menu_create(ltk_window *window, const char *id, int is_submenu) {
return menu;
}
+ltk_menu *
+ltk_menu_create(ltk_window *window) {
+ return ltk_menu_create_base(window, 0);
+}
+
+ltk_menu *
+ltk_submenu_create(ltk_window *window) {
+ return ltk_menu_create_base(window, 1);
+}
+
static int
insert_entry(ltk_menu *menu, ltk_menuentry *e, size_t idx) {
if (idx > menu->num_entries)
@@ -933,7 +931,7 @@ insert_entry(ltk_menu *menu, ltk_menuentry *e, size_t idx) {
static void
recalc_ideal_menu_size(ltk_widget *self, ltk_widget *widget) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
/* If widget with size change is submenu, it doesn't affect this menu */
if (widget && widget->vtable->type == LTK_WIDGET_MENU) {
ltk_widget_resize(widget);
@@ -985,15 +983,13 @@ ltk_menuentry_recalc_ideal_size(ltk_menuentry *entry) {
}
}
-static ltk_menuentry *
-ltk_menuentry_create(ltk_window *window, const char *id, const char *text) {
+ltk_menuentry *
+ltk_menuentry_create(ltk_window *window, const char *text) {
ltk_menuentry *e = ltk_malloc(sizeof(ltk_menuentry));
- e->text_line = ltk_text_line_create(window->text_context, window->theme->font_size, (char *)text, 0, -1);
+ /* FIXME: this should be split up into two versions with const or not const (one for taking over text) */
+ e->text_line = ltk_text_line_create_default(window->theme->font_size, (char *)text, 0, -1);
e->submenu = NULL;
- int w, h;
- ltk_text_line_get_size(e->text_line, &w, &h);
- e->text_surface_key = ltk_surface_cache_get_unnamed_key(window->surface_cache, w, h);
- ltk_fill_widget_defaults(&e->widget, id, window, &entry_vtable, 5, 5);
+ ltk_fill_widget_defaults(&e->widget, window, &entry_vtable, 5, 5);
/* Note: This is only set as a dummy value! The actual ideal size can't
be set until it is part of a menu because it needs to know which
theme it should use */
@@ -1003,12 +999,10 @@ ltk_menuentry_create(ltk_window *window, const char *id, const char *text) {
}
static int
-ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err) {
- ltk_menuentry *e = (ltk_menuentry *)self;
- if (widget != &e->submenu->widget) {
- err->type = ERR_WIDGET_NOT_IN_CONTAINER;
+ltk_menuentry_remove_child(ltk_widget *self, ltk_widget *widget) {
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
+ if (widget != &e->submenu->widget)
return 1;
- }
widget->parent = NULL;
e->submenu = NULL;
ltk_menuentry_recalc_ideal_size(e);
@@ -1017,31 +1011,25 @@ ltk_menuentry_remove_child(ltk_widget *widget, ltk_widget *self, ltk_error *err)
static void
ltk_menuentry_destroy(ltk_widget *self, int shallow) {
- ltk_menuentry *e = (ltk_menuentry *)self;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self);
ltk_text_line_destroy(e->text_line);
- ltk_surface_cache_release_key(e->text_surface_key);
/* FIXME: function to call when parent is destroyed */
/* also function to call when parent added */
if (e->submenu) {
e->submenu->widget.parent = NULL;
if (!shallow) {
- ltk_error err;
- ltk_widget_destroy(&e->submenu->widget, shallow, &err);
+ ltk_widget_destroy(LTK_CAST_WIDGET(e->submenu), shallow);
}
}
ltk_free(e);
}
-static int
-ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, ltk_error *err) {
- if (entry->widget.parent) {
- err->type = ERR_WIDGET_IN_CONTAINER;
- return 1;
- }
- if (insert_entry(menu, entry, idx)) {
- err->type = ERR_INVALID_INDEX;
- return 1;
- }
+int
+ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx) {
+ if (entry->widget.parent)
+ return 1; /* already child of some widget */
+ if (insert_entry(menu, entry, idx))
+ return 2; /* invalid index */
entry->widget.parent = &menu->widget;
ltk_menuentry_recalc_ideal_size(entry);
recalc_ideal_menu_size(&menu->widget, NULL);
@@ -1049,30 +1037,27 @@ ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx, ltk_erro
return 0;
}
-static int
-ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry, ltk_error *err) {
- return ltk_menu_insert_entry(menu, entry, menu->num_entries, err);
+int
+ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry) {
+ return ltk_menu_insert_entry(menu, entry, menu->num_entries);
}
/* FIXME: maybe allow any menu and just change is_submenu (also need to recalculate size then) */
-static int
-ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu, ltk_error *err) {
- if (!submenu->is_submenu) {
- err->type = ERR_MENU_NOT_SUBMENU;
- return 1;
- } else if (e->submenu) {
- err->type = ERR_MENU_ENTRY_CONTAINS_SUBMENU;
- return 1;
- }
+int
+ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu) {
+ if (!submenu->is_submenu)
+ return 1; /* menu is not submenu */
+ else if (e->submenu)
+ return 2; /* entry already contains submenu */
e->submenu = submenu;
ltk_menuentry_recalc_ideal_size(e);
e->widget.dirty = 1;
if (submenu) {
submenu->widget.hidden = 1;
- submenu->widget.parent = &e->widget;
+ submenu->widget.parent = LTK_CAST_WIDGET(e);
}
if (!e->widget.hidden)
- ltk_window_invalidate_widget_rect(e->widget.window, &e->widget);
+ ltk_window_invalidate_widget_rect(e->widget.window, LTK_CAST_WIDGET(e));
return 0;
}
@@ -1087,12 +1072,10 @@ shrink_entries(ltk_menu *menu) {
}
}
-static int
-ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, ltk_error *err) {
- if (idx >= menu->num_entries) {
- err->type = ERR_INVALID_INDEX;
- return 1;
- }
+int
+ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx) {
+ if (idx >= menu->num_entries)
+ return 1; /* invalid index */
menu->entries[idx]->widget.parent = NULL;
ltk_menuentry_recalc_ideal_size(menu->entries[idx]);
memmove(
@@ -1107,63 +1090,55 @@ ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx, ltk_error *err) {
}
static size_t
-get_entry_with_id(ltk_menu *menu, const char *id) {
+get_entry(ltk_menu *menu, ltk_menuentry *entry) {
for (size_t i = 0; i < menu->num_entries; i++) {
- if (!strcmp(id, menu->entries[i]->widget.id))
+ if (menu->entries[i] == entry)
return i;
}
return SIZE_MAX;
}
-static int
-ltk_menu_remove_entry_id(ltk_menu *menu, const char *id, ltk_error *err) {
- size_t idx = get_entry_with_id(menu, id);
- if (idx >= menu->num_entries) {
- err->type = ERR_INVALID_WIDGET_ID;
+int
+ltk_menu_remove_entry(ltk_menu *menu, ltk_menuentry *entry) {
+ size_t idx = get_entry(menu, entry);
+ if (idx >= menu->num_entries)
return 1;
- }
- return ltk_menu_remove_entry_index(menu, idx, err);
+ return ltk_menu_remove_entry_index(menu, idx);
}
static int
-ltk_menu_remove_child(ltk_widget *child, ltk_widget *self, ltk_error *err) {
- ltk_menu *menu = (ltk_menu *)self;
- for (size_t i = 0; i < menu->num_entries; i++) {
- if (&menu->entries[i]->widget == child) {
- return ltk_menu_remove_entry_index(menu, i, err);
- }
- }
- err->type = ERR_WIDGET_NOT_IN_CONTAINER;
- return 1;
+ltk_menu_remove_child(ltk_widget *self, ltk_widget *child) {
+ /* FIXME: this is kind of ugly - it's just there to avoid
+ aborting if a different widget type is given */
+ if (child->vtable->type != LTK_WIDGET_MENUENTRY)
+ return 1;
+ return ltk_menu_remove_entry(LTK_CAST_MENU(self), LTK_CAST_MENUENTRY(child));
}
-static void
+void
ltk_menu_remove_all_entries(ltk_menu *menu) {
for (size_t i = 0; i < menu->num_entries; i++) {
menu->entries[i]->widget.parent = NULL;
ltk_menuentry_recalc_ideal_size(menu->entries[i]);
}
menu->num_entries = menu->num_alloc = 0;
- ltk_free(menu->entries);
- menu->entries = NULL;
+ ltk_free0(menu->entries);
recalc_ideal_menu_size(&menu->widget, NULL);
}
static ltk_widget *
ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
ltk_widget *minw = NULL;
int min_dist = INT_MAX;
- int cx = rect.x + rect.w / 2;
- int cy = rect.y + rect.h / 2;
- ltk_rect r;
- int dist;
for (size_t i = 0; i < menu->num_entries; i++) {
- r = menu->entries[i]->widget.lrect;
- dist = abs((r.x + r.w / 2) - cx) + abs((r.y + r.h / 2) - cy);
+ ltk_rect r = LTK_CAST_WIDGET(menu->entries[i])->lrect;
+ /* FIXME: maybe simplify this since all entries are
+ laid out horizontal/vertical anyways */
+ int dist = ltk_rect_fakedist(rect, r);
if (dist < min_dist) {
min_dist = dist;
- minw = &menu->entries[i]->widget;
+ minw = LTK_CAST_WIDGET(menu->entries[i]);
}
}
return minw;
@@ -1179,17 +1154,18 @@ ltk_menu_nearest_child(ltk_widget *self, ltk_rect rect) {
for the first and second one disappeare */
static ltk_widget *
ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
+ ltk_menuentry *entry = LTK_CAST_MENUENTRY(widget);
ltk_widget *left = NULL;
if (!menu->is_submenu) {
left = ltk_menu_prev_child(self, widget);
} else if (widget->vtable->type == LTK_WIDGET_MENUENTRY &&
- ((ltk_menuentry *)widget)->submenu &&
- !((ltk_menuentry *)widget)->submenu->widget.hidden &&
- ((ltk_menuentry *)widget)->submenu->was_opened_left) {
- left = &((ltk_menuentry *)widget)->submenu->widget;
+ entry->submenu &&
+ !entry->submenu->widget.hidden &&
+ entry->submenu->was_opened_left) {
+ left = LTK_CAST_WIDGET(entry->submenu);
} else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)self->parent;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent);
if (!menu->was_opened_left && IN_SUBMENU(e)) {
left = self->parent;
} else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) {
@@ -1201,21 +1177,22 @@ ltk_menu_nearest_child_left(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
+ ltk_menuentry *entry = LTK_CAST_MENUENTRY(widget);
ltk_widget *right = NULL;
if (!menu->is_submenu) {
right = ltk_menu_next_child(self, widget);
} else if (widget->vtable->type == LTK_WIDGET_MENUENTRY &&
- ((ltk_menuentry *)widget)->submenu &&
- !((ltk_menuentry *)widget)->submenu->widget.hidden &&
- !((ltk_menuentry *)widget)->submenu->was_opened_left) {
- right = &((ltk_menuentry *)widget)->submenu->widget;
+ entry->submenu &&
+ !entry->submenu->widget.hidden &&
+ !entry->submenu->was_opened_left) {
+ right = LTK_CAST_WIDGET(entry->submenu);
} else if (self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)self->parent;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent);
if (menu->was_opened_left && IN_SUBMENU(e)) {
right = self->parent;
} else if (!IN_SUBMENU(e) && e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) {
- right = ltk_menu_next_child(e->widget.parent, &e->widget);
+ right = ltk_menu_next_child(e->widget.parent, LTK_CAST_WIDGET(e));
}
}
return right;
@@ -1223,25 +1200,25 @@ ltk_menu_nearest_child_right(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
ltk_widget *above = NULL;
if (menu->is_submenu) {
above = ltk_menu_prev_child(self, widget);
if (!above && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)self->parent;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent);
if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) {
- ltk_menu *pmenu = (ltk_menu *)e->widget.parent;
+ ltk_menu *pmenu = LTK_CAST_MENU(e->widget.parent);
if (!menu->was_opened_above && !pmenu->is_submenu) {
above = self->parent;
} else if (pmenu->is_submenu) {
- above = ltk_menu_prev_child(e->widget.parent, &e->widget);
+ above = ltk_menu_prev_child(e->widget.parent, LTK_CAST_WIDGET(e));
}
}
}
} else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)widget;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(widget);
if (e->submenu && !e->submenu->widget.hidden && e->submenu->was_opened_above) {
- above = &e->submenu->widget;
+ above = LTK_CAST_WIDGET(e->submenu);
}
}
return above;
@@ -1249,25 +1226,25 @@ ltk_menu_nearest_child_above(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
ltk_widget *below = NULL;
if (menu->is_submenu) {
below = ltk_menu_next_child(self, widget);
if (!below && self->parent && self->parent->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)self->parent;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(self->parent);
if (e->widget.parent && e->widget.parent->vtable->type == LTK_WIDGET_MENU) {
- ltk_menu *pmenu = (ltk_menu *)e->widget.parent;
+ ltk_menu *pmenu = LTK_CAST_MENU(e->widget.parent);
if (menu->was_opened_above && !pmenu->is_submenu) {
below = self->parent;
} else if (pmenu->is_submenu) {
- below = ltk_menu_next_child(e->widget.parent, &e->widget);
+ below = ltk_menu_next_child(e->widget.parent, LTK_CAST_WIDGET(e));
}
}
}
} else if (widget->vtable->type == LTK_WIDGET_MENUENTRY) {
- ltk_menuentry *e = (ltk_menuentry *)widget;
+ ltk_menuentry *e = LTK_CAST_MENUENTRY(widget);
if (e->submenu && !e->submenu->widget.hidden && !e->submenu->was_opened_above) {
- below = &e->submenu->widget;
+ below = LTK_CAST_WIDGET(e->submenu);
}
}
return below;
@@ -1275,48 +1252,50 @@ ltk_menu_nearest_child_below(ltk_widget *self, ltk_widget *widget) {
static ltk_widget *
ltk_menu_prev_child(ltk_widget *self, ltk_widget *child) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
for (size_t i = menu->num_entries; i-- > 0;) {
- if (&menu->entries[i]->widget == child)
- return i > 0 ? &menu->entries[i-1]->widget : NULL;
+ if (LTK_CAST_WIDGET(menu->entries[i]) == child)
+ return i > 0 ? LTK_CAST_WIDGET(menu->entries[i-1]) : NULL;
}
return NULL;
}
static ltk_widget *
ltk_menu_next_child(ltk_widget *self, ltk_widget *child) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
for (size_t i = 0; i < menu->num_entries; i++) {
- if (&menu->entries[i]->widget == child)
- return i < menu->num_entries - 1 ? &menu->entries[i+1]->widget : NULL;
+ if (LTK_CAST_WIDGET(menu->entries[i]) == child)
+ return i < menu->num_entries - 1 ? LTK_CAST_WIDGET(menu->entries[i+1]) : NULL;
}
return NULL;
}
static ltk_widget *
ltk_menu_first_child(ltk_widget *self) {
- ltk_menu *menu = (ltk_menu *)self;
- return menu->num_entries > 0 ? &menu->entries[0]->widget : NULL;
+ ltk_menu *menu = LTK_CAST_MENU(self);
+ return menu->num_entries > 0 ? LTK_CAST_WIDGET(menu->entries[0]) : NULL;
}
static ltk_widget *
ltk_menu_last_child(ltk_widget *self) {
- ltk_menu *menu = (ltk_menu *)self;
- return menu->num_entries > 0 ? &menu->entries[menu->num_entries-1]->widget : NULL;
+ ltk_menu *menu = LTK_CAST_MENU(self);
+ return menu->num_entries > 0 ? LTK_CAST_WIDGET(menu->entries[menu->num_entries-1]) : NULL;
}
/* FIXME: unregister from window popups? */
-static void
+int
ltk_menuentry_detach_submenu(ltk_menuentry *e) {
- if (e->submenu)
- e->submenu->widget.parent = NULL;
+ if (!e->submenu)
+ return 1;
+ e->submenu->widget.parent = NULL;
e->submenu = NULL;
ltk_menuentry_recalc_ideal_size(e);
+ return 0;
}
static void
ltk_menu_destroy(ltk_widget *self, int shallow) {
- ltk_menu *menu = (ltk_menu *)self;
+ ltk_menu *menu = LTK_CAST_MENU(self);
if (!menu) {
ltk_warn("Tried to destroy NULL menu.\n");
return;
@@ -1325,12 +1304,11 @@ ltk_menu_destroy(ltk_widget *self, int shallow) {
ltk_unregister_timer(menu->scroll_timer_id);
ltk_window_unregister_popup(self->window, self);
if (!shallow) {
- ltk_error err;
for (size_t i = 0; i < menu->num_entries; i++) {
/* for efficiency - to avoid ltk_widget_destroy calling
ltk_menu_remove_child for each of the entries */
menu->entries[i]->widget.parent = NULL;
- ltk_widget_destroy(&menu->entries[i]->widget, shallow, &err);
+ ltk_widget_destroy(LTK_CAST_WIDGET(menu->entries[i]), shallow);
}
ltk_free(menu->entries);
} else {
@@ -1338,227 +1316,3 @@ ltk_menu_destroy(ltk_widget *self, int shallow) {
}
ltk_free(menu);
}
-
-/* TODO: get-index-for-id */
-
-/* [sub]menu <menu id> create */
-static int
-ltk_menu_cmd_create(
- ltk_window *window,
- ltk_menu *menu_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)menu_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_menu *menu;
- if (!strcmp(cmd[0].val.str, "menu")) {
- menu = ltk_menu_create(window, cmd[1].val.str, 0);
- } else {
- menu = ltk_menu_create(window, cmd[1].val.str, 1);
- }
- ltk_set_widget((ltk_widget *)menu, cmd[1].val.str);
- return 0;
-}
-
-/* menu <menu id> insert-entry <entry widget id> <index> */
-static int
-ltk_menu_cmd_insert_entry(
- ltk_window *window,
- ltk_menu *menu,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0},
- {.type = CMDARG_INT, .min = 0, .max = menu->num_entries, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_menu_insert_entry(menu, (ltk_menuentry *)cmd[0].val.widget, cmd[1].val.i, err)) {
- err->arg = err->type == ERR_WIDGET_IN_CONTAINER ? 0 : 1;
- return 1;
- }
- return 0;
-}
-
-/* menu <menu id> add-entry <entry widget id> */
-static int
-ltk_menu_cmd_add_entry(
- ltk_window *window,
- ltk_menu *menu,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENUENTRY, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_menu_add_entry(menu, (ltk_menuentry *)cmd[0].val.widget, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* menu <menu id> remove-entry-index <entry index> */
-static int
-ltk_menu_cmd_remove_entry_index(
- ltk_window *window,
- ltk_menu *menu,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_INT, .min = 0, .max = menu->num_entries - 1, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_menu_remove_entry_index(menu, cmd[0].val.i, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* menu <menu id> remove-entry-id <entry id> */
-static int
-ltk_menu_cmd_remove_entry_id(
- ltk_window *window,
- ltk_menu *menu,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_STRING, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_menu_remove_entry_id(menu, cmd[0].val.str, err)) {
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* menu <menu id> remove-all-entries */
-static int
-ltk_menu_cmd_remove_all_entries(
- ltk_window *window,
- ltk_menu *menu,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)window;
- (void)tokens;
- (void)num_tokens;
- (void)err;
- ltk_menu_remove_all_entries(menu);
- return 0;
-}
-
-/* menuentry <id> create <text> */
-static int
-ltk_menuentry_cmd_create(
- ltk_window *window,
- ltk_menuentry *e_unneeded,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)e_unneeded;
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- {.type = CMDARG_IGNORE, .optional = 0},
- {.type = CMDARG_STRING, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (!ltk_widget_id_free(cmd[1].val.str)) {
- err->type = ERR_WIDGET_ID_IN_USE;
- err->arg = 1;
- return 1;
- }
- ltk_menuentry *e = ltk_menuentry_create(window, cmd[1].val.str, cmd[3].val.str);
- ltk_set_widget((ltk_widget *)e, cmd[1].val.str);
- return 0;
-}
-
-/* menuentry <menuentry id> attach-submenu <submenu id> */
-static int
-ltk_menuentry_cmd_attach_submenu(
- ltk_window *window,
- ltk_menuentry *e,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- ltk_cmdarg_parseinfo cmd[] = {
- {.type = CMDARG_WIDGET, .widget_type = LTK_WIDGET_MENU, .optional = 0},
- };
- if (ltk_parse_cmd(window, tokens, num_tokens, cmd, LENGTH(cmd), err))
- return 1;
- if (ltk_menuentry_attach_submenu(e, (ltk_menu *)cmd[0].val.widget, err)) {
- /* FIXME: allow setting err->arg to arg before the args given to function */
- /*err->arg = err->type == ERR_MENU_NOT_SUBMENU ? 0 : -2;*/
- err->arg = 0;
- return 1;
- }
- return 0;
-}
-
-/* menuentry <menuentry id> detach-submenu */
-static int
-ltk_menuentry_cmd_detach_submenu(
- ltk_window *window,
- ltk_menuentry *e,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)window;
- (void)tokens;
- (void)num_tokens;
- (void)err;
- ltk_menuentry_detach_submenu(e);
- return 0;
-}
-
-/* FIXME: sort out menu/submenu - it's weird right now */
-/* FIXME: distinguish between menu/submenu in commands other than create? */
-
-static struct menu_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_menu *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} menu_cmds[] = {
- {"add-entry", <k_menu_cmd_add_entry, 0},
- {"create", <k_menu_cmd_create, 1},
- {"insert-entry", <k_menu_cmd_insert_entry, 0},
- {"remove-all-entries", <k_menu_cmd_remove_all_entries, 0},
- {"remove-entry-index", <k_menu_cmd_remove_entry_index, 0},
- {"remove-entry-id", <k_menu_cmd_remove_entry_id, 0},
-};
-
-static struct menuentry_cmd {
- char *name;
- int (*func)(ltk_window *, ltk_menuentry *, ltk_cmd_token *, size_t, ltk_error *);
- int needs_all;
-} menuentry_cmds[] = {
- {"attach-submenu", <k_menuentry_cmd_attach_submenu, 0},
- {"create", <k_menuentry_cmd_create, 1},
- {"detach-submenu", <k_menuentry_cmd_detach_submenu, 0},
-};
-
-GEN_CMD_HELPERS(ltk_menu_cmd, LTK_WIDGET_MENU, ltk_menu, menu_cmds, struct menu_cmd)
-GEN_CMD_HELPERS(ltk_menuentry_cmd, LTK_WIDGET_MENUENTRY, ltk_menuentry, menuentry_cmds, struct menuentry_cmd)
diff --git a/src/menu.h b/src/menu.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -17,11 +17,19 @@
#ifndef LTK_MENU_H
#define LTK_MENU_H
-#include "cmd.h"
-#include "ltk.h"
+#include <stddef.h>
+
+#include "graphics.h"
#include "text.h"
#include "widget.h"
+#include "window.h"
+
+#define LTK_MENU_SIGNAL_INVALID -1
+#define LTK_MENUENTRY_SIGNAL_PRESSED -1
+#define LTK_MENUENTRY_SIGNAL_INVALID -2
+
+struct ltk_menuentry;
typedef struct ltk_menuentry ltk_menuentry;
typedef struct {
@@ -52,25 +60,37 @@ struct ltk_menuentry {
/* FIXME: I guess if the regular label got the ability to
change its color, a label could just be used instead of this */
ltk_text_line *text_line;
- ltk_surface_cache_key *text_surface_key;
ltk_menu *submenu;
};
-int ltk_menu_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_menu_fill_theme_defaults(ltk_window *window);
-void ltk_menu_uninitialize_theme(ltk_window *window);
-int ltk_submenu_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_submenu_fill_theme_defaults(ltk_window *window);
-void ltk_submenu_uninitialize_theme(ltk_window *window);
+/* FIXME: ltk_orientation for hor/vert!
+should submenus also allow setting orientation?
+-> would maybe look weird in some cases */
+
+int ltk_menu_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_menu_fill_theme_defaults(ltk_renderdata *data);
+void ltk_menu_uninitialize_theme(ltk_renderdata *data);
+int ltk_submenu_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_submenu_fill_theme_defaults(ltk_renderdata *data);
+void ltk_submenu_uninitialize_theme(ltk_renderdata *data);
-int ltk_menuentry_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_menuentry_fill_theme_defaults(ltk_window *window);
-void ltk_menuentry_uninitialize_theme(ltk_window *window);
-int ltk_submenuentry_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_submenuentry_fill_theme_defaults(ltk_window *window);
-void ltk_submenuentry_uninitialize_theme(ltk_window *window);
+int ltk_menuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_menuentry_fill_theme_defaults(ltk_renderdata *data);
+void ltk_menuentry_uninitialize_theme(ltk_renderdata *data);
+int ltk_submenuentry_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_submenuentry_fill_theme_defaults(ltk_renderdata *data);
+void ltk_submenuentry_uninitialize_theme(ltk_renderdata *data);
-GEN_CMD_HELPERS_PROTO(ltk_menu_cmd)
-GEN_CMD_HELPERS_PROTO(ltk_menuentry_cmd)
+/* FIXME: allow orientation */
+ltk_menu *ltk_menu_create(ltk_window *window);
+ltk_menu *ltk_submenu_create(ltk_window *window);
+ltk_menuentry *ltk_menuentry_create(ltk_window *window, const char *text);
+int ltk_menuentry_attach_submenu(ltk_menuentry *e, ltk_menu *submenu);
+int ltk_menuentry_detach_submenu(ltk_menuentry *e);
+int ltk_menu_insert_entry(ltk_menu *menu, ltk_menuentry *entry, size_t idx);
+int ltk_menu_add_entry(ltk_menu *menu, ltk_menuentry *entry);
+int ltk_menu_remove_entry_index(ltk_menu *menu, size_t idx);
+int ltk_menu_remove_entry(ltk_menu *menu, ltk_menuentry *entry);
+void ltk_menu_remove_all_entries(ltk_menu *menu);
#endif /* LTK_MENU_H */
diff --git a/src/proto_types.h b/src/proto_types.h
@@ -1,66 +0,0 @@
-#ifndef LTK_PROTO_TYPES_H
-#define LTK_PROTO_TYPES_H
-
-#define LTK_WIDGET_UNKNOWN 0
-#define LTK_WIDGET_ANY 1
-#define LTK_WIDGET_GRID 2
-#define LTK_WIDGET_BUTTON 3
-#define LTK_WIDGET_LABEL 4
-#define LTK_WIDGET_BOX 5
-#define LTK_WIDGET_MENU 6
-#define LTK_WIDGET_MENUENTRY 7
-#define LTK_WIDGET_ENTRY 8
-#define LTK_WIDGET_IMAGE 9
-#define LTK_NUM_WIDGETS 10
-
-#define LTK_WIDGETMASK_UNKNOWN (UINT32_C(1) << LTK_WIDGET_UNKNOWN)
-#define LTK_WIDGETMASK_ANY (UINT32_C(0xFFFF))
-#define LTK_WIDGETMASK_GRID (UINT32_C(1) << LTK_WIDGET_GRID)
-#define LTK_WIDGETMASK_BUTTON (UINT32_C(1) << LTK_WIDGET_BUTTON)
-#define LTK_WIDGETMASK_LABEL (UINT32_C(1) << LTK_WIDGET_LABEL)
-#define LTK_WIDGETMASK_BOX (UINT32_C(1) << LTK_WIDGET_BOX)
-#define LTK_WIDGETMASK_MENU (UINT32_C(1) << LTK_WIDGET_MENU)
-#define LTK_WIDGETMASK_MENUENTRY (UINT32_C(1) << LTK_WIDGET_MENUENTRY)
-#define LTK_WIDGETMASK_ENTRY (UINT32_C(1) << LTK_WIDGET_ENTRY)
-#define LTK_WIDGETMASK_IMAGE (UINT32_C(1) << LTK_WIDGET_IMAGE)
-
-/* P == protocol; W == widget */
-
-#define LTK_PEVENT_MOUSEPRESS 0
-#define LTK_PEVENT_2MOUSEPRESS 1
-#define LTK_PEVENT_3MOUSEPRESS 2
-#define LTK_PEVENT_MOUSERELEASE 3
-#define LTK_PEVENT_2MOUSERELEASE 4
-#define LTK_PEVENT_3MOUSERELEASE 5
-#define LTK_PEVENT_MOUSEMOTION 6
-#define LTK_PEVENT_MOUSESCROLL 7
-#define LTK_PEVENT_KEYPRESS 8
-#define LTK_PEVENT_KEYRELEASE 9
-#define LTK_PEVENT_CONFIGURE 10
-#define LTK_PEVENT_STATECHANGE 11
-
-/* FIXME: standardize names - internally, buttonpress is used, here it's mousepress... */
-#define LTK_PEVENTMASK_NONE (UINT32_C(0))
-#define LTK_PEVENTMASK_MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_MOUSEPRESS)
-#define LTK_PEVENTMASK_2MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_2MOUSEPRESS)
-#define LTK_PEVENTMASK_3MOUSEPRESS (UINT32_C(1) << LTK_PEVENT_3MOUSEPRESS)
-#define LTK_PEVENTMASK_MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_MOUSERELEASE)
-#define LTK_PEVENTMASK_2MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_2MOUSERELEASE)
-#define LTK_PEVENTMASK_3MOUSERELEASE (UINT32_C(1) << LTK_PEVENT_3MOUSERELEASE)
-#define LTK_PEVENTMASK_MOUSEMOTION (UINT32_C(1) << LTK_PEVENT_MOUSEMOTION)
-#define LTK_PEVENTMASK_KEYPRESS (UINT32_C(1) << LTK_PEVENT_KEYPRESS)
-#define LTK_PEVENTMASK_KEYRELEASE (UINT32_C(1) << LTK_PEVENT_KEYRELEASE)
-#define LTK_PEVENTMASK_CONFIGURE (UINT32_C(1) << LTK_PEVENT_CONFIGURE)
-#define LTK_PEVENTMASK_EXPOSE (UINT32_C(1) << LTK_PEVENT_EXPOSE)
-#define LTK_PEVENTMASK_STATECHANGE (UINT32_C(1) << LTK_PEVENT_STATECHANGE)
-#define LTK_PEVENTMASK_MOUSESCROLL (UINT32_C(1) << LTK_PEVENT_MOUSESCROLL)
-
-#define LTK_PWEVENT_MENUENTRY_PRESS 0
-#define LTK_PWEVENTMASK_MENUENTRY_NONE (UINT32_C(0))
-#define LTK_PWEVENTMASK_MENUENTRY_PRESS (UINT32_C(1) << LTK_PWEVENT_MENUENTRY_PRESS)
-
-#define LTK_PWEVENT_BUTTON_PRESS 0
-#define LTK_PWEVENTMASK_BUTTON_NONE (UINT32_C(0))
-#define LTK_PWEVENTMASK_BUTTON_PRESS (UINT32_C(1) << LTK_PWEVENT_BUTTON_PRESS)
-
-#endif /* LTK_PROTO_TYPES_H */
diff --git a/src/rect.c b/src/rect.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -32,6 +32,20 @@ ltk_rect_intersect(ltk_rect r1, ltk_rect r2) {
return i;
}
+int
+ltk_rect_fakedist(ltk_rect r1, ltk_rect r2) {
+ ltk_rect i = ltk_rect_intersect(r1, r2);
+ /* FIXME: this depends on the weird implementation of intersect above */
+ if (i.w >= 0 && i.h >= 0)
+ return 0;
+ else if (i.w >= 0)
+ return -i.h;
+ else if (i.h >= 0)
+ return -i.w;
+ else
+ return -(i.w + i.h);
+}
+
ltk_rect
ltk_rect_relative(ltk_rect parent, ltk_rect child) {
return (ltk_rect){child.x - parent.x, child.y - parent.y, child.w, child.h};
diff --git a/src/rect.h b/src/rect.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -30,6 +30,7 @@ typedef struct {
} ltk_rect;
ltk_rect ltk_rect_intersect(ltk_rect r1, ltk_rect r2);
+int ltk_rect_fakedist(ltk_rect r1, ltk_rect r2);
ltk_rect ltk_rect_relative(ltk_rect parent, ltk_rect child);
ltk_rect ltk_rect_union(ltk_rect r1, ltk_rect r2);
int ltk_collide_rect(ltk_rect rect, int x, int y);
diff --git a/src/scrollbar.c b/src/scrollbar.c
@@ -1,6 +1,5 @@
-/* FIXME: make scrollbar a "real" widget that is also in widget hash */
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -15,21 +14,17 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <string.h>
-#include <stdarg.h>
+#include <stddef.h>
#include "event.h"
#include "memory.h"
#include "color.h"
#include "rect.h"
#include "widget.h"
-#include "ltk.h"
#include "util.h"
#include "scrollbar.h"
#include "theme.h"
+#include "eventdefs.h"
#define MAX_SCROLLBAR_WIDTH 100 /* completely arbitrary */
@@ -52,19 +47,20 @@ static struct ltk_widget_vtable vtable = {
.mouse_enter = NULL,
.child_size_change = NULL,
.remove_child = NULL,
- .type = LTK_WIDGET_UNKNOWN, /* FIXME */
+ .type = LTK_WIDGET_SCROLLBAR,
/* FIXME: need different activatable state so arrow keys don't move onto scrollbar */
.flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
+ .invalid_signal = LTK_SCROLLBAR_SIGNAL_INVALID,
};
static struct {
int size; /* width or height, depending on orientation */
- ltk_color bg_normal;
- ltk_color bg_disabled;
- ltk_color fg_normal;
- ltk_color fg_active;
- ltk_color fg_pressed;
- ltk_color fg_disabled;
+ ltk_color *bg_normal;
+ ltk_color *bg_disabled;
+ ltk_color *fg_normal;
+ ltk_color *fg_active;
+ ltk_color *fg_pressed;
+ ltk_color *fg_disabled;
} theme;
static ltk_theme_parseinfo parseinfo[] = {
@@ -79,18 +75,18 @@ static ltk_theme_parseinfo parseinfo[] = {
static int parseinfo_sorted = 0;
int
-ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value) {
- return ltk_theme_handle_value(window, "scrollbar", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
+ltk_scrollbar_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "scrollbar", prop, value, parseinfo, LENGTH(parseinfo), &parseinfo_sorted);
}
int
-ltk_scrollbar_fill_theme_defaults(ltk_window *window) {
- return ltk_theme_fill_defaults(window, "scrollbar", parseinfo, LENGTH(parseinfo));
+ltk_scrollbar_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "scrollbar", parseinfo, LENGTH(parseinfo));
}
void
-ltk_scrollbar_uninitialize_theme(ltk_window *window) {
- ltk_theme_uninitialize(window, parseinfo, LENGTH(parseinfo));
+ltk_scrollbar_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, parseinfo, LENGTH(parseinfo));
}
void
@@ -130,8 +126,7 @@ handle_get_rect(ltk_scrollbar *sc) {
/* FIXME: implement clipping directly without extra surface */
static void
ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
- /* FIXME: dirty attribute */
- ltk_scrollbar *scrollbar = (ltk_scrollbar *)self;
+ ltk_scrollbar *scrollbar = LTK_CAST_SCROLLBAR(self);
ltk_color *bg = NULL, *fg = NULL;
ltk_rect lrect = self->lrect;
ltk_rect clip_final = ltk_rect_intersect(clip, (ltk_rect){0, 0, lrect.w, lrect.h});
@@ -139,31 +134,29 @@ ltk_scrollbar_draw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_r
return;
/* FIXME: proper theme for hover */
if (self->state & LTK_DISABLED) {
- bg = &theme.bg_disabled;
- fg = &theme.fg_disabled;
+ bg = theme.bg_disabled;
+ fg = theme.fg_disabled;
} else if (self->state & LTK_PRESSED) {
- bg = &theme.bg_normal;
- fg = &theme.fg_pressed;
+ bg = theme.bg_normal;
+ fg = theme.fg_pressed;
} else if (self->state & LTK_HOVERACTIVE) {
- bg = &theme.bg_normal;
- fg = &theme.fg_active;
+ bg = theme.bg_normal;
+ fg = theme.fg_active;
} else {
- bg = &theme.bg_normal;
- fg = &theme.fg_normal;
+ bg = theme.bg_normal;
+ fg = theme.fg_normal;
}
- ltk_surface *s;
- ltk_surface_cache_request_surface_size(scrollbar->key, lrect.w, lrect.h);
- ltk_surface_cache_get_surface(scrollbar->key, &s);
- ltk_surface_fill_rect(s, bg, (ltk_rect){0, 0, lrect.w, lrect.h});
- /* FIXME: maybe too much calculation in draw function - move to
- resizing function? */
- ltk_surface_fill_rect(s, fg, handle_get_rect(scrollbar));
- ltk_surface_copy(s, draw_surf, clip_final, x + clip_final.x, y + clip_final.y);
+ ltk_rect draw_clip = {x + clip_final.x, y + clip_final.y, clip_final.w, clip_final.h};
+ ltk_surface_fill_rect(draw_surf, bg, draw_clip);
+ draw_clip = handle_get_rect(scrollbar);
+ draw_clip.x += x;
+ draw_clip.y += y;
+ ltk_surface_fill_rect(draw_surf, fg, draw_clip);
}
static int
ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
- ltk_scrollbar *sc = (ltk_scrollbar *)self;
+ ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
int max_pos;
if (event->button != LTK_BUTTONL || event->type != LTK_BUTTONPRESS_EVENT)
return 0;
@@ -184,7 +177,7 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
sc->cur_pos = 0;
else if (sc->cur_pos > max_pos)
sc->cur_pos = max_pos;
- sc->callback(sc->callback_data);
+ ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST);
sc->last_mouse_x = event->x;
sc->last_mouse_y = event->y;
return 1;
@@ -194,7 +187,7 @@ ltk_scrollbar_mouse_press(ltk_widget *self, ltk_button_event *event) {
/* FIXME: improve interface (scaled is weird) */
void
ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) {
- ltk_scrollbar *sc = (ltk_scrollbar *)self;
+ ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
int max_pos;
double scale;
if (sc->orient == LTK_HORIZONTAL) {
@@ -212,12 +205,12 @@ ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled) {
sc->cur_pos = 0;
else if (sc->cur_pos > max_pos)
sc->cur_pos = max_pos;
- sc->callback(sc->callback_data);
+ ltk_widget_emit_signal(self, LTK_SCROLLBAR_SIGNAL_SCROLL, LTK_EMPTY_ARGLIST);
}
static int
ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) {
- ltk_scrollbar *sc = (ltk_scrollbar *)self;
+ ltk_scrollbar *sc = LTK_CAST_SCROLLBAR(self);
int delta;
if (!(self->state & LTK_PRESSED)) {
return 1;
@@ -233,9 +226,9 @@ ltk_scrollbar_motion_notify(ltk_widget *self, ltk_motion_event *event) {
}
ltk_scrollbar *
-ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback)(ltk_widget *), void *data) {
+ltk_scrollbar_create(ltk_window *window, ltk_orientation orient) {
ltk_scrollbar *sc = ltk_malloc(sizeof(ltk_scrollbar));
- ltk_fill_widget_defaults((ltk_widget *)sc, NULL, window, &vtable, 1, 1); /* FIXME: proper size */
+ ltk_fill_widget_defaults(LTK_CAST_WIDGET(sc), window, &vtable, 1, 1); /* FIXME: proper size */
sc->last_mouse_x = sc->last_mouse_y = 0;
/* This cannot be 0 because that leads to divide-by-zero */
sc->virtual_size = 1;
@@ -245,9 +238,6 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback
sc->widget.ideal_h = theme.size;
else
sc->widget.ideal_w = theme.size;
- sc->callback = callback;
- sc->callback_data = data;
- sc->key = ltk_surface_cache_get_unnamed_key(window->surface_cache, sc->widget.ideal_w, sc->widget.ideal_h);
return sc;
}
@@ -255,7 +245,5 @@ ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback
static void
ltk_scrollbar_destroy(ltk_widget *self, int shallow) {
(void)shallow;
- ltk_scrollbar *sc = (ltk_scrollbar *)self;
- ltk_surface_cache_release_key(sc->key);
ltk_free(self);
}
diff --git a/src/scrollbar.h b/src/scrollbar.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,29 +14,31 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _LTK_SCROLLBAR_H_
-#define _LTK_SCROLLBAR_H_
+#ifndef LTK_SCROLLBAR_H
+#define LTK_SCROLLBAR_H
-/* Requires: "rect.h", "widget.h", "ltk.h" */
+#include "graphics.h"
+#include "widget.h"
+#include "window.h"
+
+#define LTK_SCROLLBAR_SIGNAL_SCROLL -1
+#define LTK_SCROLLBAR_SIGNAL_INVALID -2
typedef struct {
ltk_widget widget;
- void (*callback)(ltk_widget *);
- void *callback_data;
double cur_pos;
int virtual_size;
int last_mouse_x;
int last_mouse_y;
ltk_orientation orient;
- ltk_surface_cache_key *key;
} ltk_scrollbar;
void ltk_scrollbar_set_virtual_size(ltk_scrollbar *scrollbar, int virtual_size);
-ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient, void (*callback)(ltk_widget *), void *data);
+ltk_scrollbar *ltk_scrollbar_create(ltk_window *window, ltk_orientation orient);
void ltk_scrollbar_scroll(ltk_widget *self, int delta, int scaled);
-int ltk_scrollbar_ini_handler(ltk_window *window, const char *prop, const char *value);
-int ltk_scrollbar_fill_theme_defaults(ltk_window *window);
-void ltk_scrollbar_uninitialize_theme(ltk_window *window);
+int ltk_scrollbar_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+int ltk_scrollbar_fill_theme_defaults(ltk_renderdata *data);
+void ltk_scrollbar_uninitialize_theme(ltk_renderdata *data);
-#endif /* _LTK_SCROLLBAR_H_ */
+#endif /* LTK_SCROLLBAR_H */
diff --git a/src/surface_cache.c b/src/surface_cache.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2022-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
@@ -14,13 +14,20 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+/* FIXME: properly test this once it's actually needed */
+
+#include <stddef.h>
+
+#include "array.h"
#include "graphics.h"
#include "surface_cache.h"
#include "memory.h"
+#include "util.h"
+#include "widget.h"
/* FIXME: Implement a proper cache that isn't as stupid as this one. */
-#define MAX_CACHE_PIXELS (long)3145728; /* 3*1024*1024 */
+#define MAX_CACHE_PIXELS (long)3145728 /* 3*1024*1024 */
struct ltk_surface_cache_key {
ltk_surface_cache *parent_cache;
@@ -28,16 +35,13 @@ struct ltk_surface_cache_key {
int min_w;
int min_h;
int is_named;
- ltk_widget_type widget_type;
+ int widget_type;
int id;
unsigned int refcount;
};
-struct named_cache_widget_entry {
- ltk_surface_cache_key **entries;
- size_t entries_num;
- size_t entries_alloc;
-};
+LTK_ARRAY_INIT_DECL_STATIC(skey, ltk_surface_cache_key *)
+LTK_ARRAY_INIT_IMPL_STATIC(skey, ltk_surface_cache_key *)
/* FIXME: maybe optimization using pixmap sizes so pixmaps aren't constantly resized
-> somehow make sure already large pixmaps are reused by widgets needing large
@@ -50,9 +54,9 @@ struct cache_surface {
};
struct ltk_surface_cache {
- /* FIXME: many widgets won't use named keys anyways, so this is a bit wasteful */
- ltk_renderdata *renderdata;
- struct named_cache_widget_entry named_keys[LTK_NUM_WIDGETS];
+ ltk_renderwindow *renderwindow;
+ ltk_array(skey) *named_keys;
+ /* FIXME: use generic array for surfaces */
struct cache_surface **surfaces;
size_t surfaces_num; /* total number of stored surfaces */
size_t surfaces_realnum; /* number of currently assigned surfaces */
@@ -69,14 +73,10 @@ struct ltk_surface_cache {
*/
ltk_surface_cache *
-ltk_surface_cache_create(ltk_renderdata *renderdata) {
+ltk_surface_cache_create(ltk_renderwindow *renderwindow) {
ltk_surface_cache *sc = ltk_malloc(sizeof(ltk_surface_cache));
- sc->renderdata = renderdata;
- for (int i = 0; i < LTK_NUM_WIDGETS; i++) {
- sc->named_keys[i].entries = NULL;
- sc->named_keys[i].entries_num = 0;
- sc->named_keys[i].entries_alloc = 0;
- }
+ sc->renderwindow = renderwindow;
+ sc->named_keys = NULL;
sc->surfaces = NULL;
sc->surfaces_num = sc->surfaces_realnum = sc->surfaces_alloc = 0;
sc->clock_pos = 0;
@@ -88,10 +88,8 @@ void
ltk_surface_cache_destroy(ltk_surface_cache *cache) {
/* FIXME: maybe destroy keys as well in case they haven't been released?
That would require to keep track of unnamed keys as well */
- for (int i = 0; i < LTK_NUM_WIDGETS; i++) {
- if (cache->named_keys[i].entries)
- ltk_free(cache->named_keys[i].entries);
- }
+ if (cache->named_keys)
+ ltk_array_destroy(skey, cache->named_keys);
for (size_t i = 0; i < cache->surfaces_realnum; i++) {
ltk_surface_destroy(cache->surfaces[i]->s);
ltk_free(cache->surfaces[i]);
@@ -105,33 +103,29 @@ ltk_surface_cache_destroy(ltk_surface_cache *cache) {
}
ltk_surface_cache_key *
-ltk_surface_cache_get_named_key(ltk_surface_cache *cache, ltk_widget_type type, int id, int min_w, int min_h) {
- //TODO: ltk_assert(type < LTK_NUM_WIDGETS);
- //TODO: ltk_assert(min_w > 0 && min_h > 0);
- struct named_cache_widget_entry *e = &cache->named_keys[type];
+ltk_surface_cache_get_named_key(ltk_surface_cache *cache, int widget_type, int id, int min_w, int min_h) {
+ ltk_assert(min_w > 0 && min_h > 0);
/* FIXME: binary search */
- for (size_t i = 0; i < e->entries_num; i++) {
- if (e->entries[i]->id == id) {
+ if (!cache->named_keys)
+ cache->named_keys = ltk_array_create(skey, 1);
+ for (size_t i = 0; i < ltk_array_len(cache->named_keys); i++) {
+ ltk_surface_cache_key *key = ltk_array_get(cache->named_keys, i);
+ if (key->widget_type == widget_type && key->id == id) {
/* FIXME: how to protect against overflow? */
- e->entries[i]->refcount++;
- return e->entries[i];
+ key->refcount++;
+ return key;
}
}
- if (e->entries_num >= e->entries_alloc) {
- e->entries_alloc = ideal_array_size(e->entries_alloc, e->entries_num + 1);
- e->entries = ltk_reallocarray(e->entries, e->entries_alloc, sizeof(ltk_surface_cache_key *));
- }
ltk_surface_cache_key *key = ltk_malloc(sizeof(ltk_surface_cache_key));
key->parent_cache = cache;
key->s = NULL;
key->min_w = min_w;
key->min_h = min_h;
key->is_named = 1;
- key->widget_type = type;
+ key->widget_type = widget_type;
key->id = id;
key->refcount = 1;
- e->entries[e->entries_num] = key;
- e->entries_num++;
+ ltk_array_append(skey, cache->named_keys, key);
return key;
}
@@ -195,7 +189,7 @@ ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret) {
c->surfaces[i] = NULL;
}
struct cache_surface *cs = ltk_malloc(sizeof(struct cache_surface));
- cs->s = ltk_surface_create(c->renderdata, key->min_w, key->min_h);
+ cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
cs->key = key;
key->s = cs;
c->surfaces[0] = cs;
@@ -217,7 +211,7 @@ ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret) {
struct cache_surface *cs = c->surfaces[c->surfaces_num];
c->surfaces_num++;
c->surfaces_realnum++;
- cs->s = ltk_surface_create(c->renderdata, key->min_w, key->min_h);
+ cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
cs->key = key;
key->s = cs;
c->free_pixels -= (long)key->min_w * key->min_h;
@@ -304,13 +298,13 @@ ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_ret) {
In this case, just create a surface of that size, but it will be the only
surface in the cache. */
/* c->free_pixels should be the maximum amount again here, otherwise there is a bug! */
- /* TODO: ltk_assert(c->free_pixels == MAX_CACHE_PIXELS); */
+ ltk_assert(c->free_pixels == MAX_CACHE_PIXELS);
if (!key->s) {
/* this should be impossible */
if (!c->surfaces[0])
c->surfaces[0] = ltk_malloc(sizeof(struct cache_surface));
struct cache_surface *cs = c->surfaces[0];
- cs->s = ltk_surface_create(c->renderdata, key->min_w, key->min_h);
+ cs->s = ltk_surface_create(c->renderwindow, key->min_w, key->min_h);
cs->key = key;
key->s = cs;
c->surfaces_num = 1;
@@ -329,11 +323,12 @@ ltk_surface_cache_release_key(ltk_surface_cache_key *key) {
if (key->refcount > 0)
key->refcount--;
if (key->refcount == 0) {
- struct named_cache_widget_entry *e = &key->parent_cache->named_keys[key->widget_type];
- for (size_t i = 0; i < e->entries_num; i++) {
- if (e->entries[i]->id == key->id) {
- e->entries[i] = e->entries[e->entries_num - 1];
- e->entries_num--;
+ /* if the key is valid, this must be true */
+ ltk_assert(key->parent_cache->named_keys != NULL);
+ for (size_t i = 0; i < ltk_array_len(key->parent_cache->named_keys); i++) {
+ ltk_surface_cache_key *e = ltk_array_get(key->parent_cache->named_keys, i);
+ if (e == key) {
+ ltk_array_delete(skey, key->parent_cache->named_keys, i, 1);
break;
}
}
diff --git a/src/surface_cache.h b/src/surface_cache.h
@@ -1,5 +1,23 @@
-#ifndef _LTK_SURFACE_CACHE_H_
-#define _LTK_SURFACE_CACHE_H_
+/*
+ * Copyright (c) 2022-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_SURFACE_CACHE_H
+#define LTK_SURFACE_CACHE_H
+
+#include "graphics.h"
/* FIXME: It would probably be much better to just have a named cache
and then pass a single surface around while drawing other widgets */
@@ -9,13 +27,9 @@
typedef struct ltk_surface_cache ltk_surface_cache;
typedef struct ltk_surface_cache_key ltk_surface_cache_key;
-#include "widget.h"
-#include "ltk.h"
-#include "graphics.h"
-
-ltk_surface_cache *ltk_surface_cache_create(ltk_renderdata *renderdata);
+ltk_surface_cache *ltk_surface_cache_create(ltk_renderwindow *renderwindow);
void ltk_surface_cache_destroy(ltk_surface_cache *cache);
-ltk_surface_cache_key *ltk_surface_cache_get_named_key(ltk_surface_cache *cache, ltk_widget_type type, int id, int min_w, int min_h);
+ltk_surface_cache_key *ltk_surface_cache_get_named_key(ltk_surface_cache *cache, int widget_type, int id, int min_w, int min_h);
ltk_surface_cache_key *ltk_surface_cache_get_unnamed_key(ltk_surface_cache *cache, int min_w, int min_h);
/* WARNING: DO NOT RESIZE SURFACES MANUALLY, ALWAYS USE ltk_surface_cache_request_surface_size! */
@@ -28,4 +42,4 @@ int ltk_surface_cache_get_surface(ltk_surface_cache_key *key, ltk_surface **s_re
void ltk_surface_cache_release_key(ltk_surface_cache_key *key);
-#endif /* _LTK_SURFACE_CACHE_H_ */
+#endif /* LTK_SURFACE_CACHE_H */
diff --git a/src/text.h b/src/text.h
@@ -17,12 +17,14 @@
#ifndef LTK_TEXT_H
#define LTK_TEXT_H
+#include <stddef.h>
+#include <stdint.h>
#include "color.h"
#include "graphics.h"
-#include "ltk.h"
+#include "rect.h"
typedef struct ltk_text_line ltk_text_line;
-/* typedef struct ltk_text_context ltk_text_context; */
+typedef struct ltk_text_context ltk_text_context;
ltk_text_context *ltk_text_context_create(ltk_renderdata *data, char *default_font);
void ltk_text_context_destroy(ltk_text_context *ctx);
diff --git a/src/text_pango.c b/src/text_pango.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,27 +14,25 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <math.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdint.h>
-#include <stdarg.h>
+#include <string.h>
-#include <X11/Xos.h>
+#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
+#include <X11/extensions/Xrender.h>
#include <pango/pangoxft.h>
-#include <pango/pango-utils.h> /* for PANGO_VERSION_CHECK */
-#include "xlib_shared.h"
-#include "memory.h"
#include "color.h"
+#include "graphics.h"
+#include "graphics_xlib.h"
+#include "memory.h"
#include "rect.h"
-#include "widget.h"
-#include "ltk.h"
-#include "util.h"
#include "text.h"
+#include "util.h"
struct ltk_text_line {
ltk_text_context *ctx;
@@ -135,6 +133,7 @@ ltk_text_line_draw_clipped(ltk_text_line *tl, ltk_surface *s, ltk_color *color,
{clip.x + clip.w, clip.y + clip.h},
{clip.x, clip.y + clip.h}
};
+ /* FIXME: reuse region? */
Region r = XPolygonRegion(points, 4, EvenOddRule);
/* FIXME: error checking */
XftDrawSetClip(d, r);
diff --git a/src/text_stb.c b/src/text_stb.c
@@ -611,6 +611,7 @@ ltk_text_line_draw(ltk_text_line *tl, ltk_surface *s, ltk_color *color, int x, i
}
last_break = next_break;
}
+ FIXME: pass in window to get at gc
XPutImage(tl->ctx->data->dpy, d, tl->ctx->data->gc, img, 0, 0, x, y, w, h);
XDestroyImage(img);
}
diff --git a/src/theme.c b/src/theme.c
@@ -1,5 +1,23 @@
+/*
+ * Copyright (c) 2022-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 <stdlib.h>
+#include <string.h>
+
#include "graphics.h"
-#include "surface_cache.h"
#include "util.h"
#include "theme.h"
#include "memory.h"
@@ -21,7 +39,7 @@ sort_helper(const void *entry1v, const void *entry2v) {
/* FIXME: more information for errors */
int
-ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted) {
+ltk_theme_handle_value(ltk_renderdata *renderdata, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted) {
if (!*sorted) {
qsort(parseinfo, len, sizeof(ltk_theme_parseinfo), &sort_helper);
*sorted = 1;
@@ -51,7 +69,7 @@ ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, c
entry->initialized = 1;
break;
case THEME_COLOR:
- if (ltk_color_create(window->renderdata, value, entry->ptr.color)) {
+ if (!(*(entry->ptr.color) = ltk_color_create(renderdata, value))) {
ltk_warn("Unable to create color '%s' for property '%s:%s'.\n", value, debug_name, prop);
return 1;
} else {
@@ -100,7 +118,7 @@ ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, c
}
int
-ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len) {
+ltk_theme_fill_defaults(ltk_renderdata *renderdata, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len) {
for (size_t i = 0; i < len; i++) {
ltk_theme_parseinfo *e = &parseinfo[i];
if (e->initialized)
@@ -115,7 +133,7 @@ ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinf
e->initialized = 1;
break;
case THEME_COLOR:
- if (ltk_color_create(window->renderdata, e->defaultval.color, e->ptr.color)) {
+ if (!(*(e->ptr.color) = ltk_color_create(renderdata, e->defaultval.color))) {
ltk_warn("Unable to create default color '%s' for property '%s:%s'.\n", e->defaultval.color, debug_name, e->key);
return 1;
} else {
@@ -139,7 +157,7 @@ ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinf
}
void
-ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_t len) {
+ltk_theme_uninitialize(ltk_renderdata *renderdata, ltk_theme_parseinfo *parseinfo, size_t len) {
for (size_t i = 0; i < len; i++) {
ltk_theme_parseinfo *e = &parseinfo[i];
if (!e->initialized)
@@ -150,7 +168,7 @@ ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_
e->initialized = 0;
break;
case THEME_COLOR:
- ltk_color_destroy(window->renderdata, e->ptr.color);
+ ltk_color_destroy(renderdata, *(e->ptr.color));
e->initialized = 0;
break;
case THEME_INT:
diff --git a/src/theme.h b/src/theme.h
@@ -1,7 +1,25 @@
-#ifndef _LTK_THEME_H_
-#define _LTK_THEME_H_
+/*
+ * Copyright (c) 2022-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 "ltk.h"
+#ifndef LTK_THEME_H
+#define LTK_THEME_H
+
+#include <stddef.h>
+#include "color.h"
+#include "graphics.h"
typedef enum {
THEME_STRING,
@@ -18,7 +36,7 @@ typedef struct {
separate just to make it a bit clearer */
union {
char **str;
- ltk_color *color;
+ ltk_color **color;
int *i;
int *b;
ltk_border_sides *border;
@@ -38,10 +56,9 @@ typedef struct {
int initialized;
} ltk_theme_parseinfo;
-/* FIXME: this only needs renderdata, not window */
/* Both return 1 on error, 0 on success */
-int ltk_theme_handle_value(ltk_window *window, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted);
-int ltk_theme_fill_defaults(ltk_window *window, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len);
-void ltk_theme_uninitialize(ltk_window *window, ltk_theme_parseinfo *parseinfo, size_t len);
+int ltk_theme_handle_value(ltk_renderdata *renderdata, char *debug_name, const char *prop, const char *value, ltk_theme_parseinfo *parseinfo, size_t len, int *sorted);
+int ltk_theme_fill_defaults(ltk_renderdata *renderdata, char *debug_name, ltk_theme_parseinfo *parseinfo, size_t len);
+void ltk_theme_uninitialize(ltk_renderdata *renderdata, ltk_theme_parseinfo *parseinfo, size_t len);
-#endif /* _LTK_THEME_H_ */
+#endif /* LTK_THEME_H */
diff --git a/src/txtbuf.c b/src/txtbuf.c
@@ -1,12 +1,26 @@
+/*
+ * Copyright (c) 2021-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 <stdlib.h>
#include <string.h>
#include <stdarg.h>
-#include "util.h"
#include "memory.h"
#include "txtbuf.h"
-#include "assert.h"
txtbuf *
txtbuf_new(void) {
diff --git a/src/txtbuf.h b/src/txtbuf.h
@@ -1,3 +1,19 @@
+/*
+ * Copyright (c) 2022-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_TXTBUF_H
#define LTK_TXTBUF_H
@@ -102,4 +118,4 @@ char *txtbuf_get_textcopy(txtbuf *buf);
*/
void txtbuf_clear(txtbuf *buf);
-#endif /* LTK_TXTBUF */
+#endif /* LTK_TXTBUF_H */
diff --git a/src/util.c b/src/util.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -15,7 +15,7 @@
*/
#include <pwd.h>
-#include <fcntl.h>
+#include <time.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
@@ -342,6 +342,20 @@ ltk_strcat_useful(const char *str1, const char *str2) {
}
void
+ltk_log_msg(const char *mode, const char *format, va_list args) {
+ char logtime[25]; /* FIXME: This should always be big enough, right? */
+ time_t clock;
+ struct tm *timeptr;
+
+ time(&clock);
+ timeptr = localtime(&clock);
+ strftime(logtime, 25, "%Y-%m-%d %H:%M:%S", timeptr);
+
+ fprintf(stderr, "%s ltk %s: ", logtime, mode);
+ vfprintf(stderr, format, args);
+}
+
+void
ltk_warn(const char *format, ...) {
va_list args;
va_start(args, format);
@@ -355,7 +369,7 @@ ltk_fatal(const char *format, ...) {
va_start(args, format);
ltk_log_msg("Fatal", format, args);
va_end(args);
- ltk_cleanup();
+ ltk_deinit();
exit(1);
}
@@ -411,12 +425,12 @@ next_utf8(char *text, size_t len, size_t index) {
return i;
}
-int
-set_nonblock(int fd) {
- int flags = fcntl(fd, F_GETFL, 0);
- if (flags == -1)
- return -1;
- if (fcntl(fd, F_SETFL, flags | O_NONBLOCK))
- return -1;
- return 0;
+void
+ltk_assert_impl(const char *file, int line, const char *func, const char *failedexpr)
+{
+ (void)fprintf(stderr,
+ "assertion \"%s\" failed: file \"%s\", line %d, function \"%s\"\n",
+ failedexpr, file, line, func);
+ abort();
+ /* NOTREACHED */
}
diff --git a/src/util.h b/src/util.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,8 +14,8 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#ifndef _LTK_UTIL_H_
-#define _LTK_UTIL_H_
+#ifndef LTK_UTIL_H
+#define LTK_UTIL_H
#include <stdarg.h>
#include <stddef.h>
@@ -32,11 +32,11 @@ void ltk_grow_string(char **str, int *alloc_size, int needed);
char *ltk_setup_directory(void);
char *ltk_strcat_useful(const char *str1, const char *str2);
-/* Note: these are actually implemented in ltkd.c and ltkc.c (they are just
- declared here so they can be used by the utility functions */
-void ltk_log_msg(const char *mode, const char *format, va_list args);
-void ltk_cleanup(void);
+/* Note: this is actually implemented in ltk.c (it is just
+ declared here so it can be used by the utility functions */
+void ltk_deinit(void);
+void ltk_log_msg(const char *mode, const char *format, va_list args);
void ltk_fatal_errno(const char *format, ...);
void ltk_warn_errno(const char *format, ...);
void ltk_fatal(const char *format, ...);
@@ -53,8 +53,10 @@ int str_array_equal(const char *terminated, const char *array, size_t len);
size_t prev_utf8(char *text, size_t index);
size_t next_utf8(char *text, size_t len, size_t index);
-int set_nonblock(int fd);
+/* based on the assert found in OpenBSD */
+void ltk_assert_impl(const char *file, int line, const char *func, const char *failedexpr);
+#define ltk_assert(e) ((e) ? (void)0 : ltk_assert_impl(__FILE__, __LINE__, __func__, #e))
#define LENGTH(X) (sizeof(X) / sizeof(X[0]))
-#endif /* _LTK_UTIL_H_ */
+#endif /* LTK_UTIL_H */
diff --git a/src/widget.c b/src/widget.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2022 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,281 +14,20 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include <stdarg.h>
-#include <stdint.h>
+#include <string.h>
-#include "event.h"
#include "rect.h"
#include "widget.h"
-#include "color.h"
-#include "ltk.h"
+#include "window.h"
#include "memory.h"
-#include "util.h"
-#include "khash.h"
-#include "surface_cache.h"
-#include "config.h"
#include "array.h"
-#include "keys.h"
-static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled);
-static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled);
-
-struct key_cb {
- char *func_name;
- int (*callback)(ltk_window *, ltk_key_event *, int handled);
-};
-
-static struct key_cb cb_map[] = {
- {"focus-active", &cb_focus_active},
- {"move-down", &cb_move_down},
- {"move-left", &cb_move_left},
- {"move-next", &cb_move_next},
- {"move-prev", &cb_move_prev},
- {"move-right", &cb_move_right},
- {"move-up", &cb_move_up},
- {"remove-popups", &cb_remove_popups},
- {"set-pressed", &cb_set_pressed},
- {"unfocus-active", &cb_unfocus_active},
- {"unset-pressed", &cb_unset_pressed},
-};
-
-
-struct keypress_cfg {
- ltk_keypress_binding b;
- struct key_cb cb;
-};
-
-struct keyrelease_cfg {
- ltk_keyrelease_binding b;
- struct key_cb cb;
-};
-
-LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
-LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
-LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
-
-static ltk_array(keypress) *keypresses = NULL;
-static ltk_array(keyrelease) *keyreleases = NULL;
-
-GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name)
-
-/* FIXME: most of this is duplicated code */
-/* FIXME: document that pointers inside binding are taken over! */
-int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
-int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
-
-int
-ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
- if (!keypresses)
- keypresses = ltk_array_create(keypress, 1);
- struct key_cb *cb = cb_map_get_entry(func_name, func_len);
- if (!cb)
- return 1;
- struct keypress_cfg cfg = {b, *cb};
- ltk_array_append(keypress, keypresses, cfg);
- return 0;
-}
-
-int
-ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
- if (!keyreleases)
- keyreleases = ltk_array_create(keyrelease, 1);
- struct key_cb *cb = cb_map_get_entry(func_name, func_len);
- if (!cb)
- return 1;
- struct keyrelease_cfg cfg = {b, *cb};
- ltk_array_append(keyrelease, keyreleases, cfg);
- return 0;
-}
-
-static void
-destroy_keypress_cfg(struct keypress_cfg cfg) {
- ltk_keypress_binding_destroy(cfg.b);
-}
-
-void
-ltk_widget_cleanup(void) {
- ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
- ltk_array_destroy(keyrelease, keyreleases);
- keypresses = NULL;
- keyreleases = NULL;
-}
-
-static void ltk_destroy_widget_hash(void);
-
-KHASH_MAP_INIT_STR(widget, ltk_widget *)
-static khash_t(widget) *widget_hash = NULL;
-/* Hack to make ltk_destroy_widget_hash work */
-/* FIXME: any better way to do this? */
-static int hash_locked = 0;
-
-/* needed for passing keyboard events down the hierarchy */
-static ltk_widget **widget_stack = NULL;
-static size_t widget_stack_alloc = 0;
-static size_t widget_stack_len = 0;
-
-static void
-ltk_destroy_widget_hash(void) {
- hash_locked = 1;
- khint_t k;
- ltk_widget *ptr;
- ltk_error err;
- for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
- if (kh_exist(widget_hash, k)) {
- ptr = kh_value(widget_hash, k);
- ltk_free((char *)kh_key(widget_hash, k));
- ltk_widget_destroy(ptr, 1, &err);
- }
- }
- kh_destroy(widget, widget_hash);
- widget_hash = NULL;
- hash_locked = 0;
-}
-
-/* FIXME: any way to optimize the whole event mask handling a bit? */
-void
-ltk_widget_remove_client(int client) {
- khint_t k;
- ltk_widget *ptr;
- for (k = kh_begin(widget_hash); k != kh_end(widget_hash); k++) {
- if (kh_exist(widget_hash, k)) {
- ptr = kh_value(widget_hash, k);
- for (size_t i = 0; i < ptr->masks_num; i++) {
- if (ptr->event_masks[i].client == client) {
- memmove(ptr->event_masks + i, ptr->event_masks + i + 1, ptr->masks_num - i - 1);
- ptr->masks_num--;
- /* FIXME: maybe reset to NULL in that case? */
- if (ptr->masks_num > 0) {
- size_t sz = ideal_array_size(ptr->masks_alloc, ptr->masks_num);
- if (sz != ptr->masks_alloc) {
- ptr->masks_alloc = sz;
- ptr->event_masks = ltk_reallocarray(ptr->event_masks, sz, sizeof(client_event_mask));
- }
- }
- break;
- }
- }
- }
- }
-}
-
-static client_event_mask *
-get_mask_struct(ltk_widget *widget, int client) {
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].client == client)
- return &widget->event_masks[i];
- }
- widget->masks_alloc = ideal_array_size(widget->masks_alloc, widget->masks_num + 1);
- widget->event_masks = ltk_reallocarray(widget->event_masks, widget->masks_alloc, sizeof(client_event_mask));
- client_event_mask *m = &widget->event_masks[widget->masks_num];
- widget->masks_num++;
- m->client = client;
- m->mask = m->lmask = m->wmask = m->lwmask = 0;
- return m;
-}
-
-void
-ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->mask = mask;
-}
-
-void
-ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lmask = mask;
-}
-
-void
-ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->wmask = mask;
-}
-
-void
-ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lwmask = mask;
-}
-
-void
-ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->mask |= mask;
-}
-
-void
-ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lmask |= mask;
-}
-
-void
-ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->wmask |= mask;
-}
-
-void
-ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lwmask |= mask;
-}
-
-void
-ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->mask &= ~mask;
-}
-
-void
-ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lmask &= ~mask;
-}
-
-void
-ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->wmask &= ~mask;
-}
-
-void
-ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask) {
- client_event_mask *m = get_mask_struct(widget, client);
- m->lwmask &= ~mask;
-}
+LTK_ARRAY_INIT_FUNC_DECL_STATIC(signal, ltk_signal_callback_info)
+LTK_ARRAY_INIT_IMPL_STATIC(signal, ltk_signal_callback_info)
void
-ltk_widgets_init() {
- widget_hash = kh_init(widget);
- if (!widget_hash) ltk_fatal_errno("Unable to initialize widget hash table.\n");
-}
-
-void
-ltk_widgets_cleanup() {
- free(widget_stack);
- if (widget_hash)
- ltk_destroy_widget_hash();
-}
-
-void
-ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
+ltk_fill_widget_defaults(ltk_widget *widget, ltk_window *window,
struct ltk_widget_vtable *vtable, int w, int h) {
- if (id)
- widget->id = ltk_strdup(id);
- else
- widget->id = NULL;
widget->window = window;
widget->parent = NULL;
@@ -309,9 +48,6 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
widget->ideal_w = widget->ideal_h = 0;
- widget->event_masks = NULL;
- widget->masks_num = widget->masks_alloc = 0;
-
widget->row = 0;
widget->column = 0;
widget->row_span = 0;
@@ -319,6 +55,9 @@ ltk_fill_widget_defaults(ltk_widget *widget, const char *id, ltk_window *window,
widget->sticky = 0;
widget->dirty = 1;
widget->hidden = 0;
+ widget->vtable_copied = 0;
+ widget->signal_cbs = NULL;
+ /* FIXME: null other members! */
}
void
@@ -368,29 +107,6 @@ ltk_widget_hide(ltk_widget *widget) {
/* FIXME: maybe give global and local position in event */
void
ltk_widget_resize(ltk_widget *widget) {
- int lock_client = -1;
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "eventl %s widget configure %d %d %d %d\n",
- widget->id, widget->lrect.x, widget->lrect.y,
- widget->lrect.w, widget->lrect.h
- );
- lock_client = widget->event_masks[i].client;
- } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "event %s widget configure %d %d %d %d\n",
- widget->id, widget->lrect.x, widget->lrect.y,
- widget->lrect.w, widget->lrect.h
- );
- }
- }
- if (lock_client >= 0) {
- if (ltk_handle_lock_client(widget->window, lock_client))
- return;
- }
if (widget->vtable->resize)
widget->vtable->resize(widget);
widget->dirty = 1;
@@ -400,26 +116,6 @@ void
ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
if (old_state == widget->state)
return;
- int lock_client = -1;
- /* FIXME: give old and new state in event */
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].lmask & LTK_PEVENTMASK_CONFIGURE) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "eventl %s widget statechange\n", widget->id
- );
- lock_client = widget->event_masks[i].client;
- } else if (widget->event_masks[i].mask & LTK_PEVENTMASK_CONFIGURE) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "event %s widget statechange\n", widget->id
- );
- }
- }
- if (lock_client >= 0) {
- if (ltk_handle_lock_client(widget->window, lock_client))
- return;
- }
if (widget->vtable->change_state)
widget->vtable->change_state(widget, old_state);
if (widget->vtable->flags & LTK_NEEDS_REDRAW) {
@@ -428,917 +124,118 @@ ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state) {
}
}
-/* x and y are global! */
-static ltk_widget *
-get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) {
- ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
- ltk_widget *next = NULL;
- *local_x_ret = x - glob.x;
- *local_y_ret = y - glob.y;
- while (widget && widget->vtable->get_child_at_pos) {
- next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret);
- if (!next) {
- break;
- } else {
- widget = next;
- if (next->popup) {
- *local_x_ret = x - next->lrect.x;
- *local_y_ret = y - next->lrect.y;
- } else {
- *local_x_ret -= next->lrect.x;
- *local_y_ret -= next->lrect.y;
- }
- }
- }
- return widget;
-}
-
-static ltk_widget *
-get_hover_popup(ltk_window *window, int x, int y) {
- for (size_t i = window->popups_num; i-- > 0;) {
- if (ltk_collide_rect(window->popups[i]->crect, x, y))
- return window->popups[i];
- }
- return NULL;
-}
-
-static int
-is_parent(ltk_widget *parent, ltk_widget *child) {
- while (child && child != parent) {
- child = child->parent;
- }
- return child != NULL;
-}
-
-/* FIXME: fix global and local coordinates! */
-static int
-queue_mouse_event(ltk_widget *widget, ltk_event_type type, int x, int y) {
- uint32_t mask;
- char *typename;
- switch (type) {
- case LTK_MOTION_EVENT:
- mask = LTK_PEVENTMASK_MOUSEMOTION;
- typename = "mousemotion";
- break;
- case LTK_2BUTTONPRESS_EVENT:
- mask = LTK_PEVENTMASK_2MOUSEPRESS;
- typename = "2mousepress";
- break;
- case LTK_3BUTTONPRESS_EVENT:
- mask = LTK_PEVENTMASK_3MOUSEPRESS;
- typename = "3mousepress";
- break;
- case LTK_BUTTONRELEASE_EVENT:
- mask = LTK_PEVENTMASK_MOUSERELEASE;
- typename = "mouserelease";
- break;
- case LTK_2BUTTONRELEASE_EVENT:
- mask = LTK_PEVENTMASK_2MOUSERELEASE;
- typename = "2mouserelease";
- break;
- case LTK_3BUTTONRELEASE_EVENT:
- mask = LTK_PEVENTMASK_3MOUSERELEASE;
- typename = "3mouserelease";
- break;
- case LTK_BUTTONPRESS_EVENT:
- default:
- mask = LTK_PEVENTMASK_MOUSEPRESS;
- typename = "mousepress";
- break;
- }
- int lock_client = -1;
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].lmask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "eventl %s widget %s %d %d %d %d\n",
- widget->id, typename, x, y, x, y
- /* x - widget->rect.x, y - widget->rect.y */
- );
- lock_client = widget->event_masks[i].client;
- } else if (widget->event_masks[i].mask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "event %s widget %s %d %d %d %d\n",
- widget->id, typename, x, y, x, y
- /* x - widget->rect.x, y - widget->rect.y */
- );
- }
- }
- if (lock_client >= 0) {
- if (ltk_handle_lock_client(widget->window, lock_client))
- return 1;
+/* FIXME: document that it's really dangerous to overwrite remove_child or destroy */
+int
+ltk_widget_destroy(ltk_widget *widget, int shallow) {
+ /* widget->parent->remove_child should never be NULL because of the fact that
+ the widget is set as parent, but let's just check anyways... */
+ int invalid = 0;
+ if (widget->parent) {
+ if (widget->parent->vtable->remove_child)
+ invalid = widget->parent->vtable->remove_child(widget->parent, widget);
}
- return 0;
-}
-
-/* FIXME: global/local coords (like above) */
-static int
-queue_scroll_event(ltk_widget *widget, int x, int y, int dx, int dy) {
- uint32_t mask = LTK_PEVENTMASK_MOUSESCROLL;
- int lock_client = -1;
- for (size_t i = 0; i < widget->masks_num; i++) {
- if (widget->event_masks[i].lmask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "eventl %s widget %s %d %d %d %d %d %d\n",
- widget->id, "mousescroll", x, y, x, y, dx, dy
- /* x - widget->rect.x, y - widget->rect.y */
- );
- lock_client = widget->event_masks[i].client;
- } else if (widget->event_masks[i].mask & mask) {
- ltk_queue_sock_write_fmt(
- widget->event_masks[i].client,
- "event %s widget %s %d %d %d %d %d %d\n",
- widget->id, "mousescroll", x, y, x, y, dx, dy
- /* x - widget->rect.x, y - widget->rect.y */
- );
- }
+ if (widget->vtable_copied) {
+ ltk_free(widget->vtable);
+ widget->vtable = NULL;
}
- if (lock_client >= 0) {
- if (ltk_handle_lock_client(widget->window, lock_client))
- return 1;
+ if (widget->signal_cbs) {
+ ltk_array_destroy(signal, widget->signal_cbs);
+ widget->signal_cbs = NULL;
}
- return 0;
-}
+ widget->vtable->destroy(widget, shallow);
-static void
-ensure_active_widget_shown(ltk_window *window) {
- ltk_widget *widget = window->active_widget;
- if (!widget)
- return;
- ltk_rect r = widget->lrect;
- while (widget->parent) {
- if (widget->parent->vtable->ensure_rect_shown)
- widget->parent->vtable->ensure_rect_shown(widget->parent, r);
- widget = widget->parent;
- r.x += widget->lrect.x;
- r.y += widget->lrect.y;
- /* FIXME: this currently just aborts if a widget is positioned
- absolutely because I'm not sure what the best action would
- be in that case */
- if (widget->popup)
- break;
- }
- ltk_window_invalidate_widget_rect(window, widget);
+ return invalid;
}
-/* FIXME: come up with a more elegant way to handle this? */
-/* FIXME: Handle hidden state here instead of in widgets */
-/* FIXME: handle disabled state */
-static int
-prev_child(ltk_window *window) {
- if (!window->root_widget)
- return 0;
- ltk_config *config = ltk_config_get();
- ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
- ltk_widget *new, *cur = window->active_widget;
- int changed = 0;
- ltk_widget *prevcur = cur;
- while (1) {
- if (cur) {
- while (cur->parent) {
- new = NULL;
- if (cur->parent->vtable->prev_child)
- new = cur->parent->vtable->prev_child(cur->parent, cur);
- if (new) {
- cur = new;
- ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
- while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- last_activatable = cur;
- }
- if (last_activatable) {
- cur = last_activatable;
- changed = 1;
- break;
- }
- } else {
- cur = cur->parent;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- }
- }
- }
- if (!changed) {
- cur = window->root_widget;
- ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
- while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- last_activatable = cur;
- }
- if (last_activatable)
- cur = last_activatable;
- }
- if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
+ltk_point
+ltk_widget_pos_to_global(ltk_widget *widget, int x, int y) {
+ ltk_widget *cur = widget;
+ while (cur) {
+ x += cur->lrect.x;
+ y += cur->lrect.y;
+ if (cur->popup)
break;
- prevcur = cur;
- }
- /* FIXME: What exactly should be done if no activatable widget exists? */
- if (cur != window->active_widget) {
- ltk_window_set_active_widget(window, cur);
- ensure_active_widget_shown(window);
- return 1;
+ cur = cur->parent;
}
- return 0;
+ return (ltk_point){x, y};
}
-static int
-next_child(ltk_window *window) {
- if (!window->root_widget)
- return 0;
- ltk_config *config = ltk_config_get();
- ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
- ltk_widget *new, *cur = window->active_widget;
- int changed = 0;
- ltk_widget *prevcur = cur;
- while (1) {
- if (cur) {
- while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- }
- if (!changed) {
- while (cur->parent) {
- new = NULL;
- if (cur->parent->vtable->next_child)
- new = cur->parent->vtable->next_child(cur->parent, cur);
- if (new) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- }
- if (changed)
- break;
- } else {
- cur = cur->parent;
- }
- }
- }
- }
- if (!changed) {
- cur = window->root_widget;
- if (!(cur->vtable->flags & act_flags)) {
- while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- break;
- }
- }
- if (!(cur->vtable->flags & act_flags))
- cur = window->root_widget;
- }
- if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
+ltk_point
+ltk_global_to_widget_pos(ltk_widget *widget, int x, int y) {
+ ltk_widget *cur = widget;
+ while (cur) {
+ x -= cur->lrect.x;
+ y -= cur->lrect.y;
+ if (cur->popup)
break;
- prevcur = cur;
+ cur = cur->parent;
}
- if (cur != window->active_widget) {
- ltk_window_set_active_widget(window, cur);
- ensure_active_widget_shown(window);
- return 1;
- }
- return 0;
-}
-
-/* FIXME: moving up/down/left/right needs to be rethought
- it generally is a bit weird, and in particular, nearest_child always searches for the child
- that has the smallest distance to the given rect, so it may not be the child that the user
- expects when going down (e.g. a vertical box with one widget closer vertically but on the
- other side horizontally, thus possibly leading to a different widget that is farther away
- vertically to be chosen instead) - what would be logical here? */
-static ltk_widget *
-nearest_child(ltk_widget *widget, ltk_rect r) {
- ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y);
- return widget->vtable->nearest_child(widget, (ltk_rect){local.x, local.y, r.w, r.h});
+ return (ltk_point){x, y};
}
-/* FIXME: maybe wrap around in these two functions? */
-static int
-left_top_child(ltk_window *window, int left) {
- if (!window->root_widget)
- return 0;
- ltk_config *config = ltk_config_get();
- ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
- ltk_widget *new, *cur = window->active_widget;
- ltk_rect old_rect = {0, 0, 0, 0};
- if (cur) {
- ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
- old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
- }
- if (cur) {
- while (cur->parent) {
- new = NULL;
- if (left) {
- if (cur->parent->vtable->nearest_child_left)
- new = cur->parent->vtable->nearest_child_left(cur->parent, cur);
- } else {
- if (cur->parent->vtable->nearest_child_above)
- new = cur->parent->vtable->nearest_child_above(cur->parent, cur);
- }
- if (new) {
- cur = new;
- ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
- while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- last_activatable = cur;
- }
- if (last_activatable) {
- cur = last_activatable;
- break;
- }
- } else {
- cur = cur->parent;
- if (cur->vtable->flags & act_flags) {
- break;
- }
- }
- }
- } else {
- cur = window->root_widget;
- ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
- ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0};
- while (cur->vtable->nearest_child && (new = nearest_child(cur, r))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- last_activatable = cur;
- }
- if (last_activatable)
- cur = last_activatable;
- }
- /* FIXME: What exactly should be done if no activatable widget exists? */
- if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
- ltk_window_set_active_widget(window, cur);
- ensure_active_widget_shown(window);
+int
+ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data) {
+ if ((type >= LTK_WIDGET_SIGNAL_INVALID) || type <= widget->vtable->invalid_signal)
return 1;
+ if (!widget->signal_cbs) {
+ widget->signal_cbs = ltk_array_create(signal, 1);
}
+ ltk_array_append_signal(widget->signal_cbs, (ltk_signal_callback_info){callback, data, type});
return 0;
}
-static int
-right_bottom_child(ltk_window *window, int right) {
- if (!window->root_widget)
+int
+ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args) {
+ if (!widget->signal_cbs)
return 0;
- ltk_config *config = ltk_config_get();
- ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
- ltk_widget *new, *cur = window->active_widget;
- int changed = 0;
- ltk_rect old_rect = {0, 0, 0, 0};
- ltk_rect corner = {0, 0, 0, 0};
- if (cur) {
- ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
- corner = (ltk_rect){glob.x, glob.y, 0, 0};
- old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
- while (cur->vtable->nearest_child && (new = nearest_child(cur, corner))) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- }
- if (!changed) {
- while (cur->parent) {
- new = NULL;
- if (right) {
- if (cur->parent->vtable->nearest_child_right)
- new = cur->parent->vtable->nearest_child_right(cur->parent, cur);
- } else {
- if (cur->parent->vtable->nearest_child_below)
- new = cur->parent->vtable->nearest_child_below(cur->parent, cur);
- }
- if (new) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- while (cur->vtable->nearest_child && (new = nearest_child(cur, old_rect))) {
- cur = new;
- if (cur->vtable->flags & act_flags) {
- changed = 1;
- break;
- }
- }
- if (changed)
- break;
- } else {
- cur = cur->parent;
- }
- }
- }
- } else {
- cur = window->root_widget;
- if (!(cur->vtable->flags & act_flags)) {
- while (cur->vtable->nearest_child && (new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) {
- cur = new;
- if (cur->vtable->flags & act_flags)
- break;
- }
- }
- if (!(cur->vtable->flags & act_flags))
- cur = window->root_widget;
- }
- if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
- ltk_window_set_active_widget(window, cur);
- ensure_active_widget_shown(window);
- return 1;
- }
- return 0;
-}
-
-/* FIXME: maybe just set this when active widget changes */
-/* -> but would also need to change it when widgets are created/destroyed or parents change */
-static void
-gen_widget_stack(ltk_widget *bottom) {
- widget_stack_len = 0;
- while (bottom) {
- if (widget_stack_len + 1 > widget_stack_alloc) {
- widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1);
- widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *));
+ int handled = 0;
+ for (size_t i = 0; i < ltk_array_len(widget->signal_cbs); i++) {
+ if (ltk_array_get(widget->signal_cbs, i).type == type) {
+ handled |= ltk_array_get(widget->signal_cbs, i).callback(widget, args, ltk_array_get(widget->signal_cbs, i).data);
}
- widget_stack[widget_stack_len++] = bottom;
- bottom = bottom->parent;
}
-}
-
-/* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each
- widget type, but what if the program using ltk wants to catch keyboard events even if the widget
- doesn't do that by default? */
-static int
-cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
- /* FIXME: maybe also set widgets above in hierarchy? */
- ltk_widget_state old_state = window->active_widget->state;
- window->active_widget->state |= LTK_FOCUSED;
- ltk_widget_change_state(window->active_widget, old_state);
- return 1;
- }
- return 0;
-}
-
-static int
-cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
- ltk_widget_state old_state = window->active_widget->state;
- window->active_widget->state &= ~LTK_FOCUSED;
- ltk_widget_change_state(window->active_widget, old_state);
- return 1;
- }
- return 0;
-}
-
-static int
-cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return prev_child(window);
-}
-
-static int
-cb_move_next(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return next_child(window);
-}
-
-static int
-cb_move_left(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return left_top_child(window, 1);
-}
-
-static int
-cb_move_right(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return right_bottom_child(window, 1);
-}
-
-static int
-cb_move_up(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return left_top_child(window, 0);
+ return handled;
}
static int
-cb_move_down(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- return right_bottom_child(window, 0);
+filter_by_type(ltk_signal_callback_info *info, void *data) {
+ return info->type == *(int *)data;
}
-static int
-cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
- /* FIXME: only set pressed if needs keyboard? */
- ltk_window_set_pressed_widget(window, window->active_widget, 0);
- return 1;
- }
- return 0;
+size_t
+ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type) {
+ if (!widget->signal_cbs)
+ return 0;
+ return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_type, &type);
}
-static int
-cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- if (window->pressed_widget) {
- ltk_window_set_pressed_widget(window, NULL, 1);
- return 1;
- }
- return 0;
-}
+struct func_wrapper {
+ ltk_signal_callback callback;
+};
static int
-cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) {
- (void)event;
- (void)handled;
- if (window->popups_num > 0) {
- ltk_window_unregister_all_popups(window);
- return 1;
- }
- return 0;
+filter_by_callback(ltk_signal_callback_info *info, void *data) {
+ return info->callback == ((struct func_wrapper *)data)->callback;
}
-/* FIXME: should keyrelease events be ignored if the corresponding keypress event
- was consumed for movement? */
-/* FIXME: check if there's any weirdness when combining return and mouse press */
-/* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */
-/* FIXME: implement key binding flag to run before widget handler is called */
-void
-ltk_window_key_press_event(ltk_window *window, ltk_key_event *event) {
- int handled = 0;
- if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
- gen_widget_stack(window->active_widget);
- for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
- /* FIXME: send event to socket! */
- if (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event)) {
- handled = 1;
- break;
- }
- }
- }
- if (!keypresses)
- return;
- ltk_keypress_binding *b = NULL;
- for (size_t i = 0; i < ltk_array_length(keypresses); i++) {
- b = <k_array_get(keypresses, i).b;
- if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
- continue;
- } else if (b->text) {
- if (event->mapped && !strcmp(b->text, event->mapped))
- handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
- } else if (b->rawtext) {
- if (event->text && !strcmp(b->text, event->text))
- handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
- } else if (b->sym != LTK_KEY_NONE) {
- if (event->sym == b->sym)
- handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
- }
- }
-
-}
-
-/* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */
-void
-ltk_window_key_release_event(ltk_window *window, ltk_key_event *event) {
- /* FIXME: emit event */
- int handled = 0;
- if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
- gen_widget_stack(window->active_widget);
- for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
- if (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event)) {
- handled = 1;
- break;
- }
- }
- }
- if (!keyreleases)
- return;
- ltk_keyrelease_binding *b = NULL;
- for (size_t i = 0; i < ltk_array_length(keyreleases); i++) {
- b = <k_array_get(keyreleases, i).b;
- if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
- continue;
- } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
- handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled);
- }
- }
-}
-
-/* FIXME: This is still weird. */
-void
-ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event) {
- ltk_widget *widget = get_hover_popup(window, event->x, event->y);
- int check_hide = 0;
- if (!widget) {
- widget = window->root_widget;
- check_hide = 1;
- }
- if (!widget) {
- ltk_window_unregister_all_popups(window);
- return;
- }
- int orig_x = event->x, orig_y = event->y;
- ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
- /* FIXME: need to add more flags for more fine-grained control
- -> also, should the widget still get mouse_press even if state doesn't change? */
- /* FIXME: doesn't work with e.g. disabled menu entries */
- if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
- ltk_window_unregister_all_popups(window);
- }
-
- /* FIXME: this doesn't make much sense if the popups aren't a
- hierarchy (right now, they're just menus, so that's always
- a hierarchy */
- /* don't hide popups if they are children of the now pressed widget */
- if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
- ltk_window_unregister_all_popups(window);
-
- /* FIXME: popups don't always have their children geometrically contained within parents,
- so this won't work properly in all cases */
- int first = 1;
- while (cur_widget) {
- int handled = 0;
- ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
- event->x = local.x;
- event->y = local.y;
- if (cur_widget->state != LTK_DISABLED) {
- /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
- get mouse press, but they are only set to pressed if they are activatable */
- if (queue_mouse_event(cur_widget, event->type, event->x, event->y))
- handled = 1;
- else if (cur_widget->vtable->mouse_press)
- handled = cur_widget->vtable->mouse_press(cur_widget, event);
- /* set first non-disabled widget to pressed widget */
- /* FIXME: use config values for all_activatable */
- if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
- ltk_window_set_pressed_widget(window, cur_widget, 0);
- first = 0;
- }
- }
- if (!handled)
- cur_widget = cur_widget->parent;
- else
- break;
- }
-}
-
-void
-ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event) {
- /* FIXME: should it first be sent to pressed widget? */
- ltk_widget *widget = get_hover_popup(window, event->x, event->y);
- if (!widget)
- widget = window->root_widget;
- if (!widget)
- return;
- int orig_x = event->x, orig_y = event->y;
- ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
- /* FIXME: same issue with popups like in mouse_press above */
- while (cur_widget) {
- int handled = 0;
- ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
- event->x = local.x;
- event->y = local.y;
- if (cur_widget->state != LTK_DISABLED) {
- if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
- handled = 1;
- else if (cur_widget->vtable->mouse_scroll)
- handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
- }
- if (!handled)
- cur_widget = cur_widget->parent;
- else
- break;
- }
-}
-
-void
-ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
- ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
- ltk_window_motion_notify_event(window, &e);
-}
-
-void
-ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event) {
- ltk_widget *widget = window->pressed_widget;
- int orig_x = event->x, orig_y = event->y;
- /* FIXME: why does this only take pressed widget and popups into account? */
- if (!widget) {
- widget = get_hover_popup(window, event->x, event->y);
- widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
- }
- /* FIXME: loop up to top of hierarchy if not handled */
- if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) {
- /* NOP */
- } else if (widget && widget->vtable->mouse_release) {
- widget->vtable->mouse_release(widget, event);
- }
- if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
- int release = 0;
- if (window->pressed_widget) {
- ltk_rect prect = window->pressed_widget->lrect;
- ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0);
- if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y))
- release = 1;
- }
- ltk_window_set_pressed_widget(window, NULL, release);
- /* send motion notify to widget under pointer */
- /* FIXME: only when not collide with rect? */
- ltk_window_fake_motion_event(window, orig_x, orig_y);
- }
-}
-
-void
-ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event) {
- ltk_widget *widget = get_hover_popup(window, event->x, event->y);
- int orig_x = event->x, orig_y = event->y;
- if (!widget) {
- widget = window->pressed_widget;
- if (widget) {
- ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
- event->x = local.x;
- event->y = local.y;
- if (widget->vtable->motion_notify)
- widget->vtable->motion_notify(widget, event);
- return;
- }
- widget = window->root_widget;
- }
- if (!widget)
- return;
- ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
- if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) {
- ltk_window_set_hover_widget(widget->window, NULL, event);
- return;
- }
- ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
- int first = 1;
- while (cur_widget) {
- int handled = 0;
- ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
- event->x = local.x;
- event->y = local.y;
- if (cur_widget->state != LTK_DISABLED) {
- if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
- handled = 1;
- else if (cur_widget->vtable->motion_notify)
- handled = cur_widget->vtable->motion_notify(cur_widget, event);
- /* set first non-disabled widget to hover widget */
- /* FIXME: should enter/leave event be sent to parent
- when moving from/to widget nested in parent? */
- /* FIXME: use config values for all_activatable */
- if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
- event->x = orig_x;
- event->y = orig_y;
- ltk_window_set_hover_widget(window, cur_widget, event);
- first = 0;
- }
- }
- if (!handled)
- cur_widget = cur_widget->parent;
- else
- break;
- }
- if (first) {
- event->x = orig_x;
- event->y = orig_y;
- ltk_window_set_hover_widget(window, NULL, event);
- }
-}
-
-int
-ltk_widget_id_free(const char *id) {
- khint_t k;
- k = kh_get(widget, widget_hash, id);
- if (k != kh_end(widget_hash)) {
+size_t
+ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback) {
+ if (!widget->signal_cbs)
return 0;
- }
- return 1;
-}
-
-ltk_widget *
-ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err) {
- khint_t k;
- ltk_widget *widget;
- k = kh_get(widget, widget_hash, id);
- if (k == kh_end(widget_hash)) {
- err->type = ERR_INVALID_WIDGET_ID;
- return NULL;
- }
- widget = kh_value(widget_hash, k);
- if (type != LTK_WIDGET_ANY && widget->vtable->type != type) {
- err->type = ERR_INVALID_WIDGET_TYPE;
- return NULL;
- }
- return widget;
-}
-
-void
-ltk_set_widget(ltk_widget *widget, const char *id) {
- int ret;
- khint_t k;
- /* FIXME: make sure no widget is overwritten here */
- char *tmp = ltk_strdup(id);
- k = kh_put(widget, widget_hash, tmp, &ret);
- kh_value(widget_hash, k) = widget;
+ /* callback can't be passed directly because ISO C forbids
+ conversion of object pointer to function pointer */
+ struct func_wrapper data = {callback};
+ return ltk_array_remove_if(signal, widget->signal_cbs, &filter_by_callback, &data);
}
-void
-ltk_remove_widget(const char *id) {
- if (hash_locked)
- return;
- khint_t k;
- k = kh_get(widget, widget_hash, id);
- if (k != kh_end(widget_hash)) {
- ltk_free((char *)kh_key(widget_hash, k));
- kh_del(widget, widget_hash, k);
- }
-}
-
-int
-ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err) {
- /* widget->parent->remove_child should never be NULL because of the fact that
- the widget is set as parent, but let's just check anyways... */
- int invalid = 0;
- if (widget->parent && widget->parent->vtable->remove_child) {
- invalid = widget->parent->vtable->remove_child(
- widget, widget->parent, err
- );
- }
- ltk_remove_widget(widget->id);
- ltk_free(widget->id);
- widget->id = NULL;
- ltk_free(widget->event_masks);
- widget->event_masks = NULL;
- widget->vtable->destroy(widget, shallow);
-
- return invalid;
-}
+int ltk_widget_register_type(void); /* FIXME */
-int
-ltk_widget_destroy_cmd(
- ltk_window *window,
- ltk_cmd_token *tokens,
- size_t num_tokens,
- ltk_error *err) {
- (void)window;
- int shallow = 1;
- if (num_tokens != 2 && num_tokens != 3) {
- err->type = ERR_INVALID_NUMBER_OF_ARGUMENTS;
- err->arg = -1;
- return 1;
- }
- if (tokens[1].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 1;
- return 1;
- } else if (num_tokens == 3 && tokens[2].contains_nul) {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 2;
- return 1;
+ltk_widget_vtable *
+ltk_widget_get_editable_vtable(ltk_widget *widget) {
+ if (!widget->vtable_copied) {
+ ltk_widget_vtable *vtable = ltk_malloc(sizeof(ltk_widget_vtable));
+ memcpy(vtable, widget->vtable, sizeof(ltk_widget_vtable));
+ widget->vtable_copied = 1;
}
- if (num_tokens == 3) {
- if (strcmp(tokens[2].text, "deep") == 0) {
- shallow = 0;
- } else if (strcmp(tokens[2].text, "shallow") == 0) {
- shallow = 1;
- } else {
- err->type = ERR_INVALID_ARGUMENT;
- err->arg = 2;
- return 1;
- }
- }
- ltk_widget *widget = ltk_get_widget(tokens[1].text, LTK_WIDGET_ANY, err);
- if (!widget) {
- err->arg = 1;
- return 1;
- }
- if (ltk_widget_destroy(widget, shallow, err)) {
- err->arg = -1;
- return 1;
- }
- return 0;
+ return widget->vtable;
}
diff --git a/src/widget.h b/src/widget.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021-2023 lumidify <nobody@lumidify.org>
+ * Copyright (c) 2021-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
@@ -14,21 +14,46 @@
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
+/* need to check what happens when registering signal for destroy, but then calling ltk_destroy_widget from
+that handler - maybe loop if container widget deletes children but they call parent->delete_child again */
#ifndef LTK_WIDGET_H
#define LTK_WIDGET_H
-#include "err.h"
-#include "rect.h"
-#include "event.h"
-#include "config.h"
+/* FIXME: destroy signal for widgets (also window) */
-/* FIXME: SORT OUT INCLUDES PROPERLY! */
+#include <stddef.h>
+#include "array.h"
+#include "event.h"
+#include "graphics.h"
+#include "rect.h"
+#include "util.h"
+struct ltk_widget;
+struct ltk_window;
typedef struct ltk_widget ltk_widget;
-typedef uint32_t ltk_widget_type;
+
+typedef enum {
+ LTK_WIDGET_UNKNOWN = 0,
+ LTK_WIDGET_ANY,
+ LTK_WIDGET_GRID,
+ LTK_WIDGET_BUTTON,
+ LTK_WIDGET_LABEL,
+ LTK_WIDGET_BOX,
+ LTK_WIDGET_MENU,
+ LTK_WIDGET_MENUENTRY,
+ LTK_WIDGET_ENTRY,
+ LTK_WIDGET_IMAGE,
+ LTK_WIDGET_WINDOW,
+ LTK_WIDGET_SCROLLBAR,
+ LTK_NUM_WIDGETS,
+} ltk_widget_type;
+
+/* FIXME: SORT OUT INCLUDES PROPERLY! */
typedef enum {
LTK_ACTIVATABLE_NORMAL = 1,
+ /* only activatable when "all-activatable"
+ is set to true in the config */
LTK_ACTIVATABLE_SPECIAL = 2,
LTK_ACTIVATABLE_ALWAYS = 1|2,
/* FIXME: redundant or needs better name - is implied by entries in vtable
@@ -39,6 +64,7 @@ typedef enum {
LTK_HOVER_IS_ACTIVE = 16,
} ltk_widget_flags;
+/* FIXME: "sticky" is maybe not the correct name anymore */
typedef enum {
LTK_STICKY_NONE = 0,
LTK_STICKY_LEFT = 1 << 0,
@@ -65,24 +91,130 @@ typedef enum {
LTK_DISABLED = 16,
} ltk_widget_state;
-#include "surface_cache.h"
+/* FIXME: need "ltk_register_type" just to get unique integer for type checking */
-struct ltk_window;
+typedef struct {
+ union {
+ int i;
+ size_t sz;
+ char c;
+ ltk_widget *widget;
+ char *str;
+ const char *cstr;
+ ltk_key_event *key_event;
+ ltk_button_event *button_event;
+ ltk_scroll_event *scroll_event;
+ ltk_motion_event *motion_event;
+ ltk_surface *surface;
+ void *v;
+ /* FIXME: maybe rewrite the functions to take
+ pointers instead so this doesn't increase
+ the size of the union (thereby increasing
+ the size of every arg in the arglist) */
+ ltk_rect rect;
+ } arg;
+ enum {
+ LTK_TYPE_INT,
+ LTK_TYPE_SIZE_T,
+ LTK_TYPE_CHAR,
+ LTK_TYPE_WIDGET,
+ LTK_TYPE_STRING,
+ LTK_TYPE_CONST_STRING,
+ LTK_TYPE_KEY_EVENT,
+ LTK_TYPE_BUTTON_EVENT,
+ LTK_TYPE_SCROLL_EVENT,
+ LTK_TYPE_MOTION_EVENT,
+ LTK_TYPE_SURFACE,
+ LTK_TYPE_RECT,
+ LTK_TYPE_VOID,
+ LTK_TYPE_VOIDP,
+ } type;
+} ltk_callback_arg;
-struct ltk_widget_vtable;
+/* FIXME: STRING should be CHARP for consistency */
+#define LTK_MAKE_ARG_INT(data) ((ltk_callback_arg){.type = LTK_TYPE_INT, .arg = {.i = (data)}})
+#define LTK_MAKE_ARG_SIZE_T(data) ((ltk_callback_arg){.type = LTK_TYPE_SIZE_T, .arg = {.sz = (data)}})
+#define LTK_MAKE_ARG_CHAR(data) ((ltk_callback_arg){.type = LTK_TYPE_CHAR, .arg = {.c = (data)}})
+#define LTK_MAKE_ARG_WIDGET(data) ((ltk_callback_arg){.type = LTK_TYPE_WIDGET, .arg = {.widget = (data)}})
+#define LTK_MAKE_ARG_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_STRING, .arg = {.str = (data)}})
+#define LTK_MAKE_ARG_CONST_STRING(data) ((ltk_callback_arg){.type = LTK_TYPE_CONST_STRING, .arg = {.cstr = (data)}})
+#define LTK_MAKE_ARG_KEY_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_KEY_EVENT, .arg = {.key_event = (data)}})
+#define LTK_MAKE_ARG_BUTTON_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_BUTTON_EVENT, .arg = {.button_event = (data)}})
+#define LTK_MAKE_ARG_SCROLL_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_SCROLL_EVENT, .arg = {.scroll_event = (data)}})
+#define LTK_MAKE_ARG_MOTION_EVENT(data) ((ltk_callback_arg){.type = LTK_TYPE_MOTION_EVENT, .arg = {.motion_event = (data)}})
+#define LTK_MAKE_ARG_SURFACE(data) ((ltk_callback_arg){.type = LTK_TYPE_SURFACE, .arg = {.surface = (data)}})
+#define LTK_MAKE_ARG_RECT(data) ((ltk_callback_arg){.type = LTK_TYPE_RECT, .arg = {.rect = (data)}})
+#define LTK_MAKE_ARG_VOIDP(data) ((ltk_callback_arg){.type = LTK_TYPE_VOIDP, .arg = {.v = (data)}})
+#define LTK_ARG_VOID ((ltk_callback_arg){.type = LTK_TYPE_VOID})
+
+#define LTK_CAST_ARG_INT(carg) (ltk_assert(carg.type == LTK_TYPE_INT), carg.arg.i)
+#define LTK_CAST_ARG_SIZE_T(carg) (ltk_assert(carg.type == LTK_TYPE_SIZE_T), carg.arg.sz)
+#define LTK_CAST_ARG_CHAR(carg) (ltk_assert(carg.type == LTK_TYPE_CHAR), carg.arg.c)
+#define LTK_CAST_ARG_WIDGET(carg) (ltk_assert(carg.type == LTK_TYPE_WIDGET), carg.arg.widget)
+#define LTK_CAST_ARG_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_STRING), carg.arg.str)
+#define LTK_CAST_ARG_CONST_STRING(carg) (ltk_assert(carg.type == LTK_TYPE_CONST_STRING), carg.arg.cstr)
+#define LTK_CAST_ARG_KEY_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_KEY_EVENT), carg.arg.key_event)
+#define LTK_CAST_ARG_BUTTON_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_BUTTON_EVENT), carg.arg.button_event)
+#define LTK_CAST_ARG_SCROLL_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_SCROLL_EVENT), carg.arg.scroll_event)
+#define LTK_CAST_ARG_MOTION_EVENT(carg) (ltk_assert(carg.type == LTK_TYPE_MOTION_EVENT), carg.arg.motion_event)
+#define LTK_CAST_ARG_SURFACE(carg) (ltk_assert(carg.type == LTK_TYPE_SURFACE), carg.arg.surface)
+#define LTK_CAST_ARG_RECT(carg) (ltk_assert(carg.type == LTK_TYPE_RECT), carg.arg.rect)
+#define LTK_CAST_ARG_VOIDP(carg) (ltk_assert(carg.type == LTK_TYPE_VOIDP), carg.arg.v)
+
+#define LTK_GET_ARG_INT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_INT(cargs.args[i]))
+#define LTK_GET_ARG_SIZE_T(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SIZE_T(cargs.args[i]))
+#define LTK_GET_ARG_CHAR(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CHAR(cargs.args[i]))
+#define LTK_GET_ARG_WIDGET(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_WIDGET(cargs.args[i]))
+#define LTK_GET_ARG_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_STRING(cargs.args[i]))
+#define LTK_GET_ARG_CONST_STRING(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_CONST_STRING(cargs.args[i]))
+#define LTK_GET_ARG_KEY_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_KEY_EVENT(cargs.args[i]))
+#define LTK_GET_ARG_BUTTON_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_BUTTON_EVENT(cargs.args[i]))
+#define LTK_GET_ARG_SCROLL_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SCROLL_EVENT(cargs.args[i]))
+#define LTK_GET_ARG_MOTION_EVENT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_MOTION_EVENT(cargs.args[i]))
+#define LTK_GET_ARG_SURFACE(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_SURFACE(cargs.args[i]))
+#define LTK_GET_ARG_RECT(cargs, i) (ltk_assert(i >= 0 && i < cargs.num), LTK_CAST_ARG_RECT(cargs.args[i]))
+
+#define LTK_CAST_WIDGET(w) (&(w)->widget)
+#define LTK_CAST_WINDOW(w) (ltk_assert(w->vtable->type == LTK_WIDGET_WINDOW), (ltk_window *)(w))
+#define LTK_CAST_LABEL(w) (ltk_assert(w->vtable->type == LTK_WIDGET_LABEL), (ltk_label *)(w))
+#define LTK_CAST_BUTTON(w) (ltk_assert(w->vtable->type == LTK_WIDGET_BUTTON), (ltk_button *)(w))
+#define LTK_CAST_GRID(w) (ltk_assert(w->vtable->type == LTK_WIDGET_GRID), (ltk_grid *)(w))
+#define LTK_CAST_IMAGE_WIDGET(w) (ltk_assert(w->vtable->type == LTK_WIDGET_IMAGE), (ltk_image_widget *)(w))
+#define LTK_CAST_ENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_ENTRY), (ltk_entry *)(w))
+#define LTK_CAST_MENU(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENU), (ltk_menu *)(w))
+#define LTK_CAST_MENUENTRY(w) (ltk_assert(w->vtable->type == LTK_WIDGET_MENUENTRY), (ltk_menuentry *)(w))
+#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_WIDGET_SIGNAL_INVALID 0
+
+typedef struct {
+ ltk_callback_arg *args;
+ size_t num;
+} ltk_callback_arglist;
+
+#define LTK_EMPTY_ARGLIST ((ltk_callback_arglist){NULL, 0})
+
+/* FIXME: should signals just return int to make it simpler? */
+typedef int (*ltk_signal_callback)(ltk_widget *widget, ltk_callback_arglist args, ltk_callback_arg data);
typedef struct {
- int client; /* index of client */
- uint32_t mask; /* generic event mask */
- uint32_t lmask; /* generic lock mask */
- uint32_t wmask; /* event mask for specific widget type */
- uint32_t lwmask; /* lock event mask for specific widget type */
-} client_event_mask;
+ ltk_signal_callback callback;
+ ltk_callback_arg data;
+ int type;
+} ltk_signal_callback_info;
+
+int ltk_widget_register_signal_handler(ltk_widget *widget, int type, ltk_signal_callback callback, ltk_callback_arg data);
+int ltk_widget_emit_signal(ltk_widget *widget, int type, ltk_callback_arglist args);
+size_t ltk_widget_remove_signal_handler_by_type(ltk_widget *widget, int type);
+size_t ltk_widget_remove_signal_handler_by_callback(ltk_widget *widget, ltk_signal_callback callback);
+int ltk_widget_register_type(void);
+
+LTK_ARRAY_INIT_STRUCT_DECL(signal, ltk_signal_callback_info)
struct ltk_widget {
struct ltk_window *window;
struct ltk_widget *parent;
- char *id;
struct ltk_widget_vtable *vtable;
@@ -102,25 +234,31 @@ struct ltk_widget {
unsigned int ideal_w;
unsigned int ideal_h;
- client_event_mask *event_masks;
- /* FIXME: kind of a waste of space to use size_t here */
- size_t masks_num;
- size_t masks_alloc;
+ /* maybe mask to determine quickly which callbacks are included?
+ default signals only allowed to have one callback? */
+ ltk_array(signal) *signal_cbs;
ltk_widget_state state;
- unsigned int sticky;
+ /* FIXME: store this in grid/box - for row_span, column_span the other cells could be marked with "not top left cell of widget" so they can be skipped */
+ ltk_sticky_mask sticky;
unsigned short row;
unsigned short column;
unsigned short row_span;
unsigned short column_span;
+ /* ALSO NEED SIGNALS LIKE ADD-TEXT (called *before* text is inserted to check validity) - these would need argument
+ FIGURE OUT HOW TO DO KEY MAPPINGS - should reuse parts of builtin mapping handling
+ -> maybe something like tk
+ -> or maybe just say everyone needs to override event handler? but makes simple stuff more difficult
+ -> also need "global" mappings/key event handler for global shortcuts */
/* needed to properly handle handle local coordinates since
popups are positioned globally instead of locally */
char popup;
char dirty;
char hidden;
+ char vtable_copied;
};
-struct ltk_widget_vtable {
+typedef struct ltk_widget_vtable {
int (*key_press)(struct ltk_widget *, ltk_key_event *);
int (*key_release)(struct ltk_widget *, ltk_key_event *);
/* press/release also receive double/triple-click/release */
@@ -155,7 +293,7 @@ struct ltk_widget_vtable {
struct ltk_widget *(*last_child)(struct ltk_widget *self);
void (*child_size_change)(struct ltk_widget *, struct ltk_widget *);
- int (*remove_child)(struct ltk_widget *, struct ltk_widget *, ltk_error *);
+ int (*remove_child)(struct ltk_widget *, struct ltk_widget *);
/* x and y relative to widget's lrect! */
struct ltk_widget *(*get_child_at_pos)(struct ltk_widget *, int x, int y);
/* r is in self's coordinate system */
@@ -163,46 +301,20 @@ struct ltk_widget_vtable {
ltk_widget_type type;
ltk_widget_flags flags;
-};
+ int invalid_signal;
+} ltk_widget_vtable;
void ltk_widget_hide(ltk_widget *widget);
-int ltk_widget_destroy(ltk_widget *widget, int shallow, ltk_error *err);
-int ltk_widget_destroy_cmd(struct ltk_window *window, ltk_cmd_token *tokens, size_t num_tokens, ltk_error *err);
-void ltk_fill_widget_defaults(ltk_widget *widget, const char *id, struct ltk_window *window,
- struct ltk_widget_vtable *vtable, int w, int h);
+int ltk_widget_destroy(ltk_widget *widget, int shallow);
+void ltk_fill_widget_defaults(
+ ltk_widget *widget, struct ltk_window *window,
+ struct ltk_widget_vtable *vtable, int w, int h
+);
void ltk_widget_change_state(ltk_widget *widget, ltk_widget_state old_state);
-/* FIXME: move to separate window.h */
-void ltk_window_key_press_event(ltk_window *window, ltk_key_event *event);
-void ltk_window_key_release_event(ltk_window *window, ltk_key_event *event);
-void ltk_window_mouse_press_event(ltk_window *window, ltk_button_event *event);
-void ltk_window_mouse_scroll_event(ltk_window *window, ltk_scroll_event *event);
-void ltk_window_mouse_release_event(ltk_window *window, ltk_button_event *event);
-void ltk_window_motion_notify_event(ltk_window *window, ltk_motion_event *event);
-void ltk_window_fake_motion_event(ltk_window *window, int x, int y);
-int ltk_widget_id_free(const char *id);
-ltk_widget *ltk_get_widget(const char *id, ltk_widget_type type, ltk_error *err);
-void ltk_set_widget(ltk_widget *widget, const char *id);
-void ltk_remove_widget(const char *id);
-void ltk_widgets_init();
void ltk_widget_resize(ltk_widget *widget);
-void ltk_widget_remove_client(int client);
-void ltk_widget_set_event_mask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_set_event_lmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_set_event_wmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_set_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_add_to_event_mask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_add_to_event_lmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_add_to_event_wmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_add_to_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_remove_from_event_mask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_remove_from_event_lmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_remove_from_event_wmask(ltk_widget *widget, int client, uint32_t mask);
-void ltk_widget_remove_from_event_lwmask(ltk_widget *widget, int client, uint32_t mask);
-
-
-/* FIXME: document that pointers inside binding are taken over! */
-int ltk_widget_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
-int ltk_widget_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
-void ltk_widget_cleanup(void);
+ltk_point ltk_widget_pos_to_global(ltk_widget *widget, int x, int y);
+ltk_point ltk_global_to_widget_pos(ltk_widget *widget, int x, int y);
+
+ltk_widget_vtable *ltk_widget_get_editable_vtable(ltk_widget *widget);
#endif /* LTK_WIDGET_H */
diff --git a/src/window.c b/src/window.c
@@ -0,0 +1,1270 @@
+/*
+ * Copyright (c) 2020-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 <stdlib.h>
+#include <string.h>
+
+#include "ltk.h"
+#include "util.h"
+#include "keys.h"
+#include "array.h"
+#include "theme.h"
+#include "widget.h"
+#include "window.h"
+#include "memory.h"
+#include "eventdefs.h"
+
+#define MAX_WINDOW_FONT_SIZE 200
+
+static void gen_widget_stack(ltk_widget *bottom);
+static ltk_widget *get_hover_popup(ltk_window *window, int x, int y);
+static int is_parent(ltk_widget *parent, ltk_widget *child);
+static ltk_widget *get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret);
+
+static int ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event);
+static int ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event);
+static int ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event);
+static int ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event);
+static int ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event);
+static int ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event);
+static void ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip);
+
+/* FIXME: actually use this properly */
+static struct ltk_widget_vtable vtable = {
+ .key_press = <k_window_key_press_event,
+ .key_release = <k_window_key_release_event,
+ .mouse_press = <k_window_mouse_press_event,
+ .mouse_release = <k_window_mouse_release_event,
+ .release = NULL,
+ .motion_notify = <k_window_motion_notify_event,
+ .mouse_leave = NULL,
+ .mouse_enter = NULL,
+ .change_state = NULL,
+ .get_child_at_pos = NULL,
+ .resize = NULL,
+ .hide = NULL,
+ .draw = <k_window_redraw,
+ .destroy = <k_window_destroy,
+ .child_size_change = NULL,
+ .remove_child = NULL,
+ .type = LTK_WIDGET_WINDOW,
+ .flags = LTK_NEEDS_REDRAW | LTK_ACTIVATABLE_ALWAYS,
+ .invalid_signal = LTK_WINDOW_SIGNAL_INVALID,
+};
+
+static int cb_focus_active(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_prev(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_next(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_left(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_right(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_up(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_move_down(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled);
+static int cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled);
+
+struct key_cb {
+ char *func_name;
+ int (*callback)(ltk_window *, ltk_key_event *, int handled);
+};
+
+static struct key_cb cb_map[] = {
+ {"focus-active", &cb_focus_active},
+ {"move-down", &cb_move_down},
+ {"move-left", &cb_move_left},
+ {"move-next", &cb_move_next},
+ {"move-prev", &cb_move_prev},
+ {"move-right", &cb_move_right},
+ {"move-up", &cb_move_up},
+ {"remove-popups", &cb_remove_popups},
+ {"set-pressed", &cb_set_pressed},
+ {"unfocus-active", &cb_unfocus_active},
+ {"unset-pressed", &cb_unset_pressed},
+};
+
+struct keypress_cfg {
+ ltk_keypress_binding b;
+ struct key_cb cb;
+};
+
+struct keyrelease_cfg {
+ ltk_keyrelease_binding b;
+ struct key_cb cb;
+};
+
+LTK_ARRAY_INIT_DECL_STATIC(keypress, struct keypress_cfg)
+LTK_ARRAY_INIT_IMPL_STATIC(keypress, struct keypress_cfg)
+LTK_ARRAY_INIT_DECL_STATIC(keyrelease, struct keyrelease_cfg)
+LTK_ARRAY_INIT_IMPL_STATIC(keyrelease, struct keyrelease_cfg)
+
+static ltk_array(keypress) *keypresses = NULL;
+static ltk_array(keyrelease) *keyreleases = NULL;
+
+GEN_CB_MAP_HELPERS(cb_map, struct key_cb, func_name)
+
+/* needed for passing keyboard events down the hierarchy */
+static ltk_widget **widget_stack = NULL;
+static size_t widget_stack_alloc = 0;
+static size_t widget_stack_len = 0;
+
+static ltk_window_theme theme;
+static ltk_theme_parseinfo theme_parseinfo[] = {
+ {"bg", THEME_COLOR, {.color = &theme.bg}, {.color = "#000000"}, 0, 0, 0},
+ {"fg", THEME_COLOR, {.color = &theme.fg}, {.color = "#FFFFFF"}, 0, 0, 0},
+ {"font", THEME_STRING, {.str = &theme.font}, {.str = "Monospace"}, 0, 0, 0},
+ {"font-size", THEME_INT, {.i = &theme.font_size}, {.i = 15}, 0, MAX_WINDOW_FONT_SIZE, 0},
+};
+static int theme_parseinfo_sorted = 0;
+
+int
+ltk_window_fill_theme_defaults(ltk_renderdata *data) {
+ return ltk_theme_fill_defaults(data, "window", theme_parseinfo, LENGTH(theme_parseinfo));
+}
+
+int
+ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value) {
+ return ltk_theme_handle_value(data, "window", prop, value, theme_parseinfo, LENGTH(theme_parseinfo), &theme_parseinfo_sorted);
+}
+
+void
+ltk_window_uninitialize_theme(ltk_renderdata *data) {
+ ltk_theme_uninitialize(data, theme_parseinfo, LENGTH(theme_parseinfo));
+}
+
+/* FIXME: maybe ltk_fatal if ltk not initialized? */
+ltk_window_theme *
+ltk_window_get_theme(void) {
+ return &theme;
+}
+
+/* FIXME: most of this is duplicated code */
+
+int
+ltk_window_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b) {
+ if (!keypresses)
+ keypresses = ltk_array_create(keypress, 1);
+ struct key_cb *cb = cb_map_get_entry(func_name, func_len);
+ if (!cb)
+ return 1;
+ struct keypress_cfg cfg = {b, *cb};
+ ltk_array_append(keypress, keypresses, cfg);
+ return 0;
+}
+
+int
+ltk_window_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b) {
+ if (!keyreleases)
+ keyreleases = ltk_array_create(keyrelease, 1);
+ struct key_cb *cb = cb_map_get_entry(func_name, func_len);
+ if (!cb)
+ return 1;
+ struct keyrelease_cfg cfg = {b, *cb};
+ ltk_array_append(keyrelease, keyreleases, cfg);
+ return 0;
+}
+
+static void
+destroy_keypress_cfg(struct keypress_cfg cfg) {
+ ltk_keypress_binding_destroy(cfg.b);
+}
+
+void
+ltk_window_cleanup(void) {
+ ltk_array_destroy_deep(keypress, keypresses, &destroy_keypress_cfg);
+ ltk_array_destroy(keyrelease, keyreleases);
+ free(widget_stack);
+ keypresses = NULL;
+ keyreleases = NULL;
+ widget_stack = NULL;
+}
+
+static void
+ensure_active_widget_shown(ltk_window *window) {
+ ltk_widget *widget = window->active_widget;
+ if (!widget)
+ return;
+ ltk_rect r = widget->lrect;
+ while (widget->parent) {
+ if (widget->parent->vtable->ensure_rect_shown)
+ widget->parent->vtable->ensure_rect_shown(widget->parent, r);
+ widget = widget->parent;
+ r.x += widget->lrect.x;
+ r.y += widget->lrect.y;
+ /* FIXME: this currently just aborts if a widget is positioned
+ absolutely because I'm not sure what the best action would
+ be in that case */
+ if (widget->popup)
+ break;
+ }
+ ltk_window_invalidate_widget_rect(window, widget);
+}
+
+/* FIXME: should keyrelease events be ignored if the corresponding keypress event
+ was consumed for movement? */
+/* FIXME: check if there's any weirdness when combining return and mouse press */
+/* FIXME: maybe it doesn't really make sense to make e.g. text entry pressed when enter is pressed? */
+/* FIXME: implement key binding flag to run before widget handler is called */
+static int
+ltk_window_key_press_event(ltk_widget *self, ltk_key_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ int handled = 0;
+ if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
+ gen_widget_stack(window->active_widget);
+ for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
+ if (widget_stack[i]->vtable->key_press && widget_stack[i]->vtable->key_press(widget_stack[i], event)) {
+ handled = 1;
+ break;
+ }
+ }
+ }
+ if (!keypresses)
+ return 1;
+ ltk_keypress_binding *b = NULL;
+ for (size_t i = 0; i < ltk_array_len(keypresses); i++) {
+ b = <k_array_get(keypresses, i).b;
+ if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
+ continue;
+ } else if (b->text) {
+ if (event->mapped && !strcmp(b->text, event->mapped))
+ handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+ } else if (b->rawtext) {
+ if (event->text && !strcmp(b->text, event->text))
+ handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+ } else if (b->sym != LTK_KEY_NONE) {
+ if (event->sym == b->sym)
+ handled |= ltk_array_get(keypresses, i).cb.callback(window, event, handled);
+ }
+ }
+ return 1;
+}
+
+/* FIXME: need to actually check if any of parent widgets are focused and still pass to them even if bottom widget not focused? */
+static int
+ltk_window_key_release_event(ltk_widget *self, ltk_key_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ /* FIXME: emit event */
+ int handled = 0;
+ if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
+ gen_widget_stack(window->active_widget);
+ for (size_t i = widget_stack_len; i-- > 0 && !handled;) {
+ if (widget_stack[i]->vtable->key_release && widget_stack[i]->vtable->key_release(widget_stack[i], event)) {
+ handled = 1;
+ break;
+ }
+ }
+ }
+ if (!keyreleases)
+ return 1;
+ ltk_keyrelease_binding *b = NULL;
+ for (size_t i = 0; i < ltk_array_len(keyreleases); i++) {
+ b = <k_array_get(keyreleases, i).b;
+ if (b->mods != event->modmask || (!(b->flags & LTK_KEY_BINDING_RUN_ALWAYS) && handled)) {
+ continue;
+ } else if (b->sym != LTK_KEY_NONE && event->sym == b->sym) {
+ handled |= ltk_array_get(keyreleases, i).cb.callback(window, event, handled);
+ }
+ }
+ return 1;
+}
+
+/* FIXME: This is still weird. */
+static int
+ltk_window_mouse_press_event(ltk_widget *self, ltk_button_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ ltk_widget *widget = get_hover_popup(window, event->x, event->y);
+ int check_hide = 0;
+ if (!widget) {
+ widget = window->root_widget;
+ check_hide = 1;
+ }
+ if (!widget) {
+ ltk_window_unregister_all_popups(window);
+ return 1;
+ }
+ int orig_x = event->x, orig_y = event->y;
+ ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
+ /* FIXME: need to add more flags for more fine-grained control
+ -> also, should the widget still get mouse_press even if state doesn't change? */
+ /* FIXME: doesn't work with e.g. disabled menu entries */
+ if (!(cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
+ ltk_window_unregister_all_popups(window);
+ }
+
+ /* FIXME: this doesn't make much sense if the popups aren't a
+ hierarchy (right now, they're just menus, so that's always
+ a hierarchy */
+ /* don't hide popups if they are children of the now pressed widget */
+ if (check_hide && !(window->popups_num > 0 && is_parent(cur_widget, window->popups[0])))
+ ltk_window_unregister_all_popups(window);
+
+ /* FIXME: popups don't always have their children geometrically contained within parents,
+ so this won't work properly in all cases */
+ int first = 1;
+ while (cur_widget) {
+ int handled = 0;
+ ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
+ event->x = local.x;
+ event->y = local.y;
+ if (cur_widget->state != LTK_DISABLED) {
+ /* FIXME: figure out whether this makes sense - currently, all widgets (unless disabled)
+ get mouse press, but they are only set to pressed if they are activatable */
+ if (cur_widget->vtable->mouse_press)
+ handled = cur_widget->vtable->mouse_press(cur_widget, event);
+ /* set first non-disabled widget to pressed widget */
+ /* FIXME: use config values for all_activatable */
+ if (first && event->button == LTK_BUTTONL && event->type == LTK_BUTTONPRESS_EVENT && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
+ ltk_window_set_pressed_widget(window, cur_widget, 0);
+ first = 0;
+ }
+ }
+ if (!handled)
+ cur_widget = cur_widget->parent;
+ else
+ break;
+ }
+ return 1;
+}
+
+static int
+ltk_window_mouse_scroll_event(ltk_widget *self, ltk_scroll_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ /* FIXME: should it first be sent to pressed widget? */
+ ltk_widget *widget = get_hover_popup(window, event->x, event->y);
+ if (!widget)
+ widget = window->root_widget;
+ if (!widget)
+ return 1;
+ int orig_x = event->x, orig_y = event->y;
+ ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
+ /* FIXME: same issue with popups like in mouse_press above */
+ while (cur_widget) {
+ int handled = 0;
+ ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
+ event->x = local.x;
+ event->y = local.y;
+ if (cur_widget->state != LTK_DISABLED) {
+ /* FIXME: see function above
+ if (queue_scroll_event(cur_widget, event->x, event->y, event->dx, event->dy))
+ handled = 1; */
+ if (cur_widget->vtable->mouse_scroll)
+ handled = cur_widget->vtable->mouse_scroll(cur_widget, event);
+ }
+ if (!handled)
+ cur_widget = cur_widget->parent;
+ else
+ break;
+ }
+ return 1;
+}
+
+void
+ltk_window_fake_motion_event(ltk_window *window, int x, int y) {
+ ltk_motion_event e = {.type = LTK_MOTION_EVENT, .x = x, .y = y};
+ /* FIXME: call overwritten method */
+ window->widget.vtable->motion_notify(LTK_CAST_WIDGET(window), &e);
+}
+
+static int
+ltk_window_mouse_release_event(ltk_widget *self, ltk_button_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ ltk_widget *widget = window->pressed_widget;
+ int orig_x = event->x, orig_y = event->y;
+ /* FIXME: why does this only take pressed widget and popups into account? */
+ if (!widget) {
+ widget = get_hover_popup(window, event->x, event->y);
+ widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
+ }
+ /* FIXME: loop up to top of hierarchy if not handled */
+ /* FIXME: see functions above
+ if (widget && queue_mouse_event(widget, event->type, event->x, event->y)) { */
+ /* NOP */
+ if (widget) {
+ if (widget->vtable->mouse_release)
+ widget->vtable->mouse_release(widget, event);
+ }
+ if (event->button == LTK_BUTTONL && event->type == LTK_BUTTONRELEASE_EVENT) {
+ int release = 0;
+ if (window->pressed_widget) {
+ ltk_rect prect = window->pressed_widget->lrect;
+ ltk_point pglob = ltk_widget_pos_to_global(window->pressed_widget, 0, 0);
+ if (ltk_collide_rect((ltk_rect){pglob.x, pglob.y, prect.w, prect.h}, orig_x, orig_y))
+ release = 1;
+ }
+ ltk_window_set_pressed_widget(window, NULL, release);
+ /* send motion notify to widget under pointer */
+ /* FIXME: only when not collide with rect? */
+ ltk_window_fake_motion_event(window, orig_x, orig_y);
+ }
+ return 1;
+}
+
+static int
+ltk_window_motion_notify_event(ltk_widget *self, ltk_motion_event *event) {
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ ltk_widget *widget = get_hover_popup(window, event->x, event->y);
+ int orig_x = event->x, orig_y = event->y;
+ if (!widget) {
+ widget = window->pressed_widget;
+ if (widget) {
+ ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
+ event->x = local.x;
+ event->y = local.y;
+ if (widget->vtable->motion_notify)
+ widget->vtable->motion_notify(widget, event);
+ return 1;
+ }
+ widget = window->root_widget;
+ }
+ if (!widget)
+ return 1;
+ ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
+ if (!ltk_collide_rect((ltk_rect){0, 0, widget->lrect.w, widget->lrect.h}, local.x, local.y)) {
+ ltk_window_set_hover_widget(widget->window, NULL, event);
+ return 1;
+ }
+ ltk_widget *cur_widget = get_widget_under_pointer(widget, event->x, event->y, &event->x, &event->y);
+ int first = 1;
+ while (cur_widget) {
+ int handled = 0;
+ ltk_point local = ltk_global_to_widget_pos(cur_widget, orig_x, orig_y);
+ event->x = local.x;
+ event->y = local.y;
+ if (cur_widget->state != LTK_DISABLED) {
+ /* FIXME: see functions above
+ if (queue_mouse_event(cur_widget, LTK_MOTION_EVENT, event->x, event->y))
+ handled = 1; */
+ if (cur_widget->vtable->motion_notify)
+ handled = cur_widget->vtable->motion_notify(cur_widget, event);
+ /* set first non-disabled widget to hover widget */
+ /* FIXME: should enter/leave event be sent to parent
+ when moving from/to widget nested in parent? */
+ /* FIXME: use config values for all_activatable */
+ if (first && (cur_widget->vtable->flags & LTK_ACTIVATABLE_ALWAYS)) {
+ event->x = orig_x;
+ event->y = orig_y;
+ ltk_window_set_hover_widget(window, cur_widget, event);
+ first = 0;
+ }
+ }
+ if (!handled)
+ cur_widget = cur_widget->parent;
+ else
+ break;
+ }
+ if (first) {
+ event->x = orig_x;
+ event->y = orig_y;
+ ltk_window_set_hover_widget(window, NULL, event);
+ }
+ return 1;
+}
+
+void
+ltk_window_set_root_widget(ltk_window *window, ltk_widget *widget) {
+ window->root_widget = widget;
+ widget->lrect.x = 0;
+ widget->lrect.y = 0;
+ widget->lrect.w = window->rect.w;
+ widget->lrect.h = window->rect.h;
+ widget->crect = widget->lrect;
+ ltk_window_invalidate_rect(window, widget->lrect);
+ ltk_widget_resize(widget);
+}
+
+void
+ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect) {
+ if (window->dirty_rect.w == 0 && window->dirty_rect.h == 0)
+ window->dirty_rect = rect;
+ else
+ window->dirty_rect = ltk_rect_union(rect, window->dirty_rect);
+}
+
+void
+ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget) {
+ ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
+ ltk_window_invalidate_rect(window, (ltk_rect){glob.x, glob.y, widget->lrect.w, widget->lrect.h});
+}
+
+static void
+ltk_window_redraw(ltk_widget *self, ltk_surface *draw_surf, int x, int y, ltk_rect clip) {
+ (void)draw_surf;
+ (void)x;
+ (void)y;
+ (void)clip;
+ ltk_window *window = LTK_CAST_WINDOW(self);
+ ltk_widget *ptr;
+ if (!window) return;
+ if (window->dirty_rect.x >= window->rect.w) return;
+ if (window->dirty_rect.y >= window->rect.h) return;
+ if (window->dirty_rect.x + window->dirty_rect.w > window->rect.w)
+ window->dirty_rect.w -= window->dirty_rect.x + window->dirty_rect.w - window->rect.w;
+ if (window->dirty_rect.y + window->dirty_rect.h > window->rect.h)
+ window->dirty_rect.h -= window->dirty_rect.y + window->dirty_rect.h - window->rect.h;
+ /* FIXME: this should use window->dirty_rect, but that doesn't work
+ properly with double buffering */
+ ltk_surface_fill_rect(window->surface, window->theme->bg, (ltk_rect){0, 0, window->rect.w, window->rect.h});
+ if (window->root_widget) {
+ ptr = window->root_widget;
+ ptr->vtable->draw(ptr, window->surface, 0, 0, window->rect);
+ }
+ /* last popup is the newest one, so draw that last */
+ for (size_t i = 0; i < window->popups_num; i++) {
+ ptr = window->popups[i];
+ ptr->vtable->draw(ptr, window->surface, ptr->lrect.x, ptr->lrect.y, ltk_rect_relative(ptr->lrect, window->rect));
+ }
+ renderer_swap_buffers(window->renderwindow);
+ window->dirty_rect.w = 0;
+ window->dirty_rect.h = 0;
+}
+
+static void
+ltk_window_other_event(ltk_window *window, ltk_event *event) {
+ ltk_widget *ptr = window->root_widget;
+ /* FIXME: decide whether this should be moved to separate resize function in window vtable */
+ if (event->type == LTK_CONFIGURE_EVENT) {
+ ltk_window_unregister_all_popups(window);
+ int w, h;
+ w = event->configure.w;
+ h = event->configure.h;
+ int orig_w = window->rect.w;
+ int orig_h = window->rect.h;
+ if (orig_w != w || orig_h != h) {
+ window->rect.w = w;
+ window->rect.h = h;
+ ltk_window_invalidate_rect(window, window->rect);
+ ltk_surface_update_size(window->surface, w, h);
+ if (ptr) {
+ ptr->lrect.w = w;
+ ptr->lrect.h = h;
+ ptr->crect = ptr->lrect;
+ ltk_widget_resize(ptr);
+ }
+ }
+ } else if (event->type == LTK_EXPOSE_EVENT) {
+ ltk_rect r;
+ r.x = event->expose.x;
+ r.y = event->expose.y;
+ r.w = event->expose.w;
+ r.h = event->expose.h;
+ ltk_window_invalidate_rect(window, r);
+ } else if (event->type == LTK_WINDOWCLOSE_EVENT) {
+ ltk_widget_emit_signal(LTK_CAST_WIDGET(window), LTK_WINDOW_SIGNAL_CLOSE, LTK_EMPTY_ARGLIST);
+ }
+}
+
+/* FIXME: check for duplicates? */
+void
+ltk_window_register_popup(ltk_window *window, ltk_widget *popup) {
+ if (window->popups_num == window->popups_alloc) {
+ window->popups_alloc = ideal_array_size(
+ window->popups_alloc, window->popups_num + 1
+ );
+ window->popups = ltk_reallocarray(
+ window->popups, window->popups_alloc, sizeof(ltk_widget *)
+ );
+ }
+ window->popups[window->popups_num++] = popup;
+ popup->popup = 1;
+}
+
+void
+ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup) {
+ if (window->popups_locked)
+ return;
+ for (size_t i = 0; i < window->popups_num; i++) {
+ if (window->popups[i] == popup) {
+ popup->popup = 0;
+ memmove(
+ window->popups + i,
+ window->popups + i + 1,
+ sizeof(ltk_widget *) * (window->popups_num - i - 1)
+ );
+ window->popups_num--;
+ size_t sz = ideal_array_size(
+ window->popups_alloc, window->popups_num
+ );
+ if (sz != window->popups_alloc) {
+ window->popups_alloc = sz;
+ window->popups = ltk_reallocarray(
+ window->popups, sz, sizeof(ltk_widget *)
+ );
+ }
+ return;
+ }
+ }
+}
+
+/* FIXME: where should actual hiding happen? */
+void
+ltk_window_unregister_all_popups(ltk_window *window) {
+ window->popups_locked = 1;
+ for (size_t i = 0; i < window->popups_num; i++) {
+ window->popups[i]->hidden = 1;
+ window->popups[i]->popup = 0;
+ ltk_widget_hide(window->popups[i]);
+ }
+ window->popups_num = 0;
+ /* somewhat arbitrary, but should be enough for most cases */
+ if (window->popups_num > 4) {
+ window->popups = ltk_reallocarray(
+ window->popups, 4, sizeof(ltk_widget *)
+ );
+ window->popups_alloc = 4;
+ }
+ window->popups_locked = 0;
+ /* I guess just invalidate everything instead of being smart */
+ ltk_window_invalidate_rect(window, window->rect);
+}
+
+/* FIXME: support more options like child windows */
+ltk_window *
+ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h) {
+ ltk_window *window = ltk_malloc(sizeof(ltk_window));
+ /* this is a bit weird because the window entry points to itself */
+ /* the ideal width isn't needed for a window */
+ ltk_fill_widget_defaults(&window->widget, window, &vtable, 0, 0);
+
+ window->popups = NULL;
+ window->popups_num = window->popups_alloc = 0;
+ window->popups_locked = 0;
+
+ window->renderwindow = renderer_create_window(data, title, x, y, w, h);
+ renderer_set_window_properties(window->renderwindow, theme.bg);
+ window->theme = &theme;
+
+ window->root_widget = NULL;
+ window->hover_widget = NULL;
+ window->active_widget = NULL;
+ window->pressed_widget = NULL;
+
+ //FIXME: use widget rect
+ window->rect.w = w;
+ window->rect.h = h;
+ window->rect.x = 0;
+ window->rect.y = 0;
+ window->dirty_rect.w = 0;
+ window->dirty_rect.h = 0;
+ window->dirty_rect.x = 0;
+ window->dirty_rect.y = 0;
+
+ window->surface_cache = ltk_surface_cache_create(window->renderwindow);
+ window->surface = ltk_surface_from_window(window->renderwindow, w, h);
+
+ return window;
+}
+
+/* FIXME: check if widget window matches in all public functions */
+
+void
+ltk_window_destroy_intern(ltk_window *window) {
+ if (window->root_widget) {
+ ltk_widget_destroy(window->root_widget, 0);
+ }
+ if (window->popups)
+ ltk_free(window->popups);
+ ltk_surface_cache_destroy(window->surface_cache);
+ ltk_surface_destroy(window->surface);
+ renderer_destroy_window(window->renderwindow);
+ ltk_free(window);
+}
+
+/* event must have global coordinates! */
+void
+ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event) {
+ ltk_widget *old = window->hover_widget;
+ if (old == widget)
+ return;
+ int orig_x = event->x, orig_y = event->y;
+ if (old) {
+ ltk_widget_state old_state = old->state;
+ old->state &= ~LTK_HOVER;
+ ltk_widget_change_state(old, old_state);
+ ltk_point local = ltk_global_to_widget_pos(old, event->x, event->y);
+ event->x = local.x;
+ event->y = local.y;
+ if (old->vtable->mouse_leave)
+ old->vtable->mouse_leave(old, event);
+ event->x = orig_x;
+ event->y = orig_y;
+ }
+ window->hover_widget = widget;
+ if (widget) {
+ ltk_point local = ltk_global_to_widget_pos(widget, event->x, event->y);
+ event->x = local.x;
+ event->y = local.y;
+ if (widget->vtable->mouse_enter)
+ widget->vtable->mouse_enter(widget, event);
+ ltk_widget_state old_state = widget->state;
+ widget->state |= LTK_HOVER;
+ ltk_widget_change_state(widget, old_state);
+ if ((widget->vtable->flags & LTK_HOVER_IS_ACTIVE) && widget != window->active_widget)
+ ltk_window_set_active_widget(window, widget);
+ }
+}
+
+void
+ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget) {
+ if (window->active_widget == widget) {
+ return;
+ }
+ ltk_widget *old = window->active_widget;
+ /* Note: this has to be set at the beginning to
+ avoid infinite recursion in some cases */
+ window->active_widget = widget;
+ ltk_widget *common_parent = NULL;
+ if (widget) {
+ ltk_widget *cur = widget;
+ while (cur) {
+ if (cur->state & LTK_ACTIVE) {
+ common_parent = cur;
+ break;
+ }
+ ltk_widget_state old_state = cur->state;
+ cur->state |= LTK_ACTIVE;
+ /* FIXME: should all be set focused? */
+ if (cur == widget && !(cur->vtable->flags & LTK_NEEDS_KEYBOARD))
+ widget->state |= LTK_FOCUSED;
+ ltk_widget_change_state(cur, old_state);
+ cur = cur->parent;
+ }
+ }
+ /* FIXME: better variable names; generally make this nicer */
+ /* special case if old is parent of new active widget */
+ ltk_widget *tmp = common_parent;
+ while (tmp) {
+ if (tmp == old)
+ return;
+ tmp = tmp->parent;
+ }
+ if (old) {
+ old->state &= ~LTK_FOCUSED;
+ ltk_widget *cur = old;
+ while (cur) {
+ if (cur == common_parent)
+ break;
+ ltk_widget_state old_state = cur->state;
+ cur->state &= ~LTK_ACTIVE;
+ ltk_widget_change_state(cur, old_state);
+ cur = cur->parent;
+ }
+ }
+}
+
+void
+ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release) {
+ if (window->pressed_widget == widget)
+ return;
+ if (window->pressed_widget) {
+ ltk_widget_state old_state = window->pressed_widget->state;
+ window->pressed_widget->state &= ~LTK_PRESSED;
+ ltk_widget_change_state(window->pressed_widget, old_state);
+ ltk_window_set_active_widget(window, window->pressed_widget);
+ /* FIXME: this is a bit weird because the release handler for menuentry
+ indirectly calls ltk_widget_hide, which messes with the pressed widget */
+ /* FIXME: isn't it redundant to check that state is pressed? */
+ if (release && (old_state & LTK_PRESSED)) {
+ if (window->pressed_widget->vtable->release)
+ window->pressed_widget->vtable->release(window->pressed_widget);
+ }
+ }
+ window->pressed_widget = widget;
+ if (widget) {
+ if (widget->vtable->press)
+ widget->vtable->press(widget);
+ ltk_widget_state old_state = widget->state;
+ widget->state |= LTK_PRESSED;
+ ltk_widget_change_state(widget, old_state);
+ }
+}
+
+void
+ltk_window_handle_event(ltk_window *window, ltk_event *event) {
+ switch (event->type) {
+ case LTK_KEYPRESS_EVENT:
+ ltk_window_key_press_event(LTK_CAST_WIDGET(window), &event->key);
+ break;
+ case LTK_KEYRELEASE_EVENT:
+ ltk_window_key_release_event(LTK_CAST_WIDGET(window), &event->key);
+ break;
+ case LTK_BUTTONPRESS_EVENT:
+ case LTK_2BUTTONPRESS_EVENT:
+ case LTK_3BUTTONPRESS_EVENT:
+ ltk_window_mouse_press_event(LTK_CAST_WIDGET(window), &event->button);
+ break;
+ case LTK_SCROLL_EVENT:
+ ltk_window_mouse_scroll_event(LTK_CAST_WIDGET(window), &event->scroll);
+ break;
+ case LTK_BUTTONRELEASE_EVENT:
+ case LTK_2BUTTONRELEASE_EVENT:
+ case LTK_3BUTTONRELEASE_EVENT:
+ ltk_window_mouse_release_event(LTK_CAST_WIDGET(window), &event->button);
+ break;
+ case LTK_MOTION_EVENT:
+ ltk_window_motion_notify_event(LTK_CAST_WIDGET(window), &event->motion);
+ break;
+ default:
+ ltk_window_other_event(window, event);
+ }
+}
+
+/* x and y are global! */
+static ltk_widget *
+get_widget_under_pointer(ltk_widget *widget, int x, int y, int *local_x_ret, int *local_y_ret) {
+ ltk_point glob = ltk_widget_pos_to_global(widget, 0, 0);
+ ltk_widget *next = NULL;
+ *local_x_ret = x - glob.x;
+ *local_y_ret = y - glob.y;
+ while (widget && widget->vtable->get_child_at_pos) {
+ next = widget->vtable->get_child_at_pos(widget, *local_x_ret, *local_y_ret);
+ if (!next) {
+ break;
+ } else {
+ widget = next;
+ if (next->popup) {
+ *local_x_ret = x - next->lrect.x;
+ *local_y_ret = y - next->lrect.y;
+ } else {
+ *local_x_ret -= next->lrect.x;
+ *local_y_ret -= next->lrect.y;
+ }
+ }
+ }
+ return widget;
+}
+
+static ltk_widget *
+get_hover_popup(ltk_window *window, int x, int y) {
+ for (size_t i = window->popups_num; i-- > 0;) {
+ if (ltk_collide_rect(window->popups[i]->crect, x, y))
+ return window->popups[i];
+ }
+ return NULL;
+}
+
+static int
+is_parent(ltk_widget *parent, ltk_widget *child) {
+ while (child && child != parent) {
+ child = child->parent;
+ }
+ return child != NULL;
+}
+
+/* FIXME: come up with a more elegant way to handle this? */
+/* FIXME: Handle hidden state here instead of in widgets */
+/* FIXME: handle disabled state */
+static int
+prev_child(ltk_window *window) {
+ if (!window->root_widget)
+ return 0;
+ ltk_config *config = ltk_config_get();
+ ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
+ ltk_widget *new, *cur = window->active_widget;
+ int changed = 0;
+ ltk_widget *prevcur = cur;
+ while (1) {
+ if (cur) {
+ while (cur->parent) {
+ new = NULL;
+ if (cur->parent->vtable->prev_child)
+ new = cur->parent->vtable->prev_child(cur->parent, cur);
+ if (new) {
+ cur = new;
+ ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
+ while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags)
+ last_activatable = cur;
+ }
+ if (last_activatable) {
+ cur = last_activatable;
+ changed = 1;
+ break;
+ }
+ } else {
+ cur = cur->parent;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (!changed) {
+ cur = window->root_widget;
+ ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
+ while (cur->vtable->last_child && (new = cur->vtable->last_child(cur))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags)
+ last_activatable = cur;
+ }
+ if (last_activatable)
+ cur = last_activatable;
+ }
+ if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
+ break;
+ prevcur = cur;
+ }
+ /* FIXME: What exactly should be done if no activatable widget exists? */
+ if (cur != window->active_widget) {
+ ltk_window_set_active_widget(window, cur);
+ ensure_active_widget_shown(window);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+next_child(ltk_window *window) {
+ if (!window->root_widget)
+ return 0;
+ ltk_config *config = ltk_config_get();
+ ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
+ ltk_widget *new, *cur = window->active_widget;
+ int changed = 0;
+ ltk_widget *prevcur = cur;
+ while (1) {
+ if (cur) {
+
+ while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ }
+ if (!changed) {
+ while (cur->parent) {
+ new = NULL;
+ if (cur->parent->vtable->next_child)
+ new = cur->parent->vtable->next_child(cur->parent, cur);
+ if (new) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ }
+ if (changed)
+ break;
+ } else {
+ cur = cur->parent;
+ }
+ }
+ }
+ }
+ if (!changed) {
+ cur = window->root_widget;
+ if (!(cur->vtable->flags & act_flags)) {
+ while (cur->vtable->first_child && (new = cur->vtable->first_child(cur))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags)
+ break;
+ }
+ }
+ if (!(cur->vtable->flags & act_flags))
+ cur = window->root_widget;
+ }
+ if (prevcur == cur || (cur && (cur->vtable->flags & act_flags)))
+ break;
+ prevcur = cur;
+ }
+ if (cur != window->active_widget) {
+ ltk_window_set_active_widget(window, cur);
+ ensure_active_widget_shown(window);
+ return 1;
+ }
+ return 0;
+}
+
+/* FIXME: moving up/down/left/right needs to be rethought
+ it generally is a bit weird, and in particular, nearest_child always searches for the child
+ that has the smallest distance to the given rect, so it may not be the child that the user
+ expects when going down (e.g. a vertical box with one widget closer vertically but on the
+ other side horizontally, thus possibly leading to a different widget that is farther away
+ vertically to be chosen instead) - what would be logical here? */
+static ltk_widget *
+nearest_child(ltk_widget *widget, ltk_rect r) {
+ ltk_point local = ltk_global_to_widget_pos(widget, r.x, r.y);
+ ltk_rect rect = {local.x, local.y, r.w, r.h};
+ if (widget->vtable->nearest_child)
+ return widget->vtable->nearest_child(widget, rect);
+ return NULL;
+}
+
+/* FIXME: maybe wrap around in these two functions? */
+static int
+left_top_child(ltk_window *window, int left) {
+ if (!window->root_widget)
+ return 0;
+ ltk_config *config = ltk_config_get();
+ ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
+ ltk_widget *new, *cur = window->active_widget;
+ ltk_rect old_rect = {0, 0, 0, 0};
+ ltk_widget *last_activatable = NULL;
+ if (!cur) {
+ cur = window->root_widget;
+ if (cur->vtable->flags & act_flags)
+ last_activatable = cur;
+ ltk_rect r = {cur->lrect.w, cur->lrect.h, 0, 0};
+ while ((new = nearest_child(cur, r))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags)
+ last_activatable = cur;
+ }
+ }
+ if (last_activatable) {
+ cur = last_activatable;
+ } else if (cur) {
+ ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
+ old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
+ while (cur->parent) {
+ new = NULL;
+ if (left) {
+ if (cur->parent->vtable->nearest_child_left)
+ new = cur->parent->vtable->nearest_child_left(cur->parent, cur);
+ } else {
+ if (cur->parent->vtable->nearest_child_above)
+ new = cur->parent->vtable->nearest_child_above(cur->parent, cur);
+ }
+ if (new) {
+ cur = new;
+ ltk_widget *last_activatable = (cur->vtable->flags & act_flags) ? cur : NULL;
+ while ((new = nearest_child(cur, old_rect))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags)
+ last_activatable = cur;
+ }
+ if (last_activatable) {
+ cur = last_activatable;
+ break;
+ }
+ } else {
+ cur = cur->parent;
+ if (cur->vtable->flags & act_flags) {
+ break;
+ }
+ }
+ }
+ }
+ /* FIXME: What exactly should be done if no activatable widget exists? */
+ if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
+ ltk_window_set_active_widget(window, cur);
+ ensure_active_widget_shown(window);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+right_bottom_child(ltk_window *window, int right) {
+ if (!window->root_widget)
+ return 0;
+ ltk_config *config = ltk_config_get();
+ ltk_widget_flags act_flags = config->general.all_activatable ? LTK_ACTIVATABLE_ALWAYS : LTK_ACTIVATABLE_NORMAL;
+ ltk_widget *new, *cur = window->active_widget;
+ int changed = 0;
+ ltk_rect old_rect = {0, 0, 0, 0};
+ ltk_rect corner = {0, 0, 0, 0};
+ int found_activatable = 0;
+ if (!cur) {
+ cur = window->root_widget;
+ if (!(cur->vtable->flags & act_flags)) {
+ while ((new = nearest_child(cur, (ltk_rect){0, 0, 0, 0}))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ found_activatable = 1;
+ break;
+ }
+ }
+ }
+ }
+ if (!found_activatable) {
+ ltk_point glob = cur->parent ? ltk_widget_pos_to_global(cur->parent, cur->lrect.x, cur->lrect.y) : (ltk_point){cur->lrect.x, cur->lrect.y};
+ corner = (ltk_rect){glob.x, glob.y, 0, 0};
+ old_rect = (ltk_rect){glob.x, glob.y, cur->lrect.w, cur->lrect.h};
+ while ((new = nearest_child(cur, corner))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ }
+ if (!changed) {
+ while (cur->parent) {
+ new = NULL;
+ if (right) {
+ if (cur->parent->vtable->nearest_child_right)
+ new = cur->parent->vtable->nearest_child_right(cur->parent, cur);
+ } else {
+ if (cur->parent->vtable->nearest_child_below)
+ new = cur->parent->vtable->nearest_child_below(cur->parent, cur);
+ }
+ if (new) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ while ((new = nearest_child(cur, old_rect))) {
+ cur = new;
+ if (cur->vtable->flags & act_flags) {
+ changed = 1;
+ break;
+ }
+ }
+ if (changed)
+ break;
+ } else {
+ cur = cur->parent;
+ }
+ }
+ }
+ }
+ if (cur && cur != window->active_widget && (cur->vtable->flags & act_flags)) {
+ ltk_window_set_active_widget(window, cur);
+ ensure_active_widget_shown(window);
+ return 1;
+ }
+ return 0;
+}
+
+/* FIXME: maybe just set this when active widget changes */
+/* -> but would also need to change it when widgets are created/destroyed or parents change */
+static void
+gen_widget_stack(ltk_widget *bottom) {
+ widget_stack_len = 0;
+ while (bottom) {
+ if (widget_stack_len + 1 > widget_stack_alloc) {
+ widget_stack_alloc = ideal_array_size(widget_stack_alloc, widget_stack_len + 1);
+ widget_stack = ltk_reallocarray(widget_stack, widget_stack_alloc, sizeof(ltk_widget *));
+ }
+ widget_stack[widget_stack_len++] = bottom;
+ bottom = bottom->parent;
+ }
+}
+
+/* FIXME: The focus behavior needs to be rethought. It's currently hard-coded in the vtable for each
+ widget type, but what if the program using ltk wants to catch keyboard events even if the widget
+ doesn't do that by default? */
+static int
+cb_focus_active(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ if (window->active_widget && !(window->active_widget->state & LTK_FOCUSED)) {
+ /* FIXME: maybe also set widgets above in hierarchy? */
+ ltk_widget_state old_state = window->active_widget->state;
+ window->active_widget->state |= LTK_FOCUSED;
+ ltk_widget_change_state(window->active_widget, old_state);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_unfocus_active(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ if (window->active_widget && (window->active_widget->state & LTK_FOCUSED) && (window->active_widget->vtable->flags & LTK_NEEDS_KEYBOARD)) {
+ ltk_widget_state old_state = window->active_widget->state;
+ window->active_widget->state &= ~LTK_FOCUSED;
+ ltk_widget_change_state(window->active_widget, old_state);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_move_prev(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return prev_child(window);
+}
+
+static int
+cb_move_next(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return next_child(window);
+}
+
+static int
+cb_move_left(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return left_top_child(window, 1);
+}
+
+static int
+cb_move_right(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return right_bottom_child(window, 1);
+}
+
+static int
+cb_move_up(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return left_top_child(window, 0);
+}
+
+static int
+cb_move_down(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ return right_bottom_child(window, 0);
+}
+
+static int
+cb_set_pressed(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ if (window->active_widget && (window->active_widget->state & LTK_FOCUSED)) {
+ /* FIXME: only set pressed if needs keyboard? */
+ ltk_window_set_pressed_widget(window, window->active_widget, 0);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_unset_pressed(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ if (window->pressed_widget) {
+ ltk_window_set_pressed_widget(window, NULL, 1);
+ return 1;
+ }
+ return 0;
+}
+
+static int
+cb_remove_popups(ltk_window *window, ltk_key_event *event, int handled) {
+ (void)event;
+ (void)handled;
+ if (window->popups_num > 0) {
+ ltk_window_unregister_all_popups(window);
+ return 1;
+ }
+ return 0;
+}
diff --git a/src/window.h b/src/window.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2020-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_WINDOW_H
+#define LTK_WINDOW_H
+
+#include <stddef.h>
+#include "color.h"
+#include "config.h"
+#include "event.h"
+#include "graphics.h"
+#include "rect.h"
+#include "surface_cache.h"
+#include "widget.h"
+
+#define LTK_WINDOW_SIGNAL_CLOSE -1
+#define LTK_WINDOW_SIGNAL_INVALID -2
+
+typedef struct {
+ int border_width;
+ int font_size;
+ char *font;
+ ltk_color *fg;
+ ltk_color *bg;
+} ltk_window_theme;
+
+typedef struct ltk_window {
+ ltk_widget widget;
+ ltk_renderwindow *renderwindow;
+ ltk_surface_cache *surface_cache;
+ ltk_surface *surface;
+ /* FIXME: check if these are reset properly if widget is deleted */
+ ltk_widget *root_widget;
+ ltk_widget *hover_widget;
+ ltk_widget *active_widget;
+ ltk_widget *pressed_widget;
+
+ ltk_rect rect;
+ ltk_window_theme *theme;
+ ltk_rect dirty_rect;
+ /* FIXME: generic array */
+ ltk_widget **popups;
+ size_t popups_num;
+ size_t popups_alloc;
+ /* This is a hack so ltk_window_unregister_all_popups can
+ call hide for all popup widgets even if the hide function
+ already calls ltk_window_unregister_popup */
+ char popups_locked;
+} ltk_window;
+
+int ltk_window_fill_theme_defaults(ltk_renderdata *data);
+int ltk_window_ini_handler(ltk_renderdata *data, const char *prop, const char *value);
+void ltk_window_uninitialize_theme(ltk_renderdata *data);
+ltk_window_theme *ltk_window_get_theme(void);
+
+/* FIXME: should be private to ltk */
+ltk_window *ltk_window_create_intern(ltk_renderdata *data, const char *title, int x, int y, unsigned int w, unsigned int h);
+void ltk_window_destroy_intern(ltk_window *window);
+
+void ltk_window_handle_event(ltk_window *window, ltk_event *event);
+void ltk_window_fake_motion_event(ltk_window *window, int x, int y);
+
+void ltk_window_invalidate_rect(ltk_window *window, ltk_rect rect);
+void ltk_window_invalidate_widget_rect(ltk_window *window, ltk_widget *widget);
+
+void ltk_window_set_root_widget(ltk_window *window, ltk_widget *widget);
+void ltk_window_set_hover_widget(ltk_window *window, ltk_widget *widget, ltk_motion_event *event);
+void ltk_window_set_active_widget(ltk_window *window, ltk_widget *widget);
+void ltk_window_set_pressed_widget(ltk_window *window, ltk_widget *widget, int release);
+
+void ltk_window_register_popup(ltk_window *window, ltk_widget *popup);
+void ltk_window_unregister_popup(ltk_window *window, ltk_widget *popup);
+void ltk_window_unregister_all_popups(ltk_window *window);
+
+/* FIXME: these should be private to ltk */
+/* FIXME: document that pointers inside binding are taken over! */
+int ltk_window_register_keypress(const char *func_name, size_t func_len, ltk_keypress_binding b);
+int ltk_window_register_keyrelease(const char *func_name, size_t func_len, ltk_keyrelease_binding b);
+void ltk_window_cleanup(void);
+
+#endif /* LTK_WINDOW_H */
diff --git a/src/xlib_shared.h b/src/xlib_shared.h
@@ -1,26 +0,0 @@
-#ifndef XLIB_SHARED_H
-#define XLIB_SHARED_H
-
-#include <X11/Xlib.h>
-#include <X11/extensions/Xdbe.h>
-
-struct ltk_renderdata {
- Display *dpy;
- Visual *vis;
- Colormap cm;
- GC gc;
- int screen;
- Atom wm_delete_msg;
- Window xwindow;
- XdbeBackBuffer back_buf;
- Drawable drawable;
- int depth;
- XIM xim;
- XIC xic;
- XPoint spot;
- XVaNestedList spotlist;
- int xkb_event_type;
- int xkb_supported;
-};
-
-#endif /* XLIB_SHARED_H */
diff --git a/test.gui b/test.gui
@@ -1,27 +0,0 @@
-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
-box box1 create vertical
-grid grd1 add box1 0 0 1 1 lrtb
-button btn1 create "I'm a button!"
-button btn2 create "I'm also a button!"
-button btn3 create "I'm another boring button."
-box box1 add btn1 lr
-box box1 add btn2 r
-box box1 add btn3
-box box2 create vertical
-grid grd1 add box2 1 0 1 1 lrtb
-button btn4 create "2 I'm a button!"
-button btn5 create "2 I'm also a button!"
-button btn6 create "2 I'm another boring button."
-box box2 add btn4 lr
-box box2 add btn5 r
-box box2 add btn6
-button btn7 create "Button 7"
-button btn8 create "Button 8"
-grid grd1 add btn7 0 1 1 1 lrtb
-grid grd1 add btn8 1 1 1 1 lr
-mask-add btn1 button press
diff --git a/test.sh b/test.sh
@@ -1,22 +0,0 @@
-#!/bin/sh
-
-# This is very hacky.
-
-export LTKDIR="`pwd`/.ltk"
-ltk_id=`./src/ltkd -t "Cool Window"`
-if [ $? -ne 0 ]; then
- echo "Unable to start ltkd." >&2
- exit 1
-fi
-
-cat test.gui | ./src/ltkc $ltk_id | while read cmd
-do
- case "$cmd" in
- *"event btn1 button press")
- echo "quit"
- ;;
- *)
- printf "%s\n" "$cmd" >&2
- ;;
- esac
-done | ./src/ltkc $ltk_id
diff --git a/test2.gui b/test2.gui
@@ -1,45 +0,0 @@
-grid grd1 create 2 1
-grid grd1 set-row-weight 1 1
-grid grd1 set-column-weight 0 1
-set-root-widget grd1
-menu menu1 create
-menuentry entry1 create "Entry 1"
-menuentry entry2 create "Entry 2"
-menuentry entry3 create "Entry 3"
-menuentry entry4 create "Entry 4"
-menuentry entry5 create "Entry 5"
-menuentry entry6 create "Entry 6"
-menuentry entry7 create "Entry 7"
-menuentry entry8 create "Entry 8"
-menuentry entry9 create "Entry 9"
-menuentry entry10 create "Entry 10"
-menuentry entry11 create "Entry 11"
-menuentry entry12 create "Entry 12"
-menuentry entry13 create "Entry 13"
-menuentry entry14 create "Entry 14"
-menuentry entry15 create "Entry 15"
-menuentry entry16 create "Entry 16"
-menu menu1 add-entry entry1
-menu menu1 add-entry entry2
-menu menu1 add-entry entry12
-submenu submenu1 create
-menu submenu1 add-entry entry3
-menu submenu1 add-entry entry4
-menu submenu1 add-entry entry5
-menu submenu1 add-entry entry6
-menu submenu1 add-entry entry7
-menu submenu1 add-entry entry8
-menu submenu1 add-entry entry9
-menu submenu1 add-entry entry10
-menu submenu1 add-entry entry11
-menuentry entry12 attach-submenu submenu1
-submenu submenu2 create
-menu submenu2 add-entry entry13
-menu submenu2 add-entry entry15
-menu submenu1 add-entry entry14
-menuentry entry14 attach-submenu submenu2
-submenu submenu3 create
-menu submenu3 add-entry entry16
-menuentry entry15 attach-submenu submenu3
-grid grd1 add menu1 0 0 1 1 lr
-mask-add entry10 menuentry press
diff --git a/test2.sh b/test2.sh
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-export LTKDIR="`pwd`/.ltk"
-ltk_id=`./src/ltkd -t "Cool Window"`
-if [ $? -ne 0 ]; then
- echo "Unable to start ltkd." >&2
- exit 1
-fi
-
-cat test2.gui | ./src/ltkc $ltk_id
diff --git a/test3.gui b/test3.gui
@@ -1,16 +0,0 @@
-grid grd1 create 4 1
-grid grd1 set-row-weight 0 1
-grid grd1 set-row-weight 1 1
-grid grd1 set-row-weight 2 1
-grid grd1 set-row-weight 3 1
-grid grd1 set-column-weight 0 1
-set-root-widget grd1
-button btn1 create "I'm a button!"
-button btn2 create "I'm also a button!"
-button btn3 create "I'm another boring button."
-grid grd1 add btn1 0 0 1 1
-grid grd1 add btn2 1 0 1 1
-grid grd1 add btn3 2 0 1 1
-mask-add btn1 button press
-entry entry1 create "Hi"
-grid grd1 add entry1 3 0 1 1 w
diff --git a/test3.sh b/test3.sh
@@ -1,20 +0,0 @@
-#!/bin/sh
-
-export LTKDIR="`pwd`/.ltk"
-ltk_id=`./src/ltkd -t "Cool Window"`
-#if [ $? -ne 0 ]; then
-# echo "Unable to start ltkd." >&2
-# exit 1
-#fi
-
-cat test3.gui | ./src/ltkc $ltk_id | while read cmd
-do
- case "$cmd" in
- *"event btn1 button press")
- echo "quit"
- ;;
- *)
- printf "client1: %s\n" "$cmd" >&2
- ;;
- esac
-done | ./src/ltkc $ltk_id
diff --git a/testbox.sh b/testbox.sh
@@ -1,29 +0,0 @@
-#!/bin/sh
-
-export LTKDIR="`pwd`/.ltk"
-ltk_id=`./src/ltkd -t "Cool Window"`
-if [ $? -ne 0 ]; then
- echo "Unable to start ltkd." >&2
- exit 1
-fi
-
-cmds="box box1 create vertical\nset-root-widget box1\nlabel lblbla create \"Hi\"\nbox box1 add lblbla w\nbutton exit_btn create \"Exit\"\nmask-add exit_btn button press\nbox box1 add exit_btn
-$(curl -s gopher://lumidify.org | awk -F'\t' '
-BEGIN {btn = 0; lbl = 0;}
-/^i/ { printf "label lbl%s create \"%s\"\nbox box1 add lbl%s w\n", lbl, substr($1, 2), lbl; lbl++ }
-/^[1gI]/ { printf "button btn%s create \"%s\"\nbox box1 add btn%s w\n", btn, substr($1, 2), btn; btn++ }')
-mask-add btn0 button press"
-echo "$cmds" | ./src/ltkc $ltk_id | while read cmd
-do
- case "$cmd" in
- *"event exit_btn button press")
- echo "quit"
- ;;
- *"event btn0 button press")
- echo "button bla create \"safhaskfldshk\"\nbox box1 add bla w"
- ;;
- *)
- printf "%s\n" "$cmd" >&2
- ;;
- esac
-done | ./src/ltkc $ltk_id > /dev/null
diff --git a/testimg.sh b/testimg.sh
@@ -1,13 +0,0 @@
-#!/bin/sh
-
-export LTKDIR="`pwd`/.ltk"
-ltk_id=`./src/ltkd -t "Cool Window"`
-#if [ $? -ne 0 ]; then
-# echo "Unable to start ltkd." >&2
-# exit 1
-#fi
-
-{ printf "grid grd1 create 2 1\ngrid grd1 set-row-weight 0 1\ngrid grd1 set-row-weight 1 1\ngrid grd1 set-column-weight 0 1\nset-root-widget grd1\nimage img1 create test.png \""; ./src/ltkc_img < ~/test.png; printf "\"\ngrid grd1 add img1 1 0 1 1 lrp\n"; } |./src/ltkc $ltk_id | while read cmd
-do
- printf "%s\n" "$cmd" >&2
-done | ./src/ltkc $ltk_id