ltk

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

commit 43bb385257c126c200662ed207f27a7a285f113e
parent d3c2a1bd950d2f5c11ca88da7f39bdef0a6813a0
Author: lumidify <nobody@lumidify.org>
Date:   Fri, 15 Mar 2024 22:00:25 +0100

Remove socket functionality for now

Diffstat:
M.gitignore | 1+
D.ltk/ltk.cfg | 80-------------------------------------------------------------------------------
MLICENSE | 4++--
MMakefile | 55+++++++++++++++++++++++--------------------------------
MREADME.md | 12++----------
MTODO | 2++
R.ltk/.gitignore -> config.example/.gitignore | 0
Aconfig.example/ltk.cfg | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
R.ltk/theme.ini -> config.example/theme.ini | 0
Aexamples/.gitignore | 2++
Aexamples/test.c | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aexamples/test.jpg | 0
Dsocket_format.txt | 106-------------------------------------------------------------------------------
Msrc/.gitignore | 4----
Msrc/array.h | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Msrc/box.c | 311++++++++++++++++++++++++++-----------------------------------------------------
Msrc/box.h | 22+++++++++++++---------
Msrc/button.c | 180++++++++++++++++++++++++++++---------------------------------------------------
Msrc/button.h | 20+++++++++++---------
Msrc/clipboard.h | 16++++++++++++++++
Msrc/clipboard_xlib.c | 35+++++++++++++++++++++++++----------
Msrc/clipboard_xlib.h | 17++++++++++++++++-
Dsrc/cmd.c | 160-------------------------------------------------------------------------------
Dsrc/cmd.h | 125-------------------------------------------------------------------------------
Msrc/color.h | 28+++++++---------------------
Msrc/color_xlib.c | 42++++++++++++++++++++++++++----------------
Dsrc/compat.h | 9---------
Msrc/config.c | 29++++++++++++++++++++++-------
Msrc/config.h | 16++++++++++++++++
Msrc/entry.c | 230+++++++++++++++++++++++++++++++------------------------------------------------
Msrc/entry.h | 22++++++++++++----------
Dsrc/err.c | 30------------------------------
Dsrc/err.h | 33---------------------------------
Msrc/event.h | 33+++++++++++++++++++++++++++++++--
Msrc/event_xlib.c | 93++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Msrc/eventdefs.h | 16++++++++++++++++
Msrc/graphics.h | 43++++++++++++++++---------------------------
Msrc/graphics_xlib.c | 277++++++++++++++++++++++++++++++++++++++++++-------------------------------------
Asrc/graphics_xlib.h | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/grid.c | 326++++++++++++++++++++-----------------------------------------------------------
Msrc/grid.h | 19++++++++++++++-----
Dsrc/image.c | 112-------------------------------------------------------------------------------
Msrc/image.h | 6+++---
Asrc/image_imlib.c | 123+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/image_widget.c | 94++++++++++++++++---------------------------------------------------------------
Msrc/image_widget.h | 9+++++----
Msrc/keys.h | 24+++++++++++++++++++++---
Dsrc/khash.h | 627-------------------------------------------------------------------------------
Msrc/label.c | 105++++++++++++++++++-------------------------------------------------------------
Msrc/label.h | 19++++++++++---------
Asrc/ltk.c | 672+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/ltk.h | 122++++++++++++++++---------------------------------------------------------------
Dsrc/ltkc.c | 268-------------------------------------------------------------------------------
Dsrc/ltkc_img.c | 26--------------------------
Dsrc/ltkd.c | 1914-------------------------------------------------------------------------------
Msrc/macros.h | 41+++++++++++++++++++++++++++++++++++++----
Msrc/memory.c | 66+++++++++++++++++++++++++++++++++++-------------------------------
Msrc/memory.h | 4+++-
Msrc/menu.c | 658+++++++++++++++++++++++++------------------------------------------------------
Msrc/menu.h | 56++++++++++++++++++++++++++++++++++++++------------------
Dsrc/proto_types.h | 66------------------------------------------------------------------
Msrc/rect.c | 16+++++++++++++++-
Msrc/rect.h | 3++-
Msrc/scrollbar.c | 90++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/scrollbar.h | 26++++++++++++++------------
Msrc/surface_cache.c | 87+++++++++++++++++++++++++++++++++++++------------------------------------------
Msrc/surface_cache.h | 32+++++++++++++++++++++++---------
Msrc/text.h | 6++++--
Msrc/text_pango.c | 21++++++++++-----------
Msrc/text_stb.c | 1+
Msrc/theme.c | 32+++++++++++++++++++++++++-------
Msrc/theme.h | 35++++++++++++++++++++++++++---------
Msrc/txtbuf.c | 18++++++++++++++++--
Msrc/txtbuf.h | 18+++++++++++++++++-
Msrc/util.c | 36+++++++++++++++++++++++++-----------
Msrc/util.h | 20+++++++++++---------
Msrc/widget.c | 1273++++++-------------------------------------------------------------------------
Msrc/widget.h | 234++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Asrc/window.c | 1270+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/window.h | 94+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dsrc/xlib_shared.h | 26--------------------------
Dtest.gui | 27---------------------------
Dtest.sh | 22----------------------
Dtest2.gui | 45---------------------------------------------
Dtest2.sh | 10----------
Dtest3.gui | 16----------------
Dtest3.sh | 20--------------------
Dtestbox.sh | 29-----------------------------
Dtestimg.sh | 13-------------
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 = &ltk_box_destroy, .resize = &ltk_recalculate_box, .child_size_change = &ltk_box_child_size_change, - .remove_child = &ltk_box_remove, + .remove_child = &ltk_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 = &ltk_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, &ltk_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, + &ltk_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", &ltk_box_cmd_add, 0}, - {"create", &ltk_box_cmd_create, 1}, - {"remove", &ltk_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", &ltk_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", &ltk_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 = &ltk_grid_child_size_change, - .remove_child = &ltk_grid_ungrid, + .remove_child = &ltk_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 = &ltk_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", &ltk_grid_cmd_add, 0}, - {"create", &ltk_grid_cmd_create, 1}, - {"remove", &ltk_grid_cmd_ungrid, 0}, - {"set-column-weight", &ltk_grid_cmd_set_column_weight, 0}, - {"set-row-weight", &ltk_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", &ltk_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 = &ltk_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", &ltk_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 = &ltk_button_ini_handler, + .fill_theme_defaults = &ltk_button_fill_theme_defaults, + .uninitialize_theme = &ltk_button_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + }, + { + .name = "entry", + .ini_handler = &ltk_entry_ini_handler, + .fill_theme_defaults = &ltk_entry_fill_theme_defaults, + .uninitialize_theme = &ltk_entry_uninitialize_theme, + .register_keypress = &ltk_entry_register_keypress, + .register_keyrelease = &ltk_entry_register_keyrelease, + .cleanup = &ltk_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 = &ltk_label_ini_handler, + .fill_theme_defaults = &ltk_label_fill_theme_defaults, + .uninitialize_theme = &ltk_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 = &ltk_menu_ini_handler, + .fill_theme_defaults = &ltk_menu_fill_theme_defaults, + .uninitialize_theme = &ltk_menu_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + }, + { + .name = "menuentry", + .ini_handler = &ltk_menuentry_ini_handler, + .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults, + .uninitialize_theme = &ltk_menuentry_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + }, + { + .name = "submenu", + .ini_handler = &ltk_submenu_ini_handler, + .fill_theme_defaults = &ltk_submenu_fill_theme_defaults, + .uninitialize_theme = &ltk_submenu_uninitialize_theme, + .register_keypress = NULL, + .register_keyrelease = NULL, + .cleanup = NULL, + }, + { + .name = "submenuentry", + .ini_handler = &ltk_submenuentry_ini_handler, + .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults, + .uninitialize_theme = &ltk_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 = &ltk_scrollbar_ini_handler, + .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults, + .uninitialize_theme = &ltk_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 = &ltk_window_ini_handler, + .fill_theme_defaults = &ltk_window_fill_theme_defaults, + .uninitialize_theme = &ltk_window_uninitialize_theme, + .register_keypress = &ltk_window_register_keypress, + .register_keyrelease = &ltk_window_register_keyrelease, + .cleanup = &ltk_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 = &ltk_box_cmd - }, - { - .name = "button", - .ini_handler = &ltk_button_ini_handler, - .fill_theme_defaults = &ltk_button_fill_theme_defaults, - .uninitialize_theme = &ltk_button_uninitialize_theme, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_button_cmd - }, - { - .name = "entry", - .ini_handler = &ltk_entry_ini_handler, - .fill_theme_defaults = &ltk_entry_fill_theme_defaults, - .uninitialize_theme = &ltk_entry_uninitialize_theme, - .register_keypress = &ltk_entry_register_keypress, - .register_keyrelease = &ltk_entry_register_keyrelease, - .cleanup = &ltk_entry_cleanup, - .cmd = &ltk_entry_cmd - }, - { - .name = "grid", - .ini_handler = NULL, - .fill_theme_defaults = NULL, - .uninitialize_theme = NULL, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_grid_cmd - }, - { - .name = "label", - .ini_handler = &ltk_label_ini_handler, - .fill_theme_defaults = &ltk_label_fill_theme_defaults, - .uninitialize_theme = &ltk_label_uninitialize_theme, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_label_cmd - }, - { - .name = "image", - .ini_handler = NULL, - .fill_theme_defaults = NULL, - .uninitialize_theme = NULL, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_image_widget_cmd - }, - { - .name = "menu", - .ini_handler = &ltk_menu_ini_handler, - .fill_theme_defaults = &ltk_menu_fill_theme_defaults, - .uninitialize_theme = &ltk_menu_uninitialize_theme, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_menu_cmd - }, - { - .name = "menuentry", - .ini_handler = &ltk_menuentry_ini_handler, - .fill_theme_defaults = &ltk_menuentry_fill_theme_defaults, - .uninitialize_theme = &ltk_menuentry_uninitialize_theme, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_menuentry_cmd - }, - { - .name = "submenu", - .ini_handler = &ltk_submenu_ini_handler, - .fill_theme_defaults = &ltk_submenu_fill_theme_defaults, - .uninitialize_theme = &ltk_submenu_uninitialize_theme, - .register_keypress = NULL, - .register_keyrelease = NULL, - .cleanup = NULL, - .cmd = &ltk_menu_cmd - }, - { - .name = "submenuentry", - .ini_handler = &ltk_submenuentry_ini_handler, - .fill_theme_defaults = &ltk_submenuentry_fill_theme_defaults, - .uninitialize_theme = &ltk_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 = &ltk_scrollbar_ini_handler, - .fill_theme_defaults = &ltk_scrollbar_fill_theme_defaults, - .uninitialize_theme = &ltk_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 = &ltk_widget_register_keypress, - .register_keyrelease = &ltk_widget_register_keyrelease, - .cleanup = &ltk_widget_cleanup, - .cmd = NULL - }, - { - /* Handler for window theme. */ - .name = "window", - .ini_handler = &ltk_window_ini_handler, - .fill_theme_defaults = &ltk_window_fill_theme_defaults, - .uninitialize_theme = &ltk_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 = &ltk_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 = &ltk_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 = &ltk_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, &ltk_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, &ltk_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", &ltk_menu_cmd_add_entry, 0}, - {"create", &ltk_menu_cmd_create, 1}, - {"insert-entry", &ltk_menu_cmd_insert_entry, 0}, - {"remove-all-entries", &ltk_menu_cmd_remove_all_entries, 0}, - {"remove-entry-index", &ltk_menu_cmd_remove_entry_index, 0}, - {"remove-entry-id", &ltk_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", &ltk_menuentry_cmd_attach_submenu, 0}, - {"create", &ltk_menuentry_cmd_create, 1}, - {"detach-submenu", &ltk_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 = &ltk_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 = &ltk_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 = &ltk_window_key_press_event, + .key_release = &ltk_window_key_release_event, + .mouse_press = &ltk_window_mouse_press_event, + .mouse_release = &ltk_window_mouse_release_event, + .release = NULL, + .motion_notify = &ltk_window_motion_notify_event, + .mouse_leave = NULL, + .mouse_enter = NULL, + .change_state = NULL, + .get_child_at_pos = NULL, + .resize = NULL, + .hide = NULL, + .draw = &ltk_window_redraw, + .destroy = &ltk_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 = &ltk_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 = &ltk_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