aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHanspeter Portner <dev@open-music-kontrollers.ch>2020-01-15 20:47:38 +0100
committerHanspeter Portner <dev@open-music-kontrollers.ch>2020-01-15 20:47:38 +0100
commit57d092e5b58afdf81fb6483301a07370f6dd9059 (patch)
tree4ce46cb989815edcdf431b1a391e9b80a957e491
parente25100c56069d8eb36b98161d93ba465b8f0597b (diff)
downloadd2tk.lv2-57d092e5b58afdf81fb6483301a07370f6dd9059.tar.xz
Squashed 'subprojects/d2tk/' changes from 945b2dc2..6dc8b138
6dc8b138 meson: add define for libinput >= 1.15.0. e7d28dd3 backend: load images relative to bundle_path. b145662d example: only render keyboard with libevdev. 21dd749c example: correctly use D2TK_EVDEV macro. 5e4c22c7 meson: improve handling of libevdev. be052e7b base: capture terminal bell. 4d4595b9 base: fix killing of child pty process. 1d2997b2 pugl: force full refresh upon FOCUS/ENTER/LEAVE. 30de00ce Merge commit '25a3b7dafcd024e45505db4015fa9bae6cde94e3' 25a3b7da Squashed 'pugl/' changes from a800033c..630aed14 a5f0519c base: add reinit argument to d2tk_base_pty. 5604f35a base: implement cursor focus. 18aecd49 example: add 'clear' cmd to internal pty. 4c869a53 example: prototype internal linenoise pty. 14bed7f0 Merge commit '9dad9e3df37cfbfc28c3ccb0ed4d097ff0476913' as 'linenoise' 9dad9e3d Squashed 'linenoise/' content from commit 4a961c0 51abaaff example: prototype internal pseudoterminal. b6a498f0 pty: extend invocation with clone callback func. bd64c946 base: prototype synchronous atom probe. 79e9620b base: prototype deinit mechanisms for atoms. 37b0e88b base: handle vterm key modifiers. 6837027f base: split pty into behave an draw functions. 05bc0c93 base: set again in vpty write. e12084e7 base: make pty rows/cols dependent on font height. 9bec5f5e example: fix check for D2TK_PTY. 66d82ed8 base: use config.h to store D2TK_PTY macro. a7bb3846 base: prototype pty widget. 6fd56871 base: put atom body structures into source files. 9e20fd5b base: template pty widget skeleton. c969fb49 test: extend d2tk_base_meter tests. dc1a11ac test: extend d2tk_base_link tests. 2309b55b hash: use new chained invocation as by author. 7af1afc8 core: manually unroll _d2tk_com_equal function. 538478ac core: speed up D2TK_COM_FOREACH. f3de3d62 core: do some inlining. e02208ad hash: fix typos. 8000210f base: make max/num both uint32_t vec [2]. 9548e431 base: make atom_body to be dynamically allocated. 196b4448 test: add more test for link. 1bdc9507 test: add more tests for layout. 2d6cce25 test: add more label unit tests. 6c6cc27f test: add more tests for combo. eda1dfda base: include sys/types.h. c75ca317 meson: use system glew if avaiable. 7131cbfd base: put widget code into separate files. 25bea02b meson: rename mum.c -> hash.c. git-subtree-dir: subprojects/d2tk git-subtree-split: 6dc8b138837f6217d898c2d3a4be1b341cb24889
-rw-r--r--.gitlab-ci.yml8
-rw-r--r--VERSION2
-rw-r--r--d2tk/base.h39
-rw-r--r--d2tk/config.h.in3
-rw-r--r--d2tk/core.h3
-rw-r--r--d2tk/hash.h6
-rw-r--r--example/example.c151
-rw-r--r--linenoise/.gitignore3
-rw-r--r--linenoise/LICENSE25
-rw-r--r--linenoise/Makefile7
-rw-r--r--linenoise/README.markdown229
-rw-r--r--linenoise/example.c74
-rw-r--r--linenoise/linenoise.c1201
-rw-r--r--linenoise/linenoise.h73
-rw-r--r--meson.build111
-rw-r--r--pugl/pugl/detail/x11.c24
-rw-r--r--src/backend_cairo.c8
-rw-r--r--src/backend_nanovg.c6
-rw-r--r--src/base.c3445
-rw-r--r--src/base_bitmap.c45
-rw-r--r--src/base_button.c207
-rw-r--r--src/base_combo.c251
-rw-r--r--src/base_cursor.c66
-rw-r--r--src/base_custom.c41
-rw-r--r--src/base_dial.c499
-rw-r--r--src/base_flowmatrix.c806
-rw-r--r--src/base_frame.c118
-rw-r--r--src/base_image.c53
-rw-r--r--src/base_internal.h128
-rw-r--r--src/base_label.c80
-rw-r--r--src/base_layout.c138
-rw-r--r--src/base_link.c122
-rw-r--r--src/base_meter.c233
-rw-r--r--src/base_pane.c246
-rw-r--r--src/base_pty.c910
-rw-r--r--src/base_scrollbar.c331
-rw-r--r--src/base_table.c123
-rw-r--r--src/base_textfield.c212
-rw-r--r--src/core.c170
-rw-r--r--src/core_internal.h29
-rw-r--r--src/frontend_fbdev.c3
-rw-r--r--src/frontend_pugl.c16
-rw-r--r--src/hash.c (renamed from src/mum.c)31
-rw-r--r--test/base.c400
-rw-r--r--ttf/FiraCode-Bold.ttfbin0 -> 315784 bytes
-rw-r--r--ttf/FiraCode-Light.ttfbin0 -> 276684 bytes
-rw-r--r--ttf/FiraCode-Medium.ttfbin0 -> 286232 bytes
-rw-r--r--ttf/FiraCode-Regular.ttfbin0 -> 290360 bytes
48 files changed, 7186 insertions, 3490 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 04fa3fe..42bea53 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -68,22 +68,22 @@ stages:
# building in docker
x86_64-linux-gnu:
before_script:
- - apt-get install -y libglu1-mesa-dev libevdev-dev
+ - apt-get install -y libglu1-mesa-dev libevdev-dev libvterm-dev
<<: *universal_linux_definition
i686-linux-gnu:
before_script:
- - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386
+ - apt-get install -y libglu1-mesa-dev:i386 libevdev-dev:i386 libvterm-dev:i386
<<: *universal_linux_definition
arm-linux-gnueabihf:
before_script:
- - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf
+ - apt-get install -y libglu1-mesa-dev:armhf libevdev-dev:armhf libvterm-dev:armhf
<<: *arm_linux_definition
aarch64-linux-gnu:
before_script:
- - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64
+ - apt-get install -y libglu1-mesa-dev:arm64 libevdev-dev:arm64 libvterm-dev:arm64
<<: *arm_linux_definition
x86_64-w64-mingw32:
diff --git a/VERSION b/VERSION
index 775a595..bfac85b 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.1.859
+0.1.951
diff --git a/d2tk/base.h b/d2tk/base.h
index e737962..38108e7 100644
--- a/d2tk/base.h
+++ b/d2tk/base.h
@@ -19,11 +19,15 @@
#define _D2TK_BASE_H
#include <stdlib.h>
+#include <sys/types.h>
+#include <stdio.h>
#include <d2tk/core.h>
#include <utf8.h/utf8.h>
+#include "config.h"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -127,7 +131,9 @@ typedef enum _d2tk_state_t {
D2TK_STATE_MOTION = (1 << 11),
D2TK_STATE_CHANGED = (1 << 12),
D2TK_STATE_ENTER = (1 << 13),
- D2TK_STATE_OVER = (1 << 14)
+ D2TK_STATE_OVER = (1 << 14),
+ D2TK_STATE_CLOSE = (1 << 15),
+ D2TK_STATE_BELL = (1 << 16)
} d2tk_state_t;
typedef enum _d2tk_flag_t {
@@ -235,7 +241,7 @@ d2tk_layout_get_rect(d2tk_layout_t *lay);
D2TK_API d2tk_scrollbar_t *
d2tk_scrollbar_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
- d2tk_flag_t flags, int32_t hmax, int32_t vmax, int32_t hnum, int32_t vnum,
+ d2tk_flag_t flags, const uint32_t max [2], const uint32_t num [2],
d2tk_scrollbar_t *scrollbar);
D2TK_API bool
@@ -253,11 +259,9 @@ d2tk_scrollbar_get_offset_x(d2tk_scrollbar_t *scrollbar);
D2TK_API const d2tk_rect_t *
d2tk_scrollbar_get_rect(d2tk_scrollbar_t *scrollbar);
-#define D2TK_BASE_SCROLLBAR(BASE, RECT, ID, FLAGS, HMAX, VMAX, HNUM, VNUM, \
- SCROLLBAR) \
+#define D2TK_BASE_SCROLLBAR(BASE, RECT, ID, FLAGS, MAX, NUM, SCROLLBAR) \
for(d2tk_scrollbar_t *(SCROLLBAR) = d2tk_scrollbar_begin((BASE), (RECT), \
- (ID), (FLAGS), (HMAX), (VMAX), (HNUM), (VNUM), \
- alloca(d2tk_scrollbar_sz)); \
+ (ID), (FLAGS), (MAX), (NUM), alloca(d2tk_scrollbar_sz)); \
d2tk_scrollbar_not_end((SCROLLBAR)); \
(SCROLLBAR) = d2tk_scrollbar_next((BASE), (SCROLLBAR)))
@@ -350,6 +354,12 @@ D2TK_API bool
d2tk_state_is_over(d2tk_state_t state);
D2TK_API bool
+d2tk_state_is_close(d2tk_state_t state);
+
+D2TK_API bool
+d2tk_state_is_bell(d2tk_state_t state);
+
+D2TK_API bool
d2tk_base_is_hit(d2tk_base_t *base, const d2tk_rect_t *rect);
D2TK_API void
@@ -464,6 +474,17 @@ d2tk_base_link(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len, const char *lbl
#define d2tk_base_link_is_changed(...) \
d2tk_state_is_changed(d2tk_base_link(__VA_ARGS__))
+#if D2TK_PTY
+typedef void (*d2tk_clone_t)(FILE *stderr, void *data);
+
+D2TK_API d2tk_state_t
+d2tk_base_pty(d2tk_base_t *base, d2tk_id_t id, d2tk_clone_t clone, void *data,
+ d2tk_coord_t height, const d2tk_rect_t *rect, bool reinit);
+
+#define d2tk_base_pty_is_changed(...) \
+ d2tk_state_is_changed(d2tk_base_pty(__VA_ARGS__))
+#endif
+
D2TK_API d2tk_state_t
d2tk_base_dial_bool(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
bool *value);
@@ -609,6 +630,9 @@ D2TK_API void
d2tk_base_post(d2tk_base_t *base);
D2TK_API void
+d2tk_base_probe(d2tk_base_t *base);
+
+D2TK_API bool
d2tk_base_set_again(d2tk_base_t *base);
D2TK_API void
@@ -654,6 +678,9 @@ d2tk_base_set_dimensions(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h);
D2TK_API void
d2tk_base_get_dimensions(d2tk_base_t *base, d2tk_coord_t *w, d2tk_coord_t *h);
+D2TK_API void
+d2tk_base_set_full_refresh(d2tk_base_t *base);
+
#ifdef __cplusplus
}
#endif
diff --git a/d2tk/config.h.in b/d2tk/config.h.in
new file mode 100644
index 0000000..0488566
--- /dev/null
+++ b/d2tk/config.h.in
@@ -0,0 +1,3 @@
+#define D2TK_PTY @D2TK_PTY@
+#define D2TK_EVDEV @D2TK_EVDEV@
+#define D2TK_INPUT_1_15 @D2TK_INPUT_1_15@
diff --git a/d2tk/core.h b/d2tk/core.h
index 903118d..bbe0d3c 100644
--- a/d2tk/core.h
+++ b/d2tk/core.h
@@ -206,6 +206,9 @@ d2tk_core_set_dimensions(d2tk_core_t *core, d2tk_coord_t w, d2tk_coord_t h);
D2TK_API void
d2tk_core_get_dimensions(d2tk_core_t *core, d2tk_coord_t *w, d2tk_coord_t *h);
+D2TK_API void
+d2tk_core_set_full_refresh(d2tk_core_t *core);
+
#ifdef __cplusplus
}
#endif
diff --git a/d2tk/hash.h b/d2tk/hash.h
index 52cf33e..a6cf614 100644
--- a/d2tk/hash.h
+++ b/d2tk/hash.h
@@ -15,8 +15,8 @@
* http://www.perlfoundation.org/artistic_license_2_0.
*/
-#ifndef _D2TK_MURMUR32_H
-#define _D2TK_MURMUR32_H
+#ifndef _D2TK_HASH_H
+#define _D2TK_HASH_H
#include <stdint.h>
#include <unistd.h>
@@ -47,4 +47,4 @@ d2tk_hash_dict(const d2tk_hash_dict_t *dict);
}
#endif
-#endif // _D2TK_MURMUR32_H
+#endif // _D2TK_HASH_H
diff --git a/example/example.c b/example/example.c
index 3159bf8..166c602 100644
--- a/example/example.c
+++ b/example/example.c
@@ -17,6 +17,7 @@
#include <stdio.h>
#include <stdlib.h>
+#include <unistd.h>
#include <math.h>
#include <inttypes.h>
#include <dirent.h>
@@ -25,7 +26,7 @@
#include <d2tk/frontend_pugl.h>
#include "example/example.h"
-#if !defined(_WIN32) && !defined(__APPLE__)
+#if D2TK_EVDEV
# include <libevdev/libevdev.h>
# include <libevdev/libevdev-uinput.h>
@@ -64,8 +65,13 @@ typedef enum _bar_t {
BAR_METER,
BAR_FRAME,
BAR_UTF8,
+#if D2TK_PTY
+ BAR_PTY,
+#endif
#if !defined(_WIN32) && !defined(__APPLE__)
BAR_BROWSER,
+#endif
+#if D2TK_EVDEV
BAR_KEYBOARD,
#endif
@@ -84,8 +90,13 @@ static const char *bar_lbl [BAR_MAX] = {
[BAR_METER] = "Meter",
[BAR_FRAME] = "Frame",
[BAR_UTF8] = "UTF-8",
+#if D2TK_PTY
+ [BAR_PTY] = "PTY",
+#endif
#if !defined(_WIN32) && !defined(__APPLE__)
[BAR_BROWSER] = "Browser",
+#endif
+#if D2TK_EVDEV
[BAR_KEYBOARD] = "Keyboard"
#endif
};
@@ -428,8 +439,10 @@ _render_c_scroll(d2tk_base_t *base, const d2tk_rect_t *rect)
d2tk_style_t style = *d2tk_base_get_default_style();
+ const uint32_t hmax [2] = { N, 0 };
+ const uint32_t hnum [2] = { N_2, 0 };
D2TK_BASE_SCROLLBAR(base, rect, D2TK_ID, D2TK_FLAG_SCROLL_X,
- N, 0, N_2, 0, hscroll)
+ hmax, hnum, hscroll)
{
const float hoffset = d2tk_scrollbar_get_offset_x(hscroll);
const d2tk_rect_t *row = d2tk_scrollbar_get_rect(hscroll);
@@ -439,8 +452,10 @@ _render_c_scroll(d2tk_base_t *base, const d2tk_rect_t *rect)
const unsigned j = d2tk_table_get_index_x(tcol) + hoffset;
const d2tk_rect_t *col = d2tk_table_get_rect(tcol);
+ const uint32_t vmax [2] = { 0, M/(j+1) };
+ const uint32_t vnum [2] = { 0, O };
D2TK_BASE_SCROLLBAR(base, col, D2TK_ID_IDX(j), D2TK_FLAG_SCROLL_Y,
- 0, M/(j+1), 0, O, vscroll)
+ vmax, vnum, vscroll)
{
const float voffset = d2tk_scrollbar_get_offset_y(vscroll);
const d2tk_rect_t *sub = d2tk_scrollbar_get_rect(vscroll);
@@ -789,6 +804,114 @@ _render_c_utf8(d2tk_base_t *base, const d2tk_rect_t *rect)
#undef N
}
+#if D2TK_PTY
+#include <linenoise/linenoise.h>
+
+static void
+_completion(const char *buf, linenoiseCompletions *lc)
+{
+ switch(buf[0])
+ {
+ case 'b':
+ {
+ linenoiseAddCompletion(lc, "bar");
+ } break;
+ case 'c':
+ {
+ linenoiseAddCompletion(lc, "clear");
+ } break;
+ case 'f':
+ {
+ linenoiseAddCompletion(lc, "foo");
+ } break;
+ }
+}
+
+static char *
+_hints(const char *buf, int *color, int *bold)
+{
+ static const char *hint = "<cmd>";
+
+ switch(buf[0])
+ {
+ case '\0':
+ {
+ *color = 36;
+ *bold = 0;
+ return (char *)hint;
+ } break;
+ }
+
+ return NULL;
+}
+
+static void
+_curses(FILE *err __attribute__((unused)), void *data __attribute__((unused)))
+{
+ linenoiseSetMultiLine(0);
+ linenoiseSetCompletionCallback(_completion);
+ linenoiseSetHintsCallback(_hints);
+ linenoiseHistorySetMaxLen(128);
+
+ while(true)
+ {
+ char *line = linenoise("> ");
+
+ if(!line)
+ {
+ break;
+ }
+
+ if(strlen(line))
+ {
+ //FIXME
+ linenoiseHistoryAdd(line);
+ }
+
+ if(!strcmp(line, "clear"))
+ {
+ linenoiseClearScreen();
+ }
+
+ linenoiseFree(line);
+ }
+
+ _exit(EXIT_SUCCESS);
+}
+
+static inline void
+_render_c_pty(d2tk_base_t *base, const d2tk_rect_t *rect)
+{
+#define HEIGHT 16
+ static char *argv [] = {
+ "bash",
+ NULL
+ };
+
+ static d2tk_coord_t hfrac [2] = { 1, 1 };
+
+ D2TK_BASE_LAYOUT(rect, 2, hfrac, D2TK_FLAG_LAYOUT_X_REL, hlay)
+ {
+ const d2tk_rect_t *hrect = d2tk_layout_get_rect(hlay);
+ const d2tk_coord_t x = d2tk_layout_get_index(hlay);
+
+ switch(x)
+ {
+ case 0:
+ {
+ d2tk_base_pty(base, D2TK_ID, NULL, argv, HEIGHT, hrect, false);
+ } break;
+ case 1:
+ {
+ d2tk_base_pty(base, D2TK_ID, _curses, NULL, HEIGHT, hrect, false);
+ } break;
+ }
+ }
+
+#undef HEIGHT
+}
+#endif
+
#if !defined(_WIN32) && !defined(__APPLE__)
static int
strcasenumcmp(const char *s1, const char *s2)
@@ -931,8 +1054,10 @@ _render_c_browser(d2tk_base_t *base, const d2tk_rect_t *rect)
ndir++;
}
+ const uint32_t max [2] = { 0, ndir };
+ const uint32_t num [2] = { 0, M };
D2TK_BASE_SCROLLBAR(base, col, D2TK_ID, D2TK_FLAG_SCROLL_Y,
- 0, ndir, 0, M, vscroll)
+ max, num, vscroll)
{
const float voffset = d2tk_scrollbar_get_offset_y(vscroll);
const d2tk_rect_t *row = d2tk_scrollbar_get_rect(vscroll);
@@ -979,8 +1104,10 @@ _render_c_browser(d2tk_base_t *base, const d2tk_rect_t *rect)
case 1:
{
+ const uint32_t max [2] = { 0, nlist };
+ const uint32_t num [2] = { 0, M };
D2TK_BASE_SCROLLBAR(base, col, D2TK_ID, D2TK_FLAG_SCROLL_Y,
- 0, nlist, 0, M, vscroll)
+ max, num, vscroll)
{
const float voffset = d2tk_scrollbar_get_offset_y(vscroll);
const d2tk_rect_t *row = d2tk_scrollbar_get_rect(vscroll);
@@ -1031,7 +1158,9 @@ _render_c_browser(d2tk_base_t *base, const d2tk_rect_t *rect)
_file_list_free(list);
#undef M
}
+#endif
+#if D2TK_EVDEV
static void
_fake_event(unsigned type, unsigned code, int value)
{
@@ -1532,7 +1661,7 @@ _render_c_keyboard(d2tk_base_t *base, const d2tk_rect_t *rect)
D2TK_API int
d2tk_example_init(void)
{
-#if !defined(_WIN32) && !defined(__APPLE__)
+#if D2TK_EVDEV
fake.dev = libevdev_new();
if(!fake.dev)
{
@@ -1564,7 +1693,7 @@ d2tk_example_init(void)
D2TK_API void
d2tk_example_deinit(void)
{
-#if !defined(_WIN32) && !defined(__APPLE__)
+#if D2TK_EVDEV
if(fake.uidev)
{
libevdev_uinput_destroy(fake.uidev);
@@ -1650,11 +1779,19 @@ d2tk_example_run(d2tk_base_t *base, d2tk_coord_t w, d2tk_coord_t h)
{
_render_c_utf8(base, vrect);
} break;
+#if D2TK_PTY
+ case BAR_PTY:
+ {
+ _render_c_pty(base, vrect);
+ } break;
+#endif
#if !defined(_WIN32) && !defined(__APPLE__)
case BAR_BROWSER:
{
_render_c_browser(base, vrect);
} break;
+#endif
+#if D2TK_EVDEV
case BAR_KEYBOARD:
{
_render_c_keyboard(base, vrect);
diff --git a/linenoise/.gitignore b/linenoise/.gitignore
new file mode 100644
index 0000000..7ab7825
--- /dev/null
+++ b/linenoise/.gitignore
@@ -0,0 +1,3 @@
+linenoise_example
+*.dSYM
+history.txt
diff --git a/linenoise/LICENSE b/linenoise/LICENSE
new file mode 100644
index 0000000..18e8148
--- /dev/null
+++ b/linenoise/LICENSE
@@ -0,0 +1,25 @@
+Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+* 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.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
diff --git a/linenoise/Makefile b/linenoise/Makefile
new file mode 100644
index 0000000..a285410
--- /dev/null
+++ b/linenoise/Makefile
@@ -0,0 +1,7 @@
+linenoise_example: linenoise.h linenoise.c
+
+linenoise_example: linenoise.c example.c
+ $(CC) -Wall -W -Os -g -o linenoise_example linenoise.c example.c
+
+clean:
+ rm -f linenoise_example
diff --git a/linenoise/README.markdown b/linenoise/README.markdown
new file mode 100644
index 0000000..9f583c7
--- /dev/null
+++ b/linenoise/README.markdown
@@ -0,0 +1,229 @@
+# Linenoise
+
+A minimal, zero-config, BSD licensed, readline replacement used in Redis,
+MongoDB, and Android.
+
+* Single and multi line editing mode with the usual key bindings implemented.
+* History handling.
+* Completion.
+* Hints (suggestions at the right of the prompt as you type).
+* About 1,100 lines of BSD license source code.
+* Only uses a subset of VT100 escapes (ANSI.SYS compatible).
+
+## Can a line editing library be 20k lines of code?
+
+Line editing with some support for history is a really important feature for command line utilities. Instead of retyping almost the same stuff again and again it's just much better to hit the up arrow and edit on syntax errors, or in order to try a slightly different command. But apparently code dealing with terminals is some sort of Black Magic: readline is 30k lines of code, libedit 20k. Is it reasonable to link small utilities to huge libraries just to get a minimal support for line editing?
+
+So what usually happens is either:
+
+ * Large programs with configure scripts disabling line editing if readline is not present in the system, or not supporting it at all since readline is GPL licensed and libedit (the BSD clone) is not as known and available as readline is (Real world example of this problem: Tclsh).
+ * Smaller programs not using a configure script not supporting line editing at all (A problem we had with Redis-cli for instance).
+
+The result is a pollution of binaries without line editing support.
+
+So I spent more or less two hours doing a reality check resulting in this little library: is it *really* needed for a line editing library to be 20k lines of code? Apparently not, it is possibe to get a very small, zero configuration, trivial to embed library, that solves the problem. Smaller programs will just include this, supporting line editing out of the box. Larger programs may use this little library or just checking with configure if readline/libedit is available and resorting to Linenoise if not.
+
+## Terminals, in 2010.
+
+Apparently almost every terminal you can happen to use today has some kind of support for basic VT100 escape sequences. So I tried to write a lib using just very basic VT100 features. The resulting library appears to work everywhere I tried to use it, and now can work even on ANSI.SYS compatible terminals, since no
+VT220 specific sequences are used anymore.
+
+The library is currently about 1100 lines of code. In order to use it in your project just look at the *example.c* file in the source distribution, it is trivial. Linenoise is BSD code, so you can use both in free software and commercial software.
+
+## Tested with...
+
+ * Linux text only console ($TERM = linux)
+ * Linux KDE terminal application ($TERM = xterm)
+ * Linux xterm ($TERM = xterm)
+ * Linux Buildroot ($TERM = vt100)
+ * Mac OS X iTerm ($TERM = xterm)
+ * Mac OS X default Terminal.app ($TERM = xterm)
+ * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen)
+ * IBM AIX 6.1
+ * FreeBSD xterm ($TERM = xterm)
+ * ANSI.SYS
+ * Emacs comint mode ($TERM = dumb)
+
+Please test it everywhere you can and report back!
+
+## Let's push this forward!
+
+Patches should be provided in the respect of Linenoise sensibility for small
+easy to understand code.
+
+Send feedbacks to antirez at gmail
+
+# The API
+
+Linenoise is very easy to use, and reading the example shipped with the
+library should get you up to speed ASAP. Here is a list of API calls
+and how to use them.
+
+ char *linenoise(const char *prompt);
+
+This is the main Linenoise call: it shows the user a prompt with line editing
+and history capabilities. The prompt you specify is used as a prompt, that is,
+it will be printed to the left of the cursor. The library returns a buffer
+with the line composed by the user, or NULL on end of file or when there
+is an out of memory condition.
+
+When a tty is detected (the user is actually typing into a terminal session)
+the maximum editable line length is `LINENOISE_MAX_LINE`. When instead the
+standard input is not a tty, which happens every time you redirect a file
+to a program, or use it in an Unix pipeline, there are no limits to the
+length of the line that can be returned.
+
+The returned line should be freed with the `free()` standard system call.
+However sometimes it could happen that your program uses a different dynamic
+allocation library, so you may also used `linenoiseFree` to make sure the
+line is freed with the same allocator it was created.
+
+The canonical loop used by a program using Linenoise will be something like
+this:
+
+ while((line = linenoise("hello> ")) != NULL) {
+ printf("You wrote: %s\n", line);
+ linenoiseFree(line); /* Or just free(line) if you use libc malloc. */
+ }
+
+## Single line VS multi line editing
+
+By default, Linenoise uses single line editing, that is, a single row on the
+screen will be used, and as the user types more, the text will scroll towards
+left to make room. This works if your program is one where the user is
+unlikely to write a lot of text, otherwise multi line editing, where multiple
+screens rows are used, can be a lot more comfortable.
+
+In order to enable multi line editing use the following API call:
+
+ linenoiseSetMultiLine(1);
+
+You can disable it using `0` as argument.
+
+## History
+
+Linenoise supporst history, so that the user does not have to retype
+again and again the same things, but can use the down and up arrows in order
+to search and re-edit already inserted lines of text.
+
+The followings are the history API calls:
+
+ int linenoiseHistoryAdd(const char *line);
+ int linenoiseHistorySetMaxLen(int len);
+ int linenoiseHistorySave(const char *filename);
+ int linenoiseHistoryLoad(const char *filename);
+
+Use `linenoiseHistoryAdd` every time you want to add a new element
+to the top of the history (it will be the first the user will see when
+using the up arrow).
+
+Note that for history to work, you have to set a length for the history
+(which is zero by default, so history will be disabled if you don't set
+a proper one). This is accomplished using the `linenoiseHistorySetMaxLen`
+function.
+
+Linenoise has direct support for persisting the history into an history
+file. The functions `linenoiseHistorySave` and `linenoiseHistoryLoad` do
+just that. Both functions return -1 on error and 0 on success.
+
+## Completion
+
+Linenoise supports completion, which is the ability to complete the user
+input when she or he presses the `<TAB>` key.
+
+In order to use completion, you need to register a completion callback, which
+is called every time the user presses `<TAB>`. Your callback will return a
+list of items that are completions for the current string.
+
+The following is an example of registering a completion callback:
+
+ linenoiseSetCompletionCallback(completion);
+
+The completion must be a function returning `void` and getting as input
+a `const char` pointer, which is the line the user has typed so far, and
+a `linenoiseCompletions` object pointer, which is used as argument of
+`linenoiseAddCompletion` in order to add completions inside the callback.
+An example will make it more clear:
+
+ void completion(const char *buf, linenoiseCompletions *lc) {
+ if (buf[0] == 'h') {
+ linenoiseAddCompletion(lc,"hello");
+ linenoiseAddCompletion(lc,"hello there");
+ }
+ }
+
+Basically in your completion callback, you inspect the input, and return
+a list of items that are good completions by using `linenoiseAddCompletion`.
+
+If you want to test the completion feature, compile the example program
+with `make`, run it, type `h` and press `<TAB>`.
+
+## Hints
+
+Linenoise has a feature called *hints* which is very useful when you
+use Linenoise in order to implement a REPL (Read Eval Print Loop) for
+a program that accepts commands and arguments, but may also be useful in
+other conditions.
+
+The feature shows, on the right of the cursor, as the user types, hints that
+may be useful. The hints can be displayed using a different color compared
+to the color the user is typing, and can also be bold.
+
+For example as the user starts to type `"git remote add"`, with hints it's
+possible to show on the right of the prompt a string `<name> <url>`.
+
+The feature works similarly to the history feature, using a callback.
+To register the callback we use:
+
+ linenoiseSetHintsCallback(hints);
+
+The callback itself is implemented like this:
+
+ char *hints(const char *buf, int *color, int *bold) {
+ if (!strcasecmp(buf,"git remote add")) {
+ *color = 35;
+ *bold = 0;
+ return " <name> <url>";
+ }
+ return NULL;
+ }
+
+The callback function returns the string that should be displayed or NULL
+if no hint is available for the text the user currently typed. The returned
+string will be trimmed as needed depending on the number of columns available
+on the screen.
+
+It is possible to return a string allocated in dynamic way, by also registering
+a function to deallocate the hint string once used:
+
+ void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
+
+The free hint callback will just receive the pointer and free the string
+as needed (depending on how the hits callback allocated it).
+
+As you can see in the example above, a `color` (in xterm color terminal codes)
+can be provided together with a `bold` attribute. If no color is set, the
+current terminal foreground color is used. If no bold attribute is set,
+non-bold text is printed.
+
+Color codes are:
+
+ red = 31
+ green = 32
+ yellow = 33
+ blue = 34
+ magenta = 35
+ cyan = 36
+ white = 37;
+
+## Screen handling
+
+Sometimes you may want to clear the screen as a result of something the
+user typed. You can do this by calling the following function:
+
+ void linenoiseClearScreen(void);
+
+## Related projects
+
+* [Linenoise NG](https://github.com/arangodb/linenoise-ng) is a fork of Linenoise that aims to add more advanced features like UTF-8 support, Windows support and other features. Uses C++ instead of C as development language.
+* [Linenoise-swift](https://github.com/andybest/linenoise-swift) is a reimplementation of Linenoise written in Swift.
diff --git a/linenoise/example.c b/linenoise/example.c
new file mode 100644
index 0000000..3a544d3
--- /dev/null
+++ b/linenoise/example.c
@@ -0,0 +1,74 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "linenoise.h"
+
+
+void completion(const char *buf, linenoiseCompletions *lc) {
+ if (buf[0] == 'h') {
+ linenoiseAddCompletion(lc,"hello");
+ linenoiseAddCompletion(lc,"hello there");
+ }
+}
+
+char *hints(const char *buf, int *color, int *bold) {
+ if (!strcasecmp(buf,"hello")) {
+ *color = 35;
+ *bold = 0;
+ return " World";
+ }
+ return NULL;
+}
+
+int main(int argc, char **argv) {
+ char *line;
+ char *prgname = argv[0];
+
+ /* Parse options, with --multiline we enable multi line editing. */
+ while(argc > 1) {
+ argc--;
+ argv++;
+ if (!strcmp(*argv,"--multiline")) {
+ linenoiseSetMultiLine(1);
+ printf("Multi-line mode enabled.\n");
+ } else if (!strcmp(*argv,"--keycodes")) {
+ linenoisePrintKeyCodes();
+ exit(0);
+ } else {
+ fprintf(stderr, "Usage: %s [--multiline] [--keycodes]\n", prgname);
+ exit(1);
+ }
+ }
+
+ /* Set the completion callback. This will be called every time the
+ * user uses the <tab> key. */
+ linenoiseSetCompletionCallback(completion);
+ linenoiseSetHintsCallback(hints);
+
+ /* Load history from file. The history file is just a plain text file
+ * where entries are separated by newlines. */
+ linenoiseHistoryLoad("history.txt"); /* Load the history at startup */
+
+ /* Now this is the main loop of the typical linenoise-based application.
+ * The call to linenoise() will block as long as the user types something
+ * and presses enter.
+ *
+ * The typed string is returned as a malloc() allocated string by
+ * linenoise, so the user needs to free() it. */
+ while((line = linenoise("hello> ")) != NULL) {
+ /* Do something with the string. */
+ if (line[0] != '\0' && line[0] != '/') {
+ printf("echo: '%s'\n", line);
+ linenoiseHistoryAdd(line); /* Add to the history. */
+ linenoiseHistorySave("history.txt"); /* Save the history on disk. */
+ } else if (!strncmp(line,"/historylen",11)) {
+ /* The "/historylen" command will change the history len. */
+ int len = atoi(line+11);
+ linenoiseHistorySetMaxLen(len);
+ } else if (line[0] == '/') {
+ printf("Unreconized command: %s\n", line);
+ }
+ free(line);
+ }
+ return 0;
+}
diff --git a/linenoise/linenoise.c b/linenoise/linenoise.c
new file mode 100644
index 0000000..10ffd71
--- /dev/null
+++ b/linenoise/linenoise.c
@@ -0,0 +1,1201 @@
+/* linenoise.c -- guerrilla line editing library against the idea that a
+ * line editing lib needs to be 20,000 lines of C code.
+ *
+ * You can find the latest source code at:
+ *
+ * http://github.com/antirez/linenoise
+ *
+ * Does a number of crazy assumptions that happen to be true in 99.9999% of
+ * the 2010 UNIX computers around.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2016, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDER 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.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * References:
+ * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
+ * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html
+ *
+ * Todo list:
+ * - Filter bogus Ctrl+<char> combinations.
+ * - Win32 support
+ *
+ * Bloat:
+ * - History search like Ctrl+r in readline?
+ *
+ * List of escape sequences used by this program, we do everything just
+ * with three sequences. In order to be so cheap we may have some
+ * flickering effect with some slow terminal, but the lesser sequences
+ * the more compatible.
+ *
+ * EL (Erase Line)
+ * Sequence: ESC [ n K
+ * Effect: if n is 0 or missing, clear from cursor to end of line
+ * Effect: if n is 1, clear from beginning of line to cursor
+ * Effect: if n is 2, clear entire line
+ *
+ * CUF (CUrsor Forward)
+ * Sequence: ESC [ n C
+ * Effect: moves cursor forward n chars
+ *
+ * CUB (CUrsor Backward)
+ * Sequence: ESC [ n D
+ * Effect: moves cursor backward n chars
+ *
+ * The following is used to get the terminal width if getting
+ * the width with the TIOCGWINSZ ioctl fails
+ *
+ * DSR (Device Status Report)
+ * Sequence: ESC [ 6 n
+ * Effect: reports the current cusor position as ESC [ n ; m R
+ * where n is the row and m is the column
+ *
+ * When multi line mode is enabled, we also use an additional escape
+ * sequence. However multi line editing is disabled by default.
+ *
+ * CUU (Cursor Up)
+ * Sequence: ESC [ n A
+ * Effect: moves cursor up of n chars.
+ *
+ * CUD (Cursor Down)
+ * Sequence: ESC [ n B
+ * Effect: moves cursor down of n chars.
+ *
+ * When linenoiseClearScreen() is called, two additional escape sequences
+ * are used in order to clear the screen and position the cursor at home
+ * position.
+ *
+ * CUP (Cursor position)
+ * Sequence: ESC [ H
+ * Effect: moves the cursor to upper left corner
+ *
+ * ED (Erase display)
+ * Sequence: ESC [ 2 J
+ * Effect: clear the whole screen
+ *
+ */
+
+#include <termios.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include "linenoise.h"
+
+#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100
+#define LINENOISE_MAX_LINE 4096
+static char *unsupported_term[] = {"dumb","cons25","emacs",NULL};
+static linenoiseCompletionCallback *completionCallback = NULL;
+static linenoiseHintsCallback *hintsCallback = NULL;
+static linenoiseFreeHintsCallback *freeHintsCallback = NULL;
+
+static struct termios orig_termios; /* In order to restore at exit.*/
+static int rawmode = 0; /* For atexit() function to check if restore is needed*/
+static int mlmode = 0; /* Multi line mode. Default is single line. */
+static int atexit_registered = 0; /* Register atexit just 1 time. */
+static int history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN;
+static int history_len = 0;
+static char **history = NULL;
+
+/* The linenoiseState structure represents the state during line editing.
+ * We pass this state to functions implementing specific editing
+ * functionalities. */
+struct linenoiseState {
+ int ifd; /* Terminal stdin file descriptor. */
+ int ofd; /* Terminal stdout file descriptor. */
+ char *buf; /* Edited line buffer. */
+ size_t buflen; /* Edited line buffer size. */
+ const char *prompt; /* Prompt to display. */
+ size_t plen; /* Prompt length. */
+ size_t pos; /* Current cursor position. */
+ size_t oldpos; /* Previous refresh cursor position. */
+ size_t len; /* Current edited line length. */
+ size_t cols; /* Number of columns in terminal. */
+ size_t maxrows; /* Maximum num of rows used so far (multiline mode) */
+ int history_index; /* The history index we are currently editing. */
+};
+
+enum KEY_ACTION{
+ KEY_NULL = 0, /* NULL */
+ CTRL_A = 1, /* Ctrl+a */
+ CTRL_B = 2, /* Ctrl-b */
+ CTRL_C = 3, /* Ctrl-c */
+ CTRL_D = 4, /* Ctrl-d */
+ CTRL_E = 5, /* Ctrl-e */
+ CTRL_F = 6, /* Ctrl-f */
+ CTRL_H = 8, /* Ctrl-h */
+ TAB = 9, /* Tab */
+ CTRL_K = 11, /* Ctrl+k */
+ CTRL_L = 12, /* Ctrl+l */
+ ENTER = 13, /* Enter */
+ CTRL_N = 14, /* Ctrl-n */
+ CTRL_P = 16, /* Ctrl-p */
+ CTRL_T = 20, /* Ctrl-t */
+ CTRL_U = 21, /* Ctrl+u */
+ CTRL_W = 23, /* Ctrl+w */
+ ESC = 27, /* Escape */
+ BACKSPACE = 127 /* Backspace */
+};
+
+static void linenoiseAtExit(void);
+int linenoiseHistoryAdd(const char *line);
+static void refreshLine(struct linenoiseState *l);
+
+/* Debugging macro. */
+#if 0
+FILE *lndebug_fp = NULL;
+#define lndebug(...) \
+ do { \
+ if (lndebug_fp == NULL) { \
+ lndebug_fp = fopen("/tmp/lndebug.txt","a"); \
+ fprintf(lndebug_fp, \
+ "[%d %d %d] p: %d, rows: %d, rpos: %d, max: %d, oldmax: %d\n", \
+ (int)l->len,(int)l->pos,(int)l->oldpos,plen,rows,rpos, \
+ (int)l->maxrows,old_rows); \
+ } \
+ fprintf(lndebug_fp, ", " __VA_ARGS__); \
+ fflush(lndebug_fp); \
+ } while (0)
+#else
+#define lndebug(fmt, ...)
+#endif
+
+/* ======================= Low level terminal handling ====================== */
+
+/* Set if to use or not the multi line mode. */
+void linenoiseSetMultiLine(int ml) {
+ mlmode = ml;
+}
+
+/* Return true if the terminal name is in the list of terminals we know are
+ * not able to understand basic escape sequences. */
+static int isUnsupportedTerm(void) {
+ char *term = getenv("TERM");
+ int j;
+
+ if (term == NULL) return 0;
+ for (j = 0; unsupported_term[j]; j++)
+ if (!strcasecmp(term,unsupported_term[j])) return 1;
+ return 0;
+}
+
+/* Raw mode: 1960 magic shit. */
+static int enableRawMode(int fd) {
+ struct termios raw;
+
+ if (!isatty(STDIN_FILENO)) goto fatal;
+ if (!atexit_registered) {
+ atexit(linenoiseAtExit);
+ atexit_registered = 1;
+ }
+ if (tcgetattr(fd,&orig_termios) == -1) goto fatal;
+
+ raw = orig_termios; /* modify the original mode */
+ /* input modes: no break, no CR to NL, no parity check, no strip char,
+ * no start/stop output control. */
+ raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
+ /* output modes - disable post processing */
+ raw.c_oflag &= ~(OPOST);
+ /* control modes - set 8 bit chars */
+ raw.c_cflag |= (CS8);
+ /* local modes - choing off, canonical off, no extended functions,
+ * no signal chars (^Z,^C) */
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
+ /* control chars - set return condition: min number of bytes and timer.
+ * We want read to return every single byte, without timeout. */
+ raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */
+
+ /* put terminal in raw mode after flushing */
+ if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal;
+ rawmode = 1;
+ return 0;
+
+fatal:
+ errno = ENOTTY;
+ return -1;
+}
+
+static void disableRawMode(int fd) {
+ /* Don't even check the return value as it's too late. */
+ if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1)
+ rawmode = 0;
+}
+
+/* Use the ESC [6n escape sequence to query the horizontal cursor position
+ * and return it. On error -1 is returned, on success the position of the
+ * cursor. */
+static int getCursorPosition(int ifd, int ofd) {
+ char buf[32];
+ int cols, rows;
+ unsigned int i = 0;
+
+ /* Report cursor location */
+ if (write(ofd, "\x1b[6n", 4) != 4) return -1;
+
+ /* Read the response: ESC [ rows ; cols R */
+ while (i < sizeof(buf)-1) {
+ if (read(ifd,buf+i,1) != 1) break;
+ if (buf[i] == 'R') break;
+ i++;
+ }
+ buf[i] = '\0';
+
+ /* Parse it. */
+ if (buf[0] != ESC || buf[1] != '[') return -1;
+ if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1;
+ return cols;
+}
+
+/* Try to get the number of columns in the current terminal, or assume 80
+ * if it fails. */
+static int getColumns(int ifd, int ofd) {
+ struct winsize ws;
+
+ if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
+ /* ioctl() failed. Try to query the terminal itself. */
+ int start, cols;
+
+ /* Get the initial position so we can restore it later. */
+ start = getCursorPosition(ifd,ofd);
+ if (start == -1) goto failed;
+
+ /* Go to right margin and get position. */
+ if (write(ofd,"\x1b[999C",6) != 6) goto failed;
+ cols = getCursorPosition(ifd,ofd);
+ if (cols == -1) goto failed;
+
+ /* Restore position. */
+ if (cols > start) {
+ char seq[32];
+ snprintf(seq,32,"\x1b[%dD",cols-start);
+ if (write(ofd,seq,strlen(seq)) == -1) {
+ /* Can't recover... */
+ }
+ }
+ return cols;
+ } else {
+ return ws.ws_col;
+ }
+
+failed:
+ return 80;
+}
+
+/* Clear the screen. Used to handle ctrl+l */
+void linenoiseClearScreen(void) {
+ if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) {
+ /* nothing to do, just to avoid warning. */
+ }
+}
+
+/* Beep, used for completion when there is nothing to complete or when all
+ * the choices were already shown. */
+static void linenoiseBeep(void) {
+ fprintf(stderr, "\x7");
+ fflush(stderr);
+}
+
+/* ============================== Completion ================================ */
+
+/* Free a list of completion option populated by linenoiseAddCompletion(). */
+static void freeCompletions(linenoiseCompletions *lc) {
+ size_t i;
+ for (i = 0; i < lc->len; i++)
+ free(lc->cvec[i]);
+ if (lc->cvec != NULL)
+ free(lc->cvec);
+}
+
+/* This is an helper function for linenoiseEdit() and is called when the
+ * user types the <tab> key in order to complete the string currently in the
+ * input.
+ *
+ * The state of the editing is encapsulated into the pointed linenoiseState
+ * structure as described in the structure definition. */
+static int completeLine(struct linenoiseState *ls) {
+ linenoiseCompletions lc = { 0, NULL };
+ int nread, nwritten;
+ char c = 0;
+
+ completionCallback(ls->buf,&lc);
+ if (lc.len == 0) {
+ linenoiseBeep();
+ } else {
+ size_t stop = 0, i = 0;
+
+ while(!stop) {
+ /* Show completion or original buffer */
+ if (i < lc.len) {
+ struct linenoiseState saved = *ls;
+
+ ls->len = ls->pos = strlen(lc.cvec[i]);
+ ls->buf = lc.cvec[i];
+ refreshLine(ls);
+ ls->len = saved.len;
+ ls->pos = saved.pos;
+ ls->buf = saved.buf;
+ } else {
+ refreshLine(ls);
+ }
+
+ nread = read(ls->ifd,&c,1);
+ if (nread <= 0) {
+ freeCompletions(&lc);
+ return -1;
+ }
+
+ switch(c) {
+ case 9: /* tab */
+ i = (i+1) % (lc.len+1);
+ if (i == lc.len) linenoiseBeep();
+ break;
+ case 27: /* escape */
+ /* Re-show original buffer */
+ if (i < lc.len) refreshLine(ls);
+ stop = 1;
+ break;
+ default:
+ /* Update buffer and return */
+ if (i < lc.len) {
+ nwritten = snprintf(ls->buf,ls->buflen,"%s",lc.cvec[i]);
+ ls->len = ls->pos = nwritten;
+ }
+ stop = 1;
+ break;
+ }
+ }
+ }
+
+ freeCompletions(&lc);
+ return c; /* Return last read character */
+}
+
+/* Register a callback function to be called for tab-completion. */
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *fn) {
+ completionCallback = fn;
+}
+
+/* Register a hits function to be called to show hits to the user at the
+ * right of the prompt. */
+void linenoiseSetHintsCallback(linenoiseHintsCallback *fn) {
+ hintsCallback = fn;
+}
+
+/* Register a function to free the hints returned by the hints callback
+ * registered with linenoiseSetHintsCallback(). */
+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *fn) {
+ freeHintsCallback = fn;
+}
+
+/* This function is used by the callback function registered by the user
+ * in order to add completion options given the input string when the
+ * user typed <tab>. See the example.c source code for a very easy to
+ * understand example. */
+void linenoiseAddCompletion(linenoiseCompletions *lc, const char *str) {
+ size_t len = strlen(str);
+ char *copy, **cvec;
+
+ copy = malloc(len+1);
+ if (copy == NULL) return;
+ memcpy(copy,str,len+1);
+ cvec = realloc(lc->cvec,sizeof(char*)*(lc->len+1));
+ if (cvec == NULL) {
+ free(copy);
+ return;
+ }
+ lc->cvec = cvec;
+ lc->cvec[lc->len++] = copy;
+}
+
+/* =========================== Line editing ================================= */
+
+/* We define a very simple "append buffer" structure, that is an heap
+ * allocated string where we can append to. This is useful in order to
+ * write all the escape sequences in a buffer and flush them to the standard
+ * output in a single call, to avoid flickering effects. */
+struct abuf {
+ char *b;
+ int len;
+};
+
+static void abInit(struct abuf *ab) {
+ ab->b = NULL;
+ ab->len = 0;
+}
+
+static void abAppend(struct abuf *ab, const char *s, int len) {
+ char *new = realloc(ab->b,ab->len+len);
+
+ if (new == NULL) return;
+ memcpy(new+ab->len,s,len);
+ ab->b = new;
+ ab->len += len;
+}
+
+static void abFree(struct abuf *ab) {
+ free(ab->b);
+}
+
+/* Helper of refreshSingleLine() and refreshMultiLine() to show hints
+ * to the right of the prompt. */
+void refreshShowHints(struct abuf *ab, struct linenoiseState *l, int plen) {
+ char seq[64];
+ if (hintsCallback && plen+l->len < l->cols) {
+ int color = -1, bold = 0;
+ char *hint = hintsCallback(l->buf,&color,&bold);
+ if (hint) {
+ int hintlen = strlen(hint);
+ int hintmaxlen = l->cols-(plen+l->len);
+ if (hintlen > hintmaxlen) hintlen = hintmaxlen;
+ if (bold == 1 && color == -1) color = 37;
+ if (color != -1 || bold != 0)
+ snprintf(seq,64,"\033[%d;%d;49m",bold,color);
+ else
+ seq[0] = '\0';
+ abAppend(ab,seq,strlen(seq));
+ abAppend(ab,hint,hintlen);
+ if (color != -1 || bold != 0)
+ abAppend(ab,"\033[0m",4);
+ /* Call the function to free the hint returned. */
+ if (freeHintsCallback) freeHintsCallback(hint);
+ }
+ }
+}
+
+/* Single line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshSingleLine(struct linenoiseState *l) {
+ char seq[64];
+ size_t plen = strlen(l->prompt);
+ int fd = l->ofd;
+ char *buf = l->buf;
+ size_t len = l->len;
+ size_t pos = l->pos;
+ struct abuf ab;
+
+ while((plen+pos) >= l->cols) {
+ buf++;
+ len--;
+ pos--;
+ }
+ while (plen+len > l->cols) {
+ len--;
+ }
+
+ abInit(&ab);
+ /* Cursor to left edge */
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,buf,len);
+ /* Show hits if any. */
+ refreshShowHints(&ab,l,plen);
+ /* Erase to right */
+ snprintf(seq,64,"\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+ /* Move cursor to original position. */
+ snprintf(seq,64,"\r\x1b[%dC", (int)(pos+plen));
+ abAppend(&ab,seq,strlen(seq));
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Multi line low level line refresh.
+ *
+ * Rewrite the currently edited line accordingly to the buffer content,
+ * cursor position, and number of columns of the terminal. */
+static void refreshMultiLine(struct linenoiseState *l) {
+ char seq[64];
+ int plen = strlen(l->prompt);
+ int rows = (plen+l->len+l->cols-1)/l->cols; /* rows used by current buf. */
+ int rpos = (plen+l->oldpos+l->cols)/l->cols; /* cursor relative row. */
+ int rpos2; /* rpos after refresh. */
+ int col; /* colum position, zero-based. */
+ int old_rows = l->maxrows;
+ int fd = l->ofd, j;
+ struct abuf ab;
+
+ /* Update maxrows if needed. */
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+
+ /* First step: clear all the lines used before. To do so start by
+ * going to the last row. */
+ abInit(&ab);
+ if (old_rows-rpos > 0) {
+ lndebug("go down %d", old_rows-rpos);
+ snprintf(seq,64,"\x1b[%dB", old_rows-rpos);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Now for every row clear it, go up. */
+ for (j = 0; j < old_rows-1; j++) {
+ lndebug("clear+up");
+ snprintf(seq,64,"\r\x1b[0K\x1b[1A");
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Clean the top line. */
+ lndebug("clear");
+ snprintf(seq,64,"\r\x1b[0K");
+ abAppend(&ab,seq,strlen(seq));
+
+ /* Write the prompt and the current buffer content */
+ abAppend(&ab,l->prompt,strlen(l->prompt));
+ abAppend(&ab,l->buf,l->len);
+
+ /* Show hits if any. */
+ refreshShowHints(&ab,l,plen);
+
+ /* If we are at the very end of the screen with our prompt, we need to
+ * emit a newline and move the prompt to the first column. */
+ if (l->pos &&
+ l->pos == l->len &&
+ (l->pos+plen) % l->cols == 0)
+ {
+ lndebug("<newline>");
+ abAppend(&ab,"\n",1);
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+ rows++;
+ if (rows > (int)l->maxrows) l->maxrows = rows;
+ }
+
+ /* Move cursor to right position. */
+ rpos2 = (plen+l->pos+l->cols)/l->cols; /* current cursor relative row. */
+ lndebug("rpos2 %d", rpos2);
+
+ /* Go up till we reach the expected positon. */
+ if (rows-rpos2 > 0) {
+ lndebug("go-up %d", rows-rpos2);
+ snprintf(seq,64,"\x1b[%dA", rows-rpos2);
+ abAppend(&ab,seq,strlen(seq));
+ }
+
+ /* Set column. */
+ col = (plen+(int)l->pos) % (int)l->cols;
+ lndebug("set col %d", 1+col);
+ if (col)
+ snprintf(seq,64,"\r\x1b[%dC", col);
+ else
+ snprintf(seq,64,"\r");
+ abAppend(&ab,seq,strlen(seq));
+
+ lndebug("\n");
+ l->oldpos = l->pos;
+
+ if (write(fd,ab.b,ab.len) == -1) {} /* Can't recover from write error. */
+ abFree(&ab);
+}
+
+/* Calls the two low level functions refreshSingleLine() or
+ * refreshMultiLine() according to the selected mode. */
+static void refreshLine(struct linenoiseState *l) {
+ if (mlmode)
+ refreshMultiLine(l);
+ else
+ refreshSingleLine(l);
+}
+
+/* Insert the character 'c' at cursor current position.
+ *
+ * On error writing to the terminal -1 is returned, otherwise 0. */
+int linenoiseEditInsert(struct linenoiseState *l, char c) {
+ if (l->len < l->buflen) {
+ if (l->len == l->pos) {
+ l->buf[l->pos] = c;
+ l->pos++;
+ l->len++;
+ l->buf[l->len] = '\0';
+ if ((!mlmode && l->plen+l->len < l->cols && !hintsCallback)) {
+ /* Avoid a full update of the line in the
+ * trivial case. */
+ if (write(l->ofd,&c,1) == -1) return -1;
+ } else {
+ refreshLine(l);
+ }
+ } else {
+ memmove(l->buf+l->pos+1,l->buf+l->pos,l->len-l->pos);
+ l->buf[l->pos] = c;
+ l->len++;
+ l->pos++;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+ }
+ return 0;
+}
+
+/* Move cursor on the left. */
+void linenoiseEditMoveLeft(struct linenoiseState *l) {
+ if (l->pos > 0) {
+ l->pos--;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor on the right. */
+void linenoiseEditMoveRight(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos++;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the start of the line. */
+void linenoiseEditMoveHome(struct linenoiseState *l) {
+ if (l->pos != 0) {
+ l->pos = 0;
+ refreshLine(l);
+ }
+}
+
+/* Move cursor to the end of the line. */
+void linenoiseEditMoveEnd(struct linenoiseState *l) {
+ if (l->pos != l->len) {
+ l->pos = l->len;
+ refreshLine(l);
+ }
+}
+
+/* Substitute the currently edited line with the next or previous history
+ * entry as specified by 'dir'. */
+#define LINENOISE_HISTORY_NEXT 0
+#define LINENOISE_HISTORY_PREV 1
+void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) {
+ if (history_len > 1) {
+ /* Update the current history entry before to
+ * overwrite it with the next one. */
+ free(history[history_len - 1 - l->history_index]);
+ history[history_len - 1 - l->history_index] = strdup(l->buf);
+ /* Show the new entry */
+ l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1;
+ if (l->history_index < 0) {
+ l->history_index = 0;
+ return;
+ } else if (l->history_index >= history_len) {
+ l->history_index = history_len-1;
+ return;
+ }
+ strncpy(l->buf,history[history_len - 1 - l->history_index],l->buflen);
+ l->buf[l->buflen-1] = '\0';
+ l->len = l->pos = strlen(l->buf);
+ refreshLine(l);
+ }
+}
+
+/* Delete the character at the right of the cursor without altering the cursor
+ * position. Basically this is what happens with the "Delete" keyboard key. */
+void linenoiseEditDelete(struct linenoiseState *l) {
+ if (l->len > 0 && l->pos < l->len) {
+ memmove(l->buf+l->pos,l->buf+l->pos+1,l->len-l->pos-1);
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Backspace implementation. */
+void linenoiseEditBackspace(struct linenoiseState *l) {
+ if (l->pos > 0 && l->len > 0) {
+ memmove(l->buf+l->pos-1,l->buf+l->pos,l->len-l->pos);
+ l->pos--;
+ l->len--;
+ l->buf[l->len] = '\0';
+ refreshLine(l);
+ }
+}
+
+/* Delete the previosu word, maintaining the cursor at the start of the
+ * current word. */
+void linenoiseEditDeletePrevWord(struct linenoiseState *l) {
+ size_t old_pos = l->pos;
+ size_t diff;
+
+ while (l->pos > 0 && l->buf[l->pos-1] == ' ')
+ l->pos--;
+ while (l->pos > 0 && l->buf[l->pos-1] != ' ')
+ l->pos--;
+ diff = old_pos - l->pos;
+ memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1);
+ l->len -= diff;
+ refreshLine(l);
+}
+
+/* This function is the core of the line editing capability of linenoise.
+ * It expects 'fd' to be already in "raw mode" so that every key pressed
+ * will be returned ASAP to read().
+ *
+ * The resulting string is put into 'buf' when the user type enter, or
+ * when ctrl+d is typed.
+ *
+ * The function returns the length of the current buffer. */
+static int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, size_t buflen, const char *prompt)
+{
+ struct linenoiseState l;
+
+ /* Populate the linenoise state that we pass to functions implementing
+ * specific editing functionalities. */
+ l.ifd = stdin_fd;
+ l.ofd = stdout_fd;
+ l.buf = buf;
+ l.buflen = buflen;
+ l.prompt = prompt;
+ l.plen = strlen(prompt);
+ l.oldpos = l.pos = 0;
+ l.len = 0;
+ l.cols = getColumns(stdin_fd, stdout_fd);
+ l.maxrows = 0;
+ l.history_index = 0;
+
+ /* Buffer starts empty. */
+ l.buf[0] = '\0';
+ l.buflen--; /* Make sure there is always space for the nulterm */
+
+ /* The latest history entry is always our current buffer, that
+ * initially is just an empty string. */
+ linenoiseHistoryAdd("");
+
+ if (write(l.ofd,prompt,l.plen) == -1) return -1;
+ while(1) {
+ char c;
+ int nread;
+ char seq[3];
+
+ nread = read(l.ifd,&c,1);
+ if (nread <= 0) return l.len;
+
+ /* Only autocomplete when the callback is set. It returns < 0 when
+ * there was an error reading from fd. Otherwise it will return the
+ * character that should be handled next. */
+ if (c == 9 && completionCallback != NULL) {
+ c = completeLine(&l);
+ /* Return on errors */
+ if (c < 0) return l.len;
+ /* Read next character when 0 */
+ if (c == 0) continue;
+ }
+
+ switch(c) {
+ case ENTER: /* enter */
+ history_len--;
+ free(history[history_len]);
+ if (mlmode) linenoiseEditMoveEnd(&l);
+ if (hintsCallback) {
+ /* Force a refresh without hints to leave the previous
+ * line as the user typed it after a newline. */
+ linenoiseHintsCallback *hc = hintsCallback;
+ hintsCallback = NULL;
+ refreshLine(&l);
+ hintsCallback = hc;
+ }
+ return (int)l.len;
+ case CTRL_C: /* ctrl-c */
+ errno = EAGAIN;
+ return -1;
+ case BACKSPACE: /* backspace */
+ case 8: /* ctrl-h */
+ linenoiseEditBackspace(&l);
+ break;
+ case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the
+ line is empty, act as end-of-file. */
+ if (l.len > 0) {
+ linenoiseEditDelete(&l);
+ } else {
+ history_len--;
+ free(history[history_len]);
+ return -1;
+ }
+ break;
+ case CTRL_T: /* ctrl-t, swaps current character with previous. */
+ if (l.pos > 0 && l.pos < l.len) {
+ int aux = buf[l.pos-1];
+ buf[l.pos-1] = buf[l.pos];
+ buf[l.pos] = aux;
+ if (l.pos != l.len-1) l.pos++;
+ refreshLine(&l);
+ }
+ break;
+ case CTRL_B: /* ctrl-b */
+ linenoiseEditMoveLeft(&l);
+ break;
+ case CTRL_F: /* ctrl-f */
+ linenoiseEditMoveRight(&l);
+ break;
+ case CTRL_P: /* ctrl-p */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
+ break;
+ case CTRL_N: /* ctrl-n */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
+ break;
+ case ESC: /* escape sequence */
+ /* Read the next two bytes representing the escape sequence.
+ * Use two calls to handle slow terminals returning the two
+ * chars at different times. */
+ if (read(l.ifd,seq,1) == -1) break;
+ if (read(l.ifd,seq+1,1) == -1) break;
+
+ /* ESC [ sequences. */
+ if (seq[0] == '[') {
+ if (seq[1] >= '0' && seq[1] <= '9') {
+ /* Extended escape, read additional byte. */
+ if (read(l.ifd,seq+2,1) == -1) break;
+ if (seq[2] == '~') {
+ switch(seq[1]) {
+ case '3': /* Delete key. */
+ linenoiseEditDelete(&l);
+ break;
+ }
+ }
+ } else {
+ switch(seq[1]) {
+ case 'A': /* Up */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV);
+ break;
+ case 'B': /* Down */
+ linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT);
+ break;
+ case 'C': /* Right */
+ linenoiseEditMoveRight(&l);
+ break;
+ case 'D': /* Left */
+ linenoiseEditMoveLeft(&l);
+ break;
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&l);
+ break;
+ }
+ }
+ }
+
+ /* ESC O sequences. */
+ else if (seq[0] == 'O') {
+ switch(seq[1]) {
+ case 'H': /* Home */
+ linenoiseEditMoveHome(&l);
+ break;
+ case 'F': /* End*/
+ linenoiseEditMoveEnd(&l);
+ break;
+ }
+ }
+ break;
+ default:
+ if (linenoiseEditInsert(&l,c)) return -1;
+ break;
+ case CTRL_U: /* Ctrl+u, delete the whole line. */
+ buf[0] = '\0';
+ l.pos = l.len = 0;
+ refreshLine(&l);
+ break;
+ case CTRL_K: /* Ctrl+k, delete from current to end of line. */
+ buf[l.pos] = '\0';
+ l.len = l.pos;
+ refreshLine(&l);
+ break;
+ case CTRL_A: /* Ctrl+a, go to the start of the line */
+ linenoiseEditMoveHome(&l);
+ break;
+ case CTRL_E: /* ctrl+e, go to the end of the line */
+ linenoiseEditMoveEnd(&l);
+ break;
+ case CTRL_L: /* ctrl+l, clear screen */
+ linenoiseClearScreen();
+ refreshLine(&l);
+ break;
+ case CTRL_W: /* ctrl+w, delete previous word */
+ linenoiseEditDeletePrevWord(&l);
+ break;
+ }
+ }
+ return l.len;
+}
+
+/* This special mode is used by linenoise in order to print scan codes
+ * on screen for debugging / development purposes. It is implemented
+ * by the linenoise_example program using the --keycodes option. */
+void linenoisePrintKeyCodes(void) {
+ char quit[4];
+
+ printf("Linenoise key codes debugging mode.\n"
+ "Press keys to see scan codes. Type 'quit' at any time to exit.\n");
+ if (enableRawMode(STDIN_FILENO) == -1) return;
+ memset(quit,' ',4);
+ while(1) {
+ char c;
+ int nread;
+
+ nread = read(STDIN_FILENO,&c,1);
+ if (nread <= 0) continue;
+ memmove(quit,quit+1,sizeof(quit)-1); /* shift string to left. */
+ quit[sizeof(quit)-1] = c; /* Insert current char on the right. */
+ if (memcmp(quit,"quit",sizeof(quit)) == 0) break;
+
+ printf("'%c' %02x (%d) (type quit to exit)\n",
+ isprint(c) ? c : '?', (int)c, (int)c);
+ printf("\r"); /* Go left edge manually, we are in raw mode. */
+ fflush(stdout);
+ }
+ disableRawMode(STDIN_FILENO);
+}
+
+/* This function calls the line editing function linenoiseEdit() using
+ * the STDIN file descriptor set in raw mode. */
+static int linenoiseRaw(char *buf, size_t buflen, const char *prompt) {
+ int count;
+
+ if (buflen == 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (enableRawMode(STDIN_FILENO) == -1) return -1;
+ count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, buflen, prompt);
+ disableRawMode(STDIN_FILENO);
+ printf("\n");
+ return count;
+}
+
+/* This function is called when linenoise() is called with the standard
+ * input file descriptor not attached to a TTY. So for example when the
+ * program using linenoise is called in pipe or with a file redirected
+ * to its standard input. In this case, we want to be able to return the
+ * line regardless of its length (by default we are limited to 4k). */
+static char *linenoiseNoTTY(void) {
+ char *line = NULL;
+ size_t len = 0, maxlen = 0;
+
+ while(1) {
+ if (len == maxlen) {
+ if (maxlen == 0) maxlen = 16;
+ maxlen *= 2;
+ char *oldval = line;
+ line = realloc(line,maxlen);
+ if (line == NULL) {
+ if (oldval) free(oldval);
+ return NULL;
+ }
+ }
+ int c = fgetc(stdin);
+ if (c == EOF || c == '\n') {
+ if (c == EOF && len == 0) {
+ free(line);
+ return NULL;
+ } else {
+ line[len] = '\0';
+ return line;
+ }
+ } else {
+ line[len] = c;
+ len++;
+ }
+ }
+}
+
+/* The high level function that is the main API of the linenoise library.
+ * This function checks if the terminal has basic capabilities, just checking
+ * for a blacklist of stupid terminals, and later either calls the line
+ * editing function or uses dummy fgets() so that you will be able to type
+ * something even in the most desperate of the conditions. */
+char *linenoise(const char *prompt) {
+ char buf[LINENOISE_MAX_LINE];
+ int count;
+
+ if (!isatty(STDIN_FILENO)) {
+ /* Not a tty: read from file / pipe. In this mode we don't want any
+ * limit to the line size, so we call a function to handle that. */
+ return linenoiseNoTTY();
+ } else if (isUnsupportedTerm()) {
+ size_t len;
+
+ printf("%s",prompt);
+ fflush(stdout);
+ if (fgets(buf,LINENOISE_MAX_LINE,stdin) == NULL) return NULL;
+ len = strlen(buf);
+ while(len && (buf[len-1] == '\n' || buf[len-1] == '\r')) {
+ len--;
+ buf[len] = '\0';
+ }
+ return strdup(buf);
+ } else {
+ count = linenoiseRaw(buf,LINENOISE_MAX_LINE,prompt);
+ if (count == -1) return NULL;
+ return strdup(buf);
+ }
+}
+
+/* This is just a wrapper the user may want to call in order to make sure
+ * the linenoise returned buffer is freed with the same allocator it was
+ * created with. Useful when the main program is using an alternative
+ * allocator. */
+void linenoiseFree(void *ptr) {
+ free(ptr);
+}
+
+/* ================================ History ================================= */
+
+/* Free the history, but does not reset it. Only used when we have to
+ * exit() to avoid memory leaks are reported by valgrind & co. */
+static void freeHistory(void) {
+ if (history) {
+ int j;
+
+ for (j = 0; j < history_len; j++)
+ free(history[j]);
+ free(history);
+ }
+}
+
+/* At exit we'll try to fix the terminal to the initial conditions. */
+static void linenoiseAtExit(void) {
+ disableRawMode(STDIN_FILENO);
+ freeHistory();
+}
+
+/* This is the API call to add a new entry in the linenoise history.
+ * It uses a fixed array of char pointers that are shifted (memmoved)
+ * when the history max length is reached in order to remove the older
+ * entry and make room for the new one, so it is not exactly suitable for huge
+ * histories, but will work well for a few hundred of entries.
+ *
+ * Using a circular buffer is smarter, but a bit more complex to handle. */
+int linenoiseHistoryAdd(const char *line) {
+ char *linecopy;
+
+ if (history_max_len == 0) return 0;
+
+ /* Initialization on first call. */
+ if (history == NULL) {
+ history = malloc(sizeof(char*)*history_max_len);
+ if (history == NULL) return 0;
+ memset(history,0,(sizeof(char*)*history_max_len));
+ }
+
+ /* Don't add duplicated lines. */
+ if (history_len && !strcmp(history[history_len-1], line)) return 0;
+
+ /* Add an heap allocated copy of the line in the history.
+ * If we reached the max length, remove the older line. */
+ linecopy = strdup(line);
+ if (!linecopy) return 0;
+ if (history_len == history_max_len) {
+ free(history[0]);
+ memmove(history,history+1,sizeof(char*)*(history_max_len-1));
+ history_len--;
+ }
+ history[history_len] = linecopy;
+ history_len++;
+ return 1;
+}
+
+/* Set the maximum length for the history. This function can be called even
+ * if there is already some history, the function will make sure to retain
+ * just the latest 'len' elements if the new history length value is smaller
+ * than the amount of items already inside the history. */
+int linenoiseHistorySetMaxLen(int len) {
+ char **new;
+
+ if (len < 1) return 0;
+ if (history) {
+ int tocopy = history_len;
+
+ new = malloc(sizeof(char*)*len);
+ if (new == NULL) return 0;
+
+ /* If we can't copy everything, free the elements we'll not use. */
+ if (len < tocopy) {
+ int j;
+
+ for (j = 0; j < tocopy-len; j++) free(history[j]);
+ tocopy = len;
+ }
+ memset(new,0,sizeof(char*)*len);
+ memcpy(new,history+(history_len-tocopy), sizeof(char*)*tocopy);
+ free(history);
+ history = new;
+ }
+ history_max_len = len;
+ if (history_len > history_max_len)
+ history_len = history_max_len;
+ return 1;
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int linenoiseHistorySave(const char *filename) {
+ mode_t old_umask = umask(S_IXUSR|S_IRWXG|S_IRWXO);
+ FILE *fp;
+ int j;
+
+ fp = fopen(filename,"w");
+ umask(old_umask);
+ if (fp == NULL) return -1;
+ chmod(filename,S_IRUSR|S_IWUSR);
+ for (j = 0; j < history_len; j++)
+ fprintf(fp,"%s\n",history[j]);
+ fclose(fp);
+ return 0;
+}
+
+/* Load the history from the specified file. If the file does not exist
+ * zero is returned and no operation is performed.
+ *
+ * If the file exists and the operation succeeded 0 is returned, otherwise
+ * on error -1 is returned. */
+int linenoiseHistoryLoad(const char *filename) {
+ FILE *fp = fopen(filename,"r");
+ char buf[LINENOISE_MAX_LINE];
+
+ if (fp == NULL) return -1;
+
+ while (fgets(buf,LINENOISE_MAX_LINE,fp) != NULL) {
+ char *p;
+
+ p = strchr(buf,'\r');
+ if (!p) p = strchr(buf,'\n');
+ if (p) *p = '\0';
+ linenoiseHistoryAdd(buf);
+ }
+ fclose(fp);
+ return 0;
+}
diff --git a/linenoise/linenoise.h b/linenoise/linenoise.h
new file mode 100644
index 0000000..ed20232
--- /dev/null
+++ b/linenoise/linenoise.h
@@ -0,0 +1,73 @@
+/* linenoise.h -- VERSION 1.0
+ *
+ * Guerrilla line editing library against the idea that a line editing lib
+ * needs to be 20,000 lines of C code.
+ *
+ * See linenoise.c for more information.
+ *
+ * ------------------------------------------------------------------------
+ *
+ * Copyright (c) 2010-2014, Salvatore Sanfilippo <antirez at gmail dot com>
+ * Copyright (c) 2010-2013, Pieter Noordhuis <pcnoordhuis at gmail dot com>
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
+ * HOLDER 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.
+ */
+
+#ifndef __LINENOISE_H
+#define __LINENOISE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct linenoiseCompletions {
+ size_t len;
+ char **cvec;
+} linenoiseCompletions;
+
+typedef void(linenoiseCompletionCallback)(const char *, linenoiseCompletions *);
+typedef char*(linenoiseHintsCallback)(const char *, int *color, int *bold);
+typedef void(linenoiseFreeHintsCallback)(void *);
+void linenoiseSetCompletionCallback(linenoiseCompletionCallback *);
+void linenoiseSetHintsCallback(linenoiseHintsCallback *);
+void linenoiseSetFreeHintsCallback(linenoiseFreeHintsCallback *);
+void linenoiseAddCompletion(linenoiseCompletions *, const char *);
+
+char *linenoise(const char *prompt);
+void linenoiseFree(void *ptr);
+int linenoiseHistoryAdd(const char *line);
+int linenoiseHistorySetMaxLen(int len);
+int linenoiseHistorySave(const char *filename);
+int linenoiseHistoryLoad(const char *filename);
+void linenoiseClearScreen(void);
+void linenoiseSetMultiLine(int ml);
+void linenoisePrintKeyCodes(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __LINENOISE_H */
diff --git a/meson.build b/meson.build
index 96e7c7b..cf79757 100644
--- a/meson.build
+++ b/meson.build
@@ -19,29 +19,39 @@ add_project_arguments('-DD2TK_DATA_DIR="'+pdatadir+'"', language : 'c')
cc = meson.get_compiler('c')
m_dep = cc.find_library('m')
-thread_dep = dependency('threads')
+util_dep = cc.find_library('util', required : false)
freetype_dep = dependency('freetype2', version : '>=18.0.0',
- static : static_link, required: false)
+ static : static_link, required : false)
pixman_dep = dependency('pixman-1', version : '>=0.34.0',
- static : static_link, required: false)
+ static : static_link, required : false)
cairo_dep = dependency('cairo', version : '>=1.14.0',
- static : static_link, required: false)
+ static : static_link, required : false)
cairo_xlib_dep = dependency('cairo-xlib', version : '>=1.14.0',
- static : static_link, required: false)
+ static : static_link, required : false)
evdev_dep = dependency('libevdev', version : '>=1.5.0',
- static : static_link, required: false)
+ static : static_link, required : false)
input_dep = dependency('libinput', version : '>=1.6.0',
- static : static_link, required: false)
+ static : static_link, required : false)
udev_dep = dependency('libudev', version : '>=220',
- static : static_link, required: false)
+ static : static_link, required : false)
+glew_dep = dependency('glew', version : '>=2.1.0',
+ static : static_link, required : false)
+vterm_dep = dependency('vterm', version : '>=0.1',
+ static : static_link, required : false)
+
+if not glew_dep.found()
+ # use embedded glew
+ glew_dep = declare_dependency(
+ include_directories : include_directories('glew-2.1.0'),
+ sources : join_paths('glew-2.1.0', 'glew.c'))
+endif
-deps = [m_dep, evdev_dep]
+deps = [m_dep, evdev_dep, vterm_dep]
links = []
pugl_inc = include_directories('pugl')
nanovg_inc = include_directories('nanovg/src')
-glew_inc = include_directories('glew-2.1.0')
-inc_dir = [pugl_inc, nanovg_inc, glew_inc]
+inc_dir = [pugl_inc, nanovg_inc]
rawvers = run_command('cat', 'VERSION').stdout().strip()
version = rawvers.split('.')
@@ -58,11 +68,49 @@ if build_debug
endif
lib_srcs = [
- join_paths('src', 'mum.c'),
+ join_paths('src', 'hash.c'),
join_paths('src', 'core.c'),
- join_paths('src', 'base.c')
+ join_paths('src', 'base.c'),
+ join_paths('src', 'base_table.c'),
+ join_paths('src', 'base_frame.c'),
+ join_paths('src', 'base_layout.c'),
+ join_paths('src', 'base_scrollbar.c'),
+ join_paths('src', 'base_pane.c'),
+ join_paths('src', 'base_cursor.c'),
+ join_paths('src', 'base_button.c'),
+ join_paths('src', 'base_image.c'),
+ join_paths('src', 'base_bitmap.c'),
+ join_paths('src', 'base_custom.c'),
+ join_paths('src', 'base_meter.c'),
+ join_paths('src', 'base_combo.c'),
+ join_paths('src', 'base_textfield.c'),
+ join_paths('src', 'base_label.c'),
+ join_paths('src', 'base_link.c'),
+ join_paths('src', 'base_dial.c'),
+ join_paths('src', 'base_flowmatrix.c')
]
+if vterm_dep.found()
+ conf_data.set('D2TK_PTY', 1)
+ lib_srcs += join_paths('src', 'base_pty.c')
+ lib_srcs += join_paths('linenoise', 'linenoise.c')
+ deps += util_dep
+else
+ conf_data.set('D2TK_PTY', 0)
+endif
+
+if evdev_dep.found()
+ conf_data.set('D2TK_EVDEV', 1)
+else
+ conf_data.set('D2TK_EVDEV', 0)
+endif
+
+if input_dep.found() and input_dep.version().version_compare('>=1.15.0')
+ conf_data.set('D2TK_INPUT_1_15', 1)
+else
+ conf_data.set('D2TK_INPUT_1_15', 0)
+endif
+
bin_srcs = [
join_paths('example', 'example.c')
]
@@ -82,8 +130,7 @@ pugl_bin_srcs = [
nanovg_srcs = [
join_paths('nanovg', 'src', 'nanovg.c'),
- join_paths('src', 'backend_nanovg.c'),
- join_paths('glew-2.1.0', 'glew.c')
+ join_paths('src', 'backend_nanovg.c')
]
cairo_srcs = [
@@ -149,7 +196,7 @@ if freetype_dep.found() and pixman_dep.found() and cairo_dep.found() and cairo_x
dependencies: d2tk_cairo,
install : false)
- if input_dep.found() and udev_dep.found()
+ if input_dep.found() and udev_dep.found() and evdev_dep.found()
d2tk_fbdev = declare_dependency(
include_directories : inc_dir,
dependencies : [deps, freetype_dep, pixman_dep, cairo_dep, input_dep, udev_dep],
@@ -166,7 +213,7 @@ endif
d2tk_nanovg = declare_dependency(
include_directories : inc_dir,
- dependencies : deps,
+ dependencies : [deps, glew_dep],
link_args : links,
sources : [lib_srcs, nanovg_srcs, pugl_srcs, pugl_gl_srcs])
@@ -177,6 +224,12 @@ executable('d2tk.nanovg', [bin_srcs, pugl_bin_srcs],
install : false)
configure_file(
+ input : join_paths('d2tk', 'config.h.in'),
+ output : 'config.h',
+ configuration : conf_data,
+ install : false)
+
+configure_file(
input : join_paths('ttf', 'FiraSans-Bold.ttf'),
output : 'FiraSans-Bold.ttf',
copy : true,
@@ -189,6 +242,30 @@ configure_file(
install : false)
configure_file(
+ input : join_paths('ttf', 'FiraCode-Bold.ttf'),
+ output : 'FiraCode-Bold.ttf',
+ copy : true,
+ install : false)
+
+configure_file(
+ input : join_paths('ttf', 'FiraCode-Light.ttf'),
+ output : 'FiraCode-Light.ttf',
+ copy : true,
+ install : false)
+
+configure_file(
+ input : join_paths('ttf', 'FiraCode-Medium.ttf'),
+ output : 'FiraCode-Medium.ttf',
+ copy : true,
+ install : false)
+
+configure_file(
+ input : join_paths('ttf', 'FiraCode-Regular.ttf'),
+ output : 'FiraCode-Regular.ttf',
+ copy : true,
+ install : false)
+
+configure_file(
input : join_paths('example', 'libre-arrow-circle-right.png'),
output : 'libre-arrow-circle-right.png',
copy : true,
diff --git a/pugl/pugl/detail/x11.c b/pugl/pugl/detail/x11.c
index c891604..cc84bc0 100644
--- a/pugl/pugl/detail/x11.c
+++ b/pugl/pugl/detail/x11.c
@@ -599,29 +599,11 @@ merge_expose_events(PuglEvent* dst, const PuglEvent* src)
}
}
-static void
-sendRedisplayEvent(PuglView* view)
-{
- XExposeEvent ev = { Expose, 0, True, view->impl->display, view->impl->win,
- 0, 0, (int)view->frame.width, (int)view->frame.height,
- 0 };
-
- XSendEvent(view->impl->display, view->impl->win, False, 0, (XEvent*)&ev);
-}
-
PUGL_API PuglStatus
puglDispatchEvents(PuglWorld* world)
{
const PuglX11Atoms* const atoms = &world->impl->atoms;
- // Send expose events for any views with pending redisplays
- for (size_t i = 0; i < world->numViews; ++i) {
- if (world->views[i]->redisplay) {
- sendRedisplayEvent(world->views[i]);
- world->views[i]->redisplay = false;
- }
- }
-
// Flush just once at the start to fill event queue
Display* display = world->impl->display;
XFlush(display);
@@ -722,6 +704,12 @@ puglDispatchEvents(PuglWorld* world)
PuglEvent* const configure = &view->impl->pendingConfigure;
PuglEvent* const expose = &view->impl->pendingExpose;
+ if (view->redisplay)
+ {
+ expose->type = PUGL_EXPOSE;
+ view->redisplay = false;
+ }
+
if (configure->type || expose->type) {
const bool mustExpose = expose->type && expose->expose.count == 0;
puglEnterContext(view, mustExpose);
diff --git a/src/backend_cairo.c b/src/backend_cairo.c
index fa31168..14d3d81 100644
--- a/src/backend_cairo.c
+++ b/src/backend_cairo.c
@@ -151,7 +151,7 @@ d2tk_cairo_post(void *data, d2tk_core_t *core __attribute__((unused)),
cairo_surface_t *surf = cairo_image_surface_create_for_data(
(uint8_t *)pixels, CAIRO_FORMAT_ARGB32, w, h, w*sizeof(uint32_t));
- //FIXME reuse/update suface
+ //FIXME reuse/update surface
cairo_rectangle(ctx, 0, 0, w, h);
cairo_clip(ctx);
@@ -655,10 +655,14 @@ d2tk_cairo_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
+ char *img_path = NULL;
+ assert(asprintf(&img_path, "%s%s", backend->bundle_path, body->path) != -1);
+ assert(img_path);
+
int W, H, N;
stbi_set_unpremultiply_on_load(1);
stbi_convert_iphone_png_to_rgb(1);
- uint8_t *pixels = stbi_load(body->path, &W, &H, &N, 4);
+ uint8_t *pixels = stbi_load(img_path, &W, &H, &N, 4);
assert(pixels );
// bitswap and premultiply pixel data
diff --git a/src/backend_nanovg.c b/src/backend_nanovg.c
index 1086d94..247e4eb 100644
--- a/src/backend_nanovg.c
+++ b/src/backend_nanovg.c
@@ -729,7 +729,11 @@ d2tk_nanovg_process(void *data, d2tk_core_t *core, const d2tk_com_t *com,
if(!*sprite)
{
- *sprite = nvgCreateImage(ctx, body->path, NVG_IMAGE_GENERATE_MIPMAPS);
+ char *img_path = NULL;
+ assert(asprintf(&img_path, "%s%s", backend->bundle_path, body->path) != -1);
+ assert(img_path);
+
+ *sprite = nvgCreateImage(ctx, img_path, NVG_IMAGE_GENERATE_MIPMAPS);
}
const int img = *sprite;
diff --git a/src/base.c b/src/base.c
index 61d1887..63c11f7 100644
--- a/src/base.c
+++ b/src/base.c
@@ -20,202 +20,7 @@
#include <string.h>
#include <inttypes.h>
-#include <d2tk/base.h>
-#include <d2tk/hash.h>
-#include "core_internal.h"
-
-#define _D2TK_MAX_ATOM 0x1000
-#define _D2TK_MASK_ATOMS (_D2TK_MAX_ATOM - 1)
-
-typedef struct _d2tk_flip_t d2tk_flip_t;
-typedef struct _d2tk_atom_body_scroll_t d2tk_atom_body_scroll_t;
-typedef struct _d2tk_atom_body_pane_t d2tk_atom_body_pane_t;
-typedef struct _d2tk_atom_body_flow_t d2tk_atom_body_flow_t;
-typedef union _d2tk_atom_body_t d2tk_atom_body_t;
-typedef struct _d2tk_atom_t d2tk_atom_t;
-
-typedef enum _d2tk_atom_type_t {
- D2TK_ATOM_NONE,
- D2TK_ATOM_SCROLL,
- D2TK_ATOM_PANE,
- D2TK_ATOM_FLOW,
- D2TK_ATOM_FLOW_NODE,
- D2TK_ATOM_FLOW_ARC
-} d2tk_atom_type_t;
-
-struct _d2tk_flip_t {
- d2tk_id_t old;
- d2tk_id_t cur;
-};
-
-struct _d2tk_atom_body_scroll_t {
- float offset [2];
-};
-
-struct _d2tk_atom_body_pane_t {
- float fraction;
-};
-
-struct _d2tk_atom_body_flow_t {
- d2tk_coord_t x;
- d2tk_coord_t y;
- d2tk_coord_t lx;
- d2tk_coord_t ly;
- float exponent;
- d2tk_id_t src_id;
- d2tk_id_t dst_id;
-};
-
-union _d2tk_atom_body_t {
- d2tk_atom_body_scroll_t scroll;
- d2tk_atom_body_pane_t pane;
- d2tk_atom_body_flow_t flow;
-};
-
-struct _d2tk_atom_t {
- d2tk_id_t id;
- d2tk_atom_type_t type;
- d2tk_atom_body_t body;
-};
-
-struct _d2tk_base_t {
- d2tk_flip_t hotitem;
- d2tk_flip_t activeitem;
- d2tk_flip_t focusitem;
- d2tk_id_t lastitem;
-
- bool not_first_time;
-
- struct {
- d2tk_coord_t x;
- d2tk_coord_t y;
- d2tk_coord_t ox;
- d2tk_coord_t oy;
- d2tk_coord_t dx;
- d2tk_coord_t dy;
- d2tk_butmask_t mask;
- } mouse;
-
- struct {
- int32_t odx;
- int32_t ody;
- int32_t dx;
- int32_t dy;
- } scroll;
-
- struct {
- size_t nchars;
- utf8_int32_t chars [32];
- unsigned keymod;
- d2tk_keymask_t mask;
- d2tk_modmask_t mod;
- } keys;
-
- struct {
- char text_in [1024];
- char text_out [1024];
- } edit;
-
- const d2tk_style_t *style;
-
- bool again;
- bool clear_focus;
- bool focused;
-
- d2tk_core_t *core;
-
- d2tk_atom_t atoms [_D2TK_MAX_ATOM];
-};
-
-struct _d2tk_table_t {
- unsigned x;
- unsigned y;
- unsigned N;
- unsigned NM;
- unsigned k;
- unsigned x0;
- d2tk_rect_t rect;
-};
-
-struct _d2tk_frame_t {
- d2tk_rect_t rect;
-};
-
-struct _d2tk_layout_t {
- unsigned N;
- const d2tk_coord_t *frac;
- d2tk_flag_t flag;
- d2tk_coord_t dd;
- d2tk_coord_t rem;
- unsigned k;
- d2tk_rect_t rect;
-};
-
-struct _d2tk_scrollbar_t {
- d2tk_id_t id;
- d2tk_flag_t flags;
- int32_t max [2];
- int32_t num [2];
- d2tk_atom_body_t *atom_body;
- const d2tk_rect_t *rect;
- d2tk_rect_t sub;
-};
-
-struct _d2tk_flowmatrix_t {
- d2tk_base_t *base;
- d2tk_id_t id;
- const d2tk_rect_t *rect;
- d2tk_atom_body_t *atom_body;
- float scale;
- d2tk_coord_t cx;
- d2tk_coord_t cy;
- size_t ref;
- d2tk_coord_t w;
- d2tk_coord_t h;
- d2tk_coord_t dd;
- d2tk_coord_t r;
- d2tk_coord_t s;
- bool src_conn;
- bool dst_conn;
- d2tk_pos_t src_pos;
- d2tk_pos_t dst_pos;
-};
-
-struct _d2tk_flowmatrix_node_t {
- d2tk_flowmatrix_t *flowmatrix;
- d2tk_rect_t rect;
-};
-
-struct _d2tk_flowmatrix_arc_t {
- d2tk_flowmatrix_t *flowmatrix;
- unsigned x;
- unsigned y;
- unsigned N;
- unsigned M;
- unsigned NM;
- unsigned k;
- d2tk_coord_t c;
- d2tk_coord_t c_2;
- d2tk_coord_t c_4;
- d2tk_coord_t xo;
- d2tk_coord_t yo;
- d2tk_rect_t rect;
-};
-
-struct _d2tk_pane_t {
- d2tk_atom_body_t *atom_body;
- unsigned k;
- d2tk_rect_t rect [2];
-};
-
-const size_t d2tk_table_sz = sizeof(d2tk_table_t);
-const size_t d2tk_frame_sz = sizeof(d2tk_frame_t);
-const size_t d2tk_layout_sz = sizeof(d2tk_layout_t);
-const size_t d2tk_scrollbar_sz = sizeof(d2tk_scrollbar_t);
-const size_t d2tk_flowmatrix_sz = sizeof(d2tk_flowmatrix_t);
-const size_t d2tk_flowmatrix_node_sz = sizeof(d2tk_flowmatrix_node_t);
-const size_t d2tk_flowmatrix_arc_sz = sizeof(d2tk_flowmatrix_arc_t);
-const size_t d2tk_pane_sz = sizeof(d2tk_pane_t);
+#include "base_internal.h"
static inline d2tk_id_t
_d2tk_flip_get_cur(d2tk_flip_t *flip)
@@ -282,8 +87,9 @@ _d2tk_flip_clear_old(d2tk_flip_t *flip)
_d2tk_flip_set_old(flip, 0);
}
-static d2tk_atom_body_t *
-_d2tk_base_get_atom(d2tk_base_t *base, d2tk_id_t id, d2tk_atom_type_t type)
+void *
+_d2tk_base_get_atom(d2tk_base_t *base, d2tk_id_t id, d2tk_atom_type_t type,
+ d2tk_atom_event_t event)
{
for(unsigned i = 0, idx = (id + i*i) & _D2TK_MASK_ATOMS;
i < _D2TK_MAX_ATOM;
@@ -296,313 +102,70 @@ _d2tk_base_get_atom(d2tk_base_t *base, d2tk_id_t id, d2tk_atom_type_t type)
continue;
}
- d2tk_atom_body_t *body = &atom->body;
-
- if( (atom->id == 0) || (atom->type != type) ) // new atom or changed type
+ if( (atom->id == 0) || (atom->type != type) || !atom->body) // new atom or changed type
{
- memset(atom, 0x0, sizeof(d2tk_atom_t)); // clear atom and its body
-
atom->id = id;
atom->type = type;
- }
-
- return body;
- }
-
- return NULL; // no space left
-}
-
-D2TK_API d2tk_table_t *
-d2tk_table_begin(const d2tk_rect_t *rect, unsigned N, unsigned M,
- d2tk_flag_t flag, d2tk_table_t *tab)
-{
- if( (N == 0) || (M == 0) )
- {
- return NULL;
- }
-
- unsigned w;
- unsigned h;
-
- tab->x = 0;
- tab->y = 0;
- tab->k = 0;
- tab->x0 = rect->x;
-
- if(flag & D2TK_FLAG_TABLE_REL)
- {
- w = rect->w / N;
- h = rect->h / M;
- }
- else
- {
- w = N;
- h = M;
-
- N = rect->w / N;
- M = rect->h / M;
- }
-
- tab->N = N;
- tab->NM = N*M;
-
- tab->rect.x = rect->x;
- tab->rect.y = rect->y;
- tab->rect.w = w;
- tab->rect.h = h;
-
- return tab;
-}
-
-D2TK_API bool
-d2tk_table_not_end(d2tk_table_t *tab)
-{
- return tab && (tab->k < tab->NM);
-}
-
-D2TK_API d2tk_table_t *
-d2tk_table_next(d2tk_table_t *tab)
-{
- ++tab->k;
-
- if(++tab->x % tab->N)
- {
- tab->rect.x += tab->rect.w;
- }
- else // overflow
- {
- tab->x = 0;
- ++tab->y;
-
- tab->rect.x = tab->x0;
- tab->rect.y += tab->rect.h;
- }
-
- return tab;
-}
-
-D2TK_API unsigned
-d2tk_table_get_index(d2tk_table_t *tab)
-{
- return tab->k;
-}
-
-D2TK_API unsigned
-d2tk_table_get_index_x(d2tk_table_t *tab)
-{
- return tab->x;
-}
-
-D2TK_API unsigned
-d2tk_table_get_index_y(d2tk_table_t *tab)
-{
- return tab->y;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_table_get_rect(d2tk_table_t *tab)
-{
- return &tab->rect;
-}
-
-D2TK_API d2tk_frame_t *
-d2tk_frame_begin(d2tk_base_t *base, const d2tk_rect_t *rect,
- ssize_t lbl_len, const char *lbl, d2tk_frame_t *frm)
-{
- const bool has_lbl = lbl_len && lbl;
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
- d2tk_core_t *core = base->core;
- const d2tk_coord_t h = 17; //FIXME
-
- if(has_lbl && (lbl_len == -1) ) // zero-terminated string
- {
- lbl_len = strlen(lbl);
- }
-
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { lbl, lbl_len },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_rect_shrink(&frm->rect, rect, 2*style->padding);
-
- if(has_lbl)
- {
- frm->rect.y += h;
- frm->rect.h -= h;
- }
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_rect_t bnd_outer;
- d2tk_rect_shrink(&bnd_outer, rect, style->padding);
- d2tk_rect_t bnd_inner = bnd_outer;;
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- if(has_lbl)
- {
- bnd_inner.h = h;
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, &bnd_inner, style->rounding);
- d2tk_core_color(core, style->fill_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- bnd_inner.x += style->rounding;
- bnd_inner.w -= style->rounding*2;
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd_inner);
- d2tk_core_font_size(core, bnd_inner.h - 2*style->padding);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_text(core, &bnd_inner, lbl_len, lbl,
- D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
- d2tk_core_restore(core);
- }
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- return frm;
-}
+ atom->event = event;
-D2TK_API bool
-d2tk_frame_not_end(d2tk_frame_t *frm)
-{
- return frm ? true : false;
-}
-
-D2TK_API d2tk_frame_t *
-d2tk_frame_next(d2tk_frame_t *frm __attribute__((unused)))
-{
- return NULL;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_frame_get_rect(d2tk_frame_t *frm)
-{
- return &frm->rect;
-}
-
-D2TK_API d2tk_layout_t *
-d2tk_layout_begin(const d2tk_rect_t *rect, unsigned N, const d2tk_coord_t *frac,
- d2tk_flag_t flag, d2tk_layout_t *lay)
-{
- lay->N = N;
- lay->frac = frac;
- lay->flag = flag;
-
- unsigned tot = 0;
- unsigned missing = 0;
- for(unsigned i = 0; i < N; i++)
- {
- tot += frac[i];
-
- if(frac[i] == 0)
- {
- missing += 1;
- }
- }
-
- lay->k = 0;
- lay->rect.x = rect->x;
- lay->rect.y = rect->y;
-
- if(lay->flag & D2TK_FLAG_LAYOUT_Y)
- {
- if(lay->flag & D2TK_FLAG_LAYOUT_REL)
- {
- lay->dd = tot ? (rect->h / tot) : 0;
- }
- else
- {
- lay->dd = 1;
- }
+ size_t len;
+ switch(atom->type)
+ {
+ case D2TK_ATOM_SCROLL:
+ {
+ len = d2tk_atom_body_scroll_sz;
+ } break;
+ case D2TK_ATOM_PANE:
+ {
+ len = d2tk_atom_body_pane_sz;
+ } break;
+ case D2TK_ATOM_FLOW:
+ {
+ len = d2tk_atom_body_flow_sz;
+ } break;
+#if D2TK_PTY
+ case D2TK_ATOM_PTY:
+ {
+ len = d2tk_atom_body_pty_sz;
+ } break;
+#endif
+ case D2TK_ATOM_FLOW_NODE:
+ // fall-through
+ case D2TK_ATOM_FLOW_ARC:
+ // fall-through
+ default:
+ {
+ len = 0;
+ } break;
+ }
- lay->rem = missing ? (rect->h - tot) / missing : 0;
+ if(len == 0)
+ {
+ if(atom->event)
+ {
+ atom->event(D2TK_ATOM_EVENT_DEINIT, atom->body);
+ atom->event = NULL;
+ }
+ free(atom->body);
+ atom->body = 0;
+ }
+ else
+ {
+ void *body = realloc(atom->body, len);
+ if(!body)
+ {
+ return NULL;
+ }
- lay->rect.h = lay->frac[lay->k]
- ? lay->dd * lay->frac[lay->k]
- : lay->rem;
- lay->rect.w = rect->w;
- }
- else // D2TK_FLAG_LAYOUT_X
- {
- if(lay->flag & D2TK_FLAG_LAYOUT_REL)
- {
- lay->dd = tot ? (rect->w / tot) : 0;
- }
- else
- {
- lay->dd = 1;
+ memset(body, 0x0, len);
+ atom->body = body;
+ }
}
- lay->rem = missing ? (rect->w - tot) / missing : 0;
-
- lay->rect.w = lay->frac[lay->k]
- ? lay->dd * lay->frac[lay->k]
- : lay->rem;
- lay->rect.h = rect->h;
+ return atom->body;
}
- return lay;
-}
-
-D2TK_API bool
-d2tk_layout_not_end(d2tk_layout_t *lay)
-{
- return lay;
-}
-
-D2TK_API d2tk_layout_t *
-d2tk_layout_next(d2tk_layout_t *lay)
-{
- if(++lay->k >= lay->N)
- {
- return NULL;
- }
-
- if(lay->flag & D2TK_FLAG_LAYOUT_Y)
- {
- lay->rect.y += lay->rect.h;
- lay->rect.h = lay->frac[lay->k]
- ? lay->dd * lay->frac[lay->k]
- : lay->rem;
- }
- else // D2TK_FLAG_LAYOUT_X
- {
- lay->rect.x += lay->rect.w;
- lay->rect.w = lay->frac[lay->k]
- ? lay->dd * lay->frac[lay->k]
- : lay->rem;
- }
-
- return lay;
-}
-
-D2TK_API unsigned
-d2tk_layout_get_index(d2tk_layout_t *lay)
-{
- return lay->k;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_layout_get_rect(d2tk_layout_t *lay)
-{
- return &lay->rect;
+ return NULL; // no space left
}
D2TK_API void
@@ -773,6 +336,18 @@ d2tk_state_is_over(d2tk_state_t state)
}
D2TK_API bool
+d2tk_state_is_close(d2tk_state_t state)
+{
+ return (state & D2TK_STATE_CLOSE);
+}
+
+D2TK_API bool
+d2tk_state_is_bell(d2tk_state_t state)
+{
+ return (state & D2TK_STATE_BELL);
+}
+
+D2TK_API bool
d2tk_base_is_hit(d2tk_base_t *base, const d2tk_rect_t *rect)
{
if( (base->mouse.x < rect->x)
@@ -801,7 +376,7 @@ _d2tk_base_change_focus(d2tk_base_t *base)
strncpy(base->edit.text_out, base->edit.text_in, sizeof(base->edit.text_out));
}
-static inline void
+void
_d2tk_base_clear_chars(d2tk_base_t *base)
{
base->keys.nchars = 0;
@@ -832,7 +407,7 @@ d2tk_base_get_utf8(d2tk_base_t *base, ssize_t *len, const utf8_int32_t **utf8)
_d2tk_base_clear_chars(base);
}
-static inline d2tk_state_t
+d2tk_state_t
_d2tk_base_is_active_hot_vertical_scroll(d2tk_base_t *base)
{
d2tk_state_t state = D2TK_STATE_NONE;
@@ -856,7 +431,7 @@ _d2tk_base_is_active_hot_vertical_scroll(d2tk_base_t *base)
return state;
}
-static inline d2tk_state_t
+d2tk_state_t
_d2tk_base_is_active_hot_horizontal_scroll(d2tk_base_t *base)
{
d2tk_state_t state = D2TK_STATE_NONE;
@@ -1141,2828 +716,6 @@ d2tk_base_set_default_style(d2tk_base_t *base)
d2tk_base_set_style(base, NULL);
}
-D2TK_API d2tk_scrollbar_t *
-d2tk_scrollbar_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
- d2tk_flag_t flags, int32_t hmax, int32_t vmax, int32_t hnum, int32_t vnum,
- d2tk_scrollbar_t *scrollbar)
-{
- scrollbar->id = id;
- scrollbar->flags = flags;
- scrollbar->max[0] = hmax;
- scrollbar->max[1] = vmax;
- scrollbar->num[0] = hnum;
- scrollbar->num[1] = vnum;
- scrollbar->rect = rect;
- scrollbar->sub = *rect;
- scrollbar->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_SCROLL);
-
- const d2tk_coord_t s = 10; //FIXME
-
- if(flags & D2TK_FLAG_SCROLL_X)
- {
- scrollbar->sub.h -= s;
- }
-
- if(flags & D2TK_FLAG_SCROLL_Y)
- {
- scrollbar->sub.w -= s;
- }
-
- return scrollbar;
-}
-
-D2TK_API bool
-d2tk_scrollbar_not_end(d2tk_scrollbar_t *scrollbar)
-{
- return scrollbar ? true : false;
-}
-
-static void
-_d2tk_draw_scrollbar(d2tk_core_t *core, d2tk_state_t hstate, d2tk_state_t vstate,
- const d2tk_rect_t *hbar, const d2tk_rect_t *vbar, const d2tk_style_t *style,
- d2tk_flag_t flags)
-{
- const d2tk_hash_dict_t dict [] = {
- { &hstate, sizeof(d2tk_state_t) },
- { &vstate, sizeof(d2tk_state_t) },
- { hbar, sizeof(d2tk_rect_t) },
- { vbar, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &flags, sizeof(d2tk_flag_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- if(flags & D2TK_FLAG_SCROLL_X)
- {
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(hstate))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(hstate))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(hstate))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- const size_t ref = d2tk_core_bbox_push(core, true, hbar);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, hbar, style->rounding);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, hbar, style->rounding);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);\
- }
-
- if(flags & D2TK_FLAG_SCROLL_Y)
- {
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(vstate))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(vstate))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(vstate))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- const size_t ref = d2tk_core_bbox_push(core, true, vbar);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, vbar, style->rounding);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, vbar, style->rounding);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API d2tk_scrollbar_t *
-d2tk_scrollbar_next(d2tk_base_t *base, d2tk_scrollbar_t *scrollbar)
-{
- const d2tk_style_t *style = d2tk_base_get_style(base);
- const d2tk_coord_t s = 10; //FIXME
- const d2tk_coord_t s2 = s*2;
-
- const d2tk_id_t id = scrollbar->id;
- d2tk_flag_t flags = scrollbar->flags;
- const int32_t *max = scrollbar->max;
- const int32_t *num = scrollbar->num;
- const d2tk_rect_t *rect = scrollbar->rect;
- float *scroll = scrollbar->atom_body->scroll.offset;
-
- const int32_t rel_max [2] = {
- max[0] - num[0],
- max[1] - num[1]
- };
- d2tk_rect_t hbar = {
- .x = rect->x,
- .y = rect->y + rect->h - s,
- .w = rect->w,
- .h = s
- };
- d2tk_rect_t vbar = {
- .x = rect->x + rect->w - s,
- .y = rect->y,
- .w = s,
- .h = rect->h
- };
- d2tk_rect_t sub = *rect;
- d2tk_state_t hstate = D2TK_STATE_NONE;
- d2tk_state_t vstate = D2TK_STATE_NONE;
-
- const d2tk_id_t hid = (1 << 24) | id;
- const d2tk_id_t vid = (2 << 24) | id;
-
- if(max[0] < num[0])
- {
- flags &= ~D2TK_FLAG_SCROLL_X;
- }
-
- if(max[1] < num[1])
- {
- flags &= ~D2TK_FLAG_SCROLL_Y;
- }
-
- if(flags & D2TK_FLAG_SCROLL_X)
- {
- sub.h -= s;
-
- hstate |= d2tk_base_is_active_hot(base, hid, &hbar, D2TK_FLAG_SCROLL_X);
- }
-
- if(flags & D2TK_FLAG_SCROLL_Y)
- {
- sub.w -= s;
-
- vstate |= d2tk_base_is_active_hot(base, vid, &vbar, D2TK_FLAG_SCROLL_Y);
- }
-
- if(d2tk_base_is_hit(base, &sub))
- {
- if(flags & D2TK_FLAG_SCROLL_X)
- {
- hstate |= _d2tk_base_is_active_hot_horizontal_scroll(base);
- }
-
- if(flags & D2TK_FLAG_SCROLL_Y)
- {
- vstate |= _d2tk_base_is_active_hot_vertical_scroll(base);
- }
- }
-
- const float old_scroll [2] = {
- scroll[0],
- scroll[1]
- };
-
- if(flags & D2TK_FLAG_SCROLL_X)
- {
- int32_t dw = hbar.w * num[0] / max[0];
- d2tk_clip_int32(s2, &dw, dw);
- const float w = (float)(hbar.w - dw) / rel_max[0];
-
- if(d2tk_state_is_scroll_right(hstate))
- {
- scroll[0] += base->scroll.odx;
- }
- else if(d2tk_state_is_scroll_left(hstate))
- {
- scroll[0] += base->scroll.odx;
- }
- else if(d2tk_state_is_motion(hstate))
- {
- const float adx = base->mouse.dx;
-
- scroll[0] += adx / w;
- }
-
- // always do clipping, as max may have changed in due course
- d2tk_clip_float(0, &scroll[0], rel_max[0]);
-
- hbar.w = dw;
- hbar.x += scroll[0]*w;
- }
-
- if(flags & D2TK_FLAG_SCROLL_Y)
- {
- int32_t dh = vbar.h * num[1] / max[1];
- d2tk_clip_int32(s2, &dh, dh);
- const float h = (float)(vbar.h - dh) / rel_max[1];
-
- if(d2tk_state_is_scroll_down(vstate))
- {
- scroll[1] -= base->scroll.ody;
- }
- else if(d2tk_state_is_scroll_up(vstate))
- {
- scroll[1] -= base->scroll.ody;
- }
- else if(d2tk_state_is_motion(vstate))
- {
- const float ady = base->mouse.dy;
-
- scroll[1] += ady / h;
- }
-
- // always do clipping, as max may have changed in due course
- d2tk_clip_float(0, &scroll[1], rel_max[1]);
-
- vbar.h = dh;
- vbar.y += scroll[1]*h;
- }
-
- if( (old_scroll[0] != scroll[0]) || (old_scroll[1] != scroll[1]) )
- {
- d2tk_base_set_again(base);
- }
-
- d2tk_core_t *core = base->core;
-
- _d2tk_draw_scrollbar(core, hstate, vstate, &hbar, &vbar, style, flags);
-
- //return state; //FIXME
- return NULL;
-}
-
-D2TK_API float
-d2tk_scrollbar_get_offset_y(d2tk_scrollbar_t *scrollbar)
-{
- return scrollbar->atom_body->scroll.offset[1];
-}
-
-D2TK_API float
-d2tk_scrollbar_get_offset_x(d2tk_scrollbar_t *scrollbar)
-{
- return scrollbar->atom_body->scroll.offset[0];
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_scrollbar_get_rect(d2tk_scrollbar_t *scrollbar)
-{
- return &scrollbar->sub;
-}
-
-static void
-_d2tk_draw_pane(d2tk_core_t *core, d2tk_state_t state, const d2tk_rect_t *sub,
- const d2tk_style_t *style, d2tk_flag_t flags)
-{
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { sub, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &flags, sizeof(d2tk_flag_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const d2tk_coord_t s = 10; //FIXME
- const d2tk_coord_t r = (s - 2) / 2; //FIXME
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(state))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- d2tk_coord_t x0, x1, x2, y0, y1, y2;
-
- if(flags & D2TK_FLAG_PANE_X)
- {
- x0 = sub->x + sub->w/2;
- x1 = x0;
- x2 = x0;
-
- y0 = sub->y;
- y1 = y0 + sub->h/2;
- y2 = y0 + sub->h;
- }
- else // flags & D2TK_FLAG_PANE_Y
- {
- x0 = sub->x;
- x1 = x0 + sub->w/2;
- x2 = x0 + sub->w;
-
- y0 = sub->y + sub->h/2;
- y1 = y0;
- y2 = y0;
- }
-
- const size_t ref = d2tk_core_bbox_push(core, true, sub);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_line_to(core, x2, y2);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x1, y1, r, 0, 360, true);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x1, y1, r, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);\
- }
-}
-
-D2TK_API d2tk_pane_t *
-d2tk_pane_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
- d2tk_flag_t flags, float fmin, float fmax, float fstep, d2tk_pane_t *pane)
-{
- pane->k = 0;
- pane->rect[0] = *rect;
- pane->rect[1] = *rect;
- pane->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_PANE);
-
- float *fraction = &pane->atom_body->pane.fraction;
- d2tk_rect_t sub = *rect;
-
- const d2tk_coord_t s = 10; //FIXME
-
- d2tk_clip_float(fmin, &pane->atom_body->pane.fraction, fmax);
-
- if(flags & D2TK_FLAG_PANE_X)
- {
- pane->rect[0].w *= pane->atom_body->pane.fraction;
-
- sub.x += pane->rect[0].w;
- sub.w = s;
-
- const d2tk_coord_t rsvd = pane->rect[0].w + s;
- pane->rect[1].x += rsvd;
- pane->rect[1].w -= rsvd;
- }
- else if(flags & D2TK_FLAG_PANE_Y)
- {
- pane->rect[0].h *= pane->atom_body->pane.fraction;
-
- sub.y += pane->rect[0].h;
- sub.h = s;
-
- const d2tk_coord_t rsvd = pane->rect[0].h + s;
- pane->rect[1].y += rsvd;
- pane->rect[1].h -= rsvd;
- }
-
- d2tk_state_t state = D2TK_STATE_NONE;
-
- if(flags & D2TK_FLAG_PANE_X)
- {
- state |= d2tk_base_is_active_hot(base, id, &sub, D2TK_FLAG_NONE);
- }
- else if(flags & D2TK_FLAG_PANE_Y)
- {
- state |= d2tk_base_is_active_hot(base, id, &sub, D2TK_FLAG_NONE);
- }
-
- const float old_fraction = *fraction;
-
- if(flags & D2TK_FLAG_PANE_X)
- {
- if(d2tk_state_is_scroll_left(state))
- {
- *fraction -= fstep;
- }
- else if(d2tk_state_is_scroll_right(state))
- {
- *fraction += fstep;
- }
- else if(d2tk_state_is_motion(state))
- {
- *fraction = roundf((float)(base->mouse.x - rect->x) / rect->w / fstep) * fstep;
- }
- }
- else if(flags & D2TK_FLAG_PANE_Y)
- {
- if(d2tk_state_is_scroll_up(state))
- {
- *fraction -= fstep;
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- *fraction += fstep;
- }
- else if(d2tk_state_is_motion(state))
- {
- *fraction = roundf((float)(base->mouse.y - rect->y) / rect->h / fstep) * fstep;
- }
- }
-
- if(old_fraction != *fraction)
- {
- state |= D2TK_STATE_CHANGED;
- d2tk_base_set_again(base);
- }
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- d2tk_core_t *core = base->core;
-
- _d2tk_draw_pane(core, state, &sub, style, flags);
-
- return pane;
-}
-
-D2TK_API bool
-d2tk_pane_not_end(d2tk_pane_t *pane)
-{
- return pane ? true : false;
-}
-
-D2TK_API d2tk_pane_t *
-d2tk_pane_next(d2tk_pane_t *pane)
-{
- return (pane->k++ == 0) ? pane : NULL;
-}
-
-D2TK_API float
-d2tk_pane_get_fraction(d2tk_pane_t *pane)
-{
- return pane->atom_body->pane.fraction;
-}
-
-D2TK_API unsigned
-d2tk_pane_get_index(d2tk_pane_t *pane)
-{
- return pane->k;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_pane_get_rect(d2tk_pane_t *pane)
-{
- return &pane->rect[pane->k];
-}
-
-D2TK_API void
-d2tk_base_cursor(d2tk_base_t *base, const d2tk_rect_t *rect)
-{
- d2tk_core_t *core = base->core;
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(rect) },
- { style, sizeof(d2tk_style_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const d2tk_coord_t x0 = rect->x;
- const d2tk_coord_t x1 = x0 + rect->w/2;
- const d2tk_coord_t x2 = x0 + rect->w;
- const d2tk_coord_t y0 = rect->y;
- const d2tk_coord_t y1 = y0 + rect->h/2;
- const d2tk_coord_t y2 = y0 + rect->h;
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_line_to(core, x1, y2);
- d2tk_core_line_to(core, x1, y1);
- d2tk_core_line_to(core, x2, y1);
- d2tk_core_close_path(core);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_FOCUS]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_line_to(core, x1, y2);
- d2tk_core_line_to(core, x1, y1);
- d2tk_core_line_to(core, x2, y1);
- d2tk_core_close_path(core);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, 2*style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-}
-
-static inline void
-_d2tk_base_draw_button(d2tk_core_t *core, ssize_t lbl_len, const char *lbl,
- d2tk_align_t align, ssize_t path_len, const char *path,
- const d2tk_rect_t *rect, d2tk_triple_t triple, const d2tk_style_t *style)
-{
- const bool has_lbl = lbl_len && lbl;
- const bool has_img = path_len && path;
-
- if(has_lbl && (lbl_len == -1) ) // zero-terminated string
- {
- lbl_len = strlen(lbl);
- }
-
- if(has_img && (path_len == -1) ) // zero-terminated string
- {
- path_len = strlen(path);
- }
-
- const d2tk_hash_dict_t dict [] = {
- { &triple, sizeof(d2tk_triple_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &align, sizeof(d2tk_align_t) },
- { (lbl ? lbl : path), (lbl ? lbl_len : path_len) },
- { path, path_len },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_rect_t bnd_outer;
- d2tk_rect_t bnd_inner;
- d2tk_rect_shrink(&bnd_outer, rect, style->padding);
- d2tk_rect_shrink(&bnd_inner, &bnd_outer, 2*style->padding);
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- if(has_lbl)
- {
- const d2tk_coord_t h_2 = rect->h / 2;
- const d2tk_align_t lbl_align = align;
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd_inner);
- d2tk_core_font_size(core, h_2);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[triple]);
- d2tk_core_text(core, &bnd_inner, lbl_len, lbl, lbl_align);
- d2tk_core_restore(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- if(has_img)
- {
- const d2tk_align_t img_align = D2TK_ALIGN_MIDDLE
- | (has_lbl ? D2TK_ALIGN_RIGHT : D2TK_ALIGN_CENTER);
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_image(core, &bnd_inner, path_len, path, img_align);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_button_label_image(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
- const char *lbl, d2tk_align_t align, ssize_t path_len, const char *path,
- const d2tk_rect_t *rect)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
-
- if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(state))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- _d2tk_base_draw_button(base->core, lbl_len, lbl, align, path_len, path, rect,
- triple, d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_button_label(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
- const char *lbl, d2tk_align_t align, const d2tk_rect_t *rect)
-{
- return d2tk_base_button_label_image(base, id, lbl_len, lbl,
- align, 0, NULL, rect);
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_button_image(d2tk_base_t *base, d2tk_id_t id, ssize_t path_len,
- const char *path, const d2tk_rect_t *rect)
-{
- return d2tk_base_button_label_image(base, id, 0, NULL,
- D2TK_ALIGN_NONE, path_len, path, rect);
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_button(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect)
-{
- return d2tk_base_button_label_image(base, id, 0, NULL,
- D2TK_ALIGN_NONE, 0, NULL, rect);
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_toggle_label(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
- const char *lbl, d2tk_align_t align, const d2tk_rect_t *rect, bool *value)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
-
- if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
- {
- *value = !*value;
- state |= D2TK_STATE_CHANGED;
- }
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(*value)
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- _d2tk_base_draw_button(base->core, lbl_len, lbl, align, 0, NULL, rect, triple,
- d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_toggle(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- bool *value)
-{
- return d2tk_base_toggle_label(base, id, 0, NULL,
- D2TK_ALIGN_NONE, rect, value);
-}
-
-D2TK_API void
-d2tk_base_image(d2tk_base_t *base, ssize_t path_len, const char *path,
- const d2tk_rect_t *rect, d2tk_align_t align)
-{
- const bool has_img = path_len && path;
-
- if(has_img && (path_len == -1) ) // zero-terminated string
- {
- path_len = strlen(path);
- }
-
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(d2tk_rect_t) },
- { (path ? path : NULL), (path ? path_len : 0) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;;
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- if(has_img)
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_image(core, rect, path_len, path, align);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API void
-d2tk_base_bitmap(d2tk_base_t *base, uint32_t w, uint32_t h, uint32_t stride,
- const uint32_t *argb, uint64_t rev, const d2tk_rect_t *rect,
- d2tk_align_t align)
-{
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(d2tk_rect_t) },
- { &w, sizeof(uint32_t) },
- { &h, sizeof(uint32_t) },
- { &stride, sizeof(uint32_t) },
- { &rev, sizeof(uint64_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;;
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_bitmap(core, rect, w, h, stride, argb, rev, align);
-
- d2tk_core_bbox_pop(core, ref);
- }
-}
-
-D2TK_API void
-d2tk_base_custom(d2tk_base_t *base, uint32_t size, const void *data,
- const d2tk_rect_t *rect, d2tk_core_custom_t custom)
-{
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(d2tk_rect_t) } ,
- { data, size }, //FIXME
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;;
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_custom(core, rect, size, data, custom);
-
- d2tk_core_bbox_pop(core, ref);
- }
-}
-
-static inline void
-_d2tk_base_draw_meter(d2tk_core_t *core, const d2tk_rect_t *rect,
- d2tk_state_t state, int32_t value, const d2tk_style_t *style)
-{
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &value, sizeof(int32_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
-#define N 4
-#define N_1 (N - 1)
-#define L 11
-
-#define dBFS3_min -18 // -54 dBFS
-#define dBFS3_max 2 // +6 dBFS
-#define dBFS3_range (dBFS3_max - dBFS3_min)
-
- static const int32_t dBFS3_off [N] = {
- dBFS3_min,
- -2, // -6 dBFS
- 0, // +0 dBFS
- dBFS3_max
- };
-
- static const uint32_t rgba [N] = {
- 0x00ffffff, // cyan
- 0x00ff00ff, // green
- 0xffff00ff, // yellow
- 0xff0000ff // red
- };
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(state))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
- bnd.h /= 2;
-
- const d2tk_coord_t dx = bnd.w / dBFS3_range;
- const d2tk_coord_t y0 = bnd.y;
- const d2tk_coord_t y1 = y0 + bnd.h;
- const d2tk_coord_t ym = (y0 + y1)/2;
-
- const d2tk_point_t p [N] = {
- D2TK_POINT(bnd.x + (dBFS3_off[0] - dBFS3_min)*dx, ym),
- D2TK_POINT(bnd.x + (dBFS3_off[1] - dBFS3_min)*dx, ym),
- D2TK_POINT(bnd.x + (dBFS3_off[2] - dBFS3_min)*dx, ym),
- D2TK_POINT(bnd.x + (dBFS3_off[3] - dBFS3_min)*dx, ym)
- };
-
- // dependent on value, e.g. linear gradient
- {
- const d2tk_coord_t xv = bnd.x + (value - dBFS3_min*3)*dx/3;
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- for(unsigned i = 0; i < N_1; i++)
- {
- const d2tk_coord_t x0 = p[i].x;
- d2tk_coord_t x1 = p[i+1].x;
- bool do_break = false;
-
- if(x1 > xv)
- {
- x1 = xv;
- do_break = true;
- }
-
- const d2tk_rect_t bnd2 = {
- .x = x0,
- .y = y0,
- .w = x1 - x0,
- .h = bnd.h
- };
-
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd2);
- d2tk_core_linear_gradient(core, &p[i], &rgba[i]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- if(do_break)
- {
- break;
- }
- }
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- // independent on value, eg scale + border
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- for(int32_t dBFS3 = dBFS3_min + 1; dBFS3 < dBFS3_max; dBFS3++)
- {
- const d2tk_coord_t x = bnd.x + (dBFS3 - dBFS3_min)*dx;
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x, y0);
- d2tk_core_line_to(core, x, y1);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
- }
-
- {
- const d2tk_rect_t bnd2 = {
- .x = bnd.x,
- .y = y0,
- .w = p[N_1].x - p[0].x,
- .h = bnd.h
- };
-
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd2);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
- }
-
- static const unsigned lbls [L] = {
- +2,
- +1,
- +0,
- -1,
- -2,
- -4,
- -8,
- -12,
- -15, // unit
- -16
- };
-
- for(unsigned i = 0; i<L; i++)
- {
- const int32_t dBFS3 = lbls[i] - 1;
- const d2tk_coord_t x = bnd.x + (dBFS3 - dBFS3_min)*dx;
- const bool is_unit = (i == 8);
-
- const d2tk_rect_t bnd2 = {
- .x = x,
- .y = y0 + bnd.h,
- .w = is_unit ? 3*dx : dx,
- .h = bnd.h
- };
-
- char lbl [16];
- const ssize_t lbl_len = is_unit
- ? snprintf(lbl, sizeof(lbl), " dBFS")
- : snprintf(lbl, sizeof(lbl), "%+"PRIi32, (dBFS3+1)*3);
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd2);
- d2tk_core_font_size(core, bnd2.h);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[triple]);
- d2tk_core_text(core, &bnd2, lbl_len, lbl,
- D2TK_ALIGN_BOTTOM | (is_unit ? D2TK_ALIGN_LEFT: D2TK_ALIGN_RIGHT));
- d2tk_core_restore(core);
- }
-
- d2tk_core_bbox_pop(core, ref);
- }
-
-#undef dBFS3_range
-#undef dBFS3_max
-#undef dBFS3_min
-
-#undef L
-#undef N
-#undef N_1
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_meter(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- const int32_t *value)
-{
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- const d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_NONE);
-
- d2tk_core_t *core = base->core;
-
- _d2tk_base_draw_meter(core, rect, state, *value, style);
-
- return state;
-}
-
-static inline void
-_d2tk_base_draw_combo(d2tk_core_t *core, ssize_t nitms, const char **itms,
- const d2tk_rect_t *rect, d2tk_state_t state, int32_t value,
- const d2tk_style_t *style)
-{
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &value, sizeof(int32_t) },
- { &nitms, sizeof(ssize_t) },
- { itms, sizeof(const char **) }, //FIXME we should actually cache the labels
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- const d2tk_coord_t w_2 = bnd.w / 2;
- const d2tk_coord_t w_4 = bnd.w / 4;
-
- d2tk_rect_t left = bnd;
- left.x -= w_4;
- left.w = w_2;
-
- d2tk_rect_t midd = bnd;
- midd.x += w_4;
- midd.w = w_2;
-
- d2tk_rect_t right = bnd;
- right.x += w_2 + w_4;
- right.w = w_2;
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- uint32_t fill_color_2 = style->fill_color[triple];
- fill_color_2 = (fill_color_2 & 0xffffff00) | (fill_color_2 & 0xff / 2);
-
- // left filling
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &left);
- d2tk_core_color(core, fill_color_2);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- // middle filling
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &midd);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- // right filling
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &right);
- d2tk_core_color(core, fill_color_2);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- // draw lines above and below text
- const d2tk_coord_t h_8 = bnd.h / 8;
- const d2tk_coord_t dx = bnd.w / nitms;
- const d2tk_coord_t x0 = bnd.x;
- const d2tk_coord_t x1 = bnd.x + value*dx;
- const d2tk_coord_t x2 = x1 + dx;
- const d2tk_coord_t x3 = bnd.x + bnd.w;
- const d2tk_coord_t y0 = bnd.y + h_8;
- const d2tk_coord_t y1 = bnd.y + bnd.h - h_8;
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_line_to(core, x3, y0);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y1);
- d2tk_core_line_to(core, x3, y1);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width*2);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x1, y0);
- d2tk_core_line_to(core, x2, y0);
- d2tk_core_color(core, style->fill_color[triple | D2TK_TRIPLE_ACTIVE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x1, y1);
- d2tk_core_line_to(core, x2, y1);
- d2tk_core_color(core, style->fill_color[triple | D2TK_TRIPLE_ACTIVE]);
- d2tk_core_stroke_width(core, style->border_width*2);
- d2tk_core_stroke(core);
-
- // draw bounding box
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- // left label
- if(value > 0)
- {
- const char *lbl = itms[value - 1];
- const size_t lbl_len = lbl ? strlen(lbl) : 0;
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &left);
- d2tk_core_font_size(core, left.h / 2);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_text(core, &left, lbl_len, lbl, D2TK_ALIGN_CENTERED);
- d2tk_core_restore(core);
- }
-
- // middle label
- {
- const char *lbl = itms[value];
- const size_t lbl_len = lbl ? strlen(lbl) : 0;
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &midd);
- d2tk_core_font_size(core, midd.h / 2);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[triple]);
- d2tk_core_text(core, &midd, lbl_len, lbl, D2TK_ALIGN_CENTERED);
- d2tk_core_restore(core);
- }
-
- // right label
- if(value < (nitms-1) )
- {
- const char *lbl = itms[value + 1];
- const size_t lbl_len = lbl ? strlen(lbl) : 0;
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &right);
- d2tk_core_font_size(core, right.h / 2);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_text(core, &right, lbl_len, lbl, D2TK_ALIGN_CENTERED);
- d2tk_core_restore(core);
- }
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_combo(d2tk_base_t *base, d2tk_id_t id, ssize_t nitms,
- const char **itms, const d2tk_rect_t *rect, int32_t *value)
-{
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL_Y);
-
- const int32_t old_value = *value;
-
- if( d2tk_state_is_scroll_up(state)
- || d2tk_state_is_scroll_right(state)
- || d2tk_state_is_enter(state) )
- {
- *value += 1;
- }
-
- if( d2tk_state_is_scroll_down(state)
- || d2tk_state_is_scroll_left(state))
- {
- *value -= 1;
- }
-
- if(d2tk_state_is_down(state))
- {
- const d2tk_coord_t w_2 = rect->w/2;
- const d2tk_coord_t w_4 = rect->w/4;
-
- const d2tk_coord_t x1 = rect->x + w_4;
- const d2tk_coord_t x2 = x1 + w_2;
-
- if(base->mouse.x < x1)
- {
- *value -= 1;
- }
- else if(base->mouse.x > x2)
- {
- *value += 1;
- }
- }
-
- d2tk_clip_int32(0, value, nitms-1);
-
- if(*value != old_value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- d2tk_core_t *core = base->core;
-
- _d2tk_base_draw_combo(core, nitms, itms, rect, state, *value, style);
-
- return state;
-}
-
-static void
-_d2tk_base_draw_text_field(d2tk_core_t *core, d2tk_state_t state,
- const d2tk_rect_t *rect, const d2tk_style_t *style, char *value,
- d2tk_align_t align)
-{
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &align, sizeof(d2tk_align_t) },
- { value, strlen(value) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- const d2tk_coord_t h_8 = bnd.h / 8;
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- // draw background
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- // draw lines above and below text
- const d2tk_coord_t x0 = bnd.x;
- const d2tk_coord_t x1 = bnd.x + bnd.w;
- const d2tk_coord_t y0 = bnd.y + h_8;
- const d2tk_coord_t y1 = bnd.y + bnd.h - h_8;
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_line_to(core, x1, y0);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y1);
- d2tk_core_line_to(core, x1, y1);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- // draw bounding box
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- const size_t valuelen= strlen(value);
- if(valuelen)
- {
- const d2tk_coord_t h_2 = bnd.h / 2;
-
- d2tk_rect_t bnd2;
- d2tk_rect_shrink_x(&bnd2, &bnd, h_8);
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd2);
- d2tk_core_font_size(core, h_2);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_text(core, &bnd2, valuelen, value, align);
- d2tk_core_restore(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_text_field(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- size_t maxlen, char *value, d2tk_align_t align, const char *accept)
-{
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
-
- if(d2tk_state_is_focus_in(state))
- {
- _d2tk_base_clear_chars(base); // eat keys
-
- // copy text from value to edit.text_in
- strncpy(base->edit.text_in, value, maxlen);
- }
-
- if(d2tk_state_is_focused(state))
- {
- // use edit.text_in
- value = base->edit.text_in;
-
- if(d2tk_base_get_keymask(base, D2TK_KEYMASK_BACKSPACE, true))
- {
- const ssize_t ulen = utf8len(value) - 1;
- char *head = value;
- utf8_int32_t codepoint;
-
- for(ssize_t i = 0; i < ulen; i++)
- {
- head = utf8codepoint(head, &codepoint);
- }
-
- head[0] = '\0';
- //_d2tk_base_clear_chars(base); // eat key
- }
- else if(d2tk_base_get_keymask(base, D2TK_KEYMASK_DEL, true))
- {
- memset(value, 0x0, maxlen);
- //_d2tk_base_clear_chars(base); // eat key
- }
-
- if(base->keys.nchars)
- {
- const utf8_int32_t *head = base->keys.chars;
-
- const ssize_t len = strlen(value);
- char *tail = &value[len];
-
- utf8_int32_t codepoint;
-
- for(size_t i = 0; i < base->keys.nchars; i++)
- {
- codepoint = head[i];
-
- if(accept && !utf8chr(accept, codepoint))
- {
- continue;
- }
-
- const ssize_t left = maxlen - (tail - value);
- if(left > 0)
- {
- tail = utf8catcodepoint(tail, codepoint, left);
- }
- }
-
- _d2tk_base_clear_chars(base); // eat keys
- }
-
- char *buf = alloca(maxlen + 1);
- if(buf)
- {
- snprintf(buf, maxlen, "%s|", value);
- value = buf;
- }
- }
-
- if(d2tk_state_is_focus_out(state))
- {
- // copy text from edit.text_out to value
- strncpy(value, base->edit.text_out, maxlen);
-
- state |= D2TK_STATE_CHANGED;
- }
-
- //FIXME handle d2tk_state_is_enter(state)
-
- d2tk_core_t *core = base->core;
-
- _d2tk_base_draw_text_field(core, state, rect, style, value, align);
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_label(d2tk_base_t *base, ssize_t lbl_len, const char *lbl,
- float mul, const d2tk_rect_t *rect, d2tk_align_t align)
-{
- const bool has_lbl = lbl_len && lbl;
-
- if(has_lbl && (lbl_len == -1) ) // zero terminated string
- {
- lbl_len = strlen(lbl);
- }
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- const d2tk_hash_dict_t dict [] = {
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &mul, sizeof(float) },
- { &align, sizeof(d2tk_align_t) },
- { lbl, lbl_len },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- const d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- if(style->text_fill_color[triple])
- {
- d2tk_core_begin_path(core);
- d2tk_core_rect(core, &bnd);
- d2tk_core_color(core, style->text_fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
- }
-
- if(lbl_len > 0)
- {
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd);
- d2tk_core_font_size(core, mul*bnd.h);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[triple]);
- d2tk_core_text(core, &bnd, lbl_len, lbl, align);
- d2tk_core_restore(core);
- }
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- return D2TK_STATE_NONE;
-}
-
-static void
-_d2tk_base_draw_link(d2tk_base_t *base, ssize_t lbl_len, const char *lbl,
- float mul, const d2tk_rect_t *rect, d2tk_align_t align, d2tk_triple_t triple,
- const d2tk_style_t *style)
-{
- const bool has_lbl = lbl_len && lbl;
-
- if(has_lbl && (lbl_len == -1) ) // zero terminated string
- {
- lbl_len = strlen(lbl);
- }
-
- const d2tk_hash_dict_t dict [] = {
- { &triple, sizeof(d2tk_triple_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &mul, sizeof(float) },
- { &align, sizeof(d2tk_align_t) },
- { lbl, lbl_len },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;
-
- //FIXME analyse link and draw underline, hover, etc.
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_save(core);
- d2tk_core_scissor(core, &bnd);
- d2tk_core_font_size(core, mul*bnd.h);
- d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
- d2tk_core_color(core, style->text_stroke_color[triple]);
- d2tk_core_text(core, &bnd, lbl_len, lbl, align);
- d2tk_core_restore(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- {
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, bnd.x, bnd.y + bnd.h);
- d2tk_core_line_to(core, bnd.x + bnd.w, bnd.y + bnd.h);
- if(triple & D2TK_TRIPLE_FOCUS)
- {
- d2tk_core_color(core, style->stroke_color[triple]);
- }
- else
- {
- d2tk_core_color(core, style->fill_color[triple]);
- }
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_link(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len, const char *lbl,
- float mul, const d2tk_rect_t *rect, d2tk_align_t align)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
-
- if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(state))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- _d2tk_base_draw_link(base, lbl_len, lbl, mul, rect, align, triple,
- d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_dial_bool(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- bool *value)
-{
- const bool oldvalue = *value;
-
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL);
-
- if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
- {
- *value = !*value;
- }
- else if(d2tk_state_is_scroll_up(state))
- {
- *value = true;
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- *value = false;
- }
-
- if(oldvalue != *value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
- d2tk_core_t *core = base->core;
-
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { value, sizeof(bool) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
-
- if(*value)
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- }
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- const d2tk_coord_t d = bnd.h < bnd.w ? bnd.h : bnd.w;
- const d2tk_coord_t r1 = d / 2;
- const d2tk_coord_t r0 = d / 3;
- bnd.x += bnd.w / 2;
- bnd.y += bnd.h / 2;
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, r1, 0, 360, true);
- d2tk_core_color(core, style->fill_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, r1, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, r0, 0, 360, true);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, r0, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- return state;
-}
-
-static inline void
-_d2tk_base_draw_dial(d2tk_core_t *core, const d2tk_rect_t *rect,
- d2tk_state_t state, float rel, const d2tk_style_t *style)
-{
- const d2tk_hash_dict_t dict [] = {
- { &state, sizeof(d2tk_state_t) },
- { rect, sizeof(d2tk_rect_t) },
- { style, sizeof(d2tk_style_t) },
- { &rel, sizeof(float) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- d2tk_triple_t triple = D2TK_TRIPLE_NONE;
- d2tk_triple_t triple_active = D2TK_TRIPLE_ACTIVE;
- d2tk_triple_t triple_inactive = D2TK_TRIPLE_NONE;
-
- if(d2tk_state_is_active(state))
- {
- triple |= D2TK_TRIPLE_ACTIVE;
- }
-
- if(d2tk_state_is_hot(state))
- {
- triple |= D2TK_TRIPLE_HOT;
- triple_active |= D2TK_TRIPLE_HOT;
- triple_inactive |= D2TK_TRIPLE_HOT;
- }
-
- if(d2tk_state_is_focused(state))
- {
- triple |= D2TK_TRIPLE_FOCUS;
- triple_active |= D2TK_TRIPLE_FOCUS;
- triple_inactive |= D2TK_TRIPLE_FOCUS;
- }
-
- const size_t ref = d2tk_core_bbox_push(core, true, rect);
-
- d2tk_rect_t bnd;
- d2tk_rect_shrink(&bnd, rect, style->padding);
-
- const d2tk_coord_t d = bnd.h < bnd.w ? bnd.h : bnd.w;
- const d2tk_coord_t r1 = d / 2;
- const d2tk_coord_t r0 = d / 4;
- bnd.x += bnd.w / 2;
- bnd.y += bnd.h / 2;
-
- static const d2tk_coord_t a = 90 + 22; //FIXME
- static const d2tk_coord_t c = 90 - 22; //FIXME
- const d2tk_coord_t b = a + (360 - 44)*rel; //FIXME
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, (r1 + r0)/2, a, c, true);
- d2tk_core_color(core, style->fill_color[triple_inactive]);
- d2tk_core_stroke_width(core, r1 - r0);
- d2tk_core_stroke(core);
-
- if(rel > 0.f)
- {
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, (r1 + r0)/2, a, b, true);
- d2tk_core_color(core, style->fill_color[triple_active]);
- d2tk_core_stroke_width(core, (r1 - r0) * 3/4);
- d2tk_core_stroke(core);
- }
-
- const float phi = (b + 90) / 180.f * M_PI;
- const d2tk_coord_t rx1 = bnd.x + r0 * sinf(phi);
- const d2tk_coord_t ry1 = bnd.y - r0 * cosf(phi);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, bnd.x, bnd.y);
- d2tk_core_line_to(core, rx1, ry1);
- d2tk_core_close_path(core);
- d2tk_core_color(core, style->fill_color[triple_active]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, bnd.x, bnd.y, r1, a, c, true);
- d2tk_core_arc(core, bnd.x, bnd.y, r0, c, a, false);
- d2tk_core_close_path(core);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_dial_int32(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- int32_t min, int32_t *value, int32_t max)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL);
-
- const int32_t oldvalue = *value;
-
- if(d2tk_state_is_scroll_up(state))
- {
- *value += base->scroll.ody;
- d2tk_clip_int32(min, value, max);
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- *value += base->scroll.ody;
- d2tk_clip_int32(min, value, max);
- }
- else if(d2tk_state_is_motion(state))
- {
- const int32_t adx = abs(base->mouse.dx);
- const int32_t ady = abs(base->mouse.dy);
- const int32_t adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
-
- *value += adz;
- d2tk_clip_int32(min, value, max);
- }
-
- if(oldvalue != *value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- float rel = (float)(*value - min) / (max - min);
- d2tk_clip_float(0.f, &rel, 1.f);
-
- d2tk_core_t *core = base->core;
- _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_prop_int32(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- int32_t min, int32_t *value, int32_t max)
-{
- const d2tk_coord_t dw = rect->w / 3;
-
- const d2tk_rect_t left = {
- .x = rect->x,
- .y = rect->y,
- .w = dw,
- .h = rect->h
- };
- const d2tk_rect_t right = {
- .x = rect->x + dw,
- .y = rect->y,
- .w = rect->w - dw,
- .h = rect->h
- };
-
- const d2tk_id_t id_left = (1 << 24) | id;
- const d2tk_id_t id_right = (2 << 24) | id;
-
- const d2tk_state_t state_dial = d2tk_base_dial_int32(base, id_left, &left,
- min, value, max);
-
- char text [32];
- snprintf(text, sizeof(text), "%+"PRIi32, *value);
-
- const d2tk_state_t state_field = d2tk_base_text_field(base, id_right, &right,
- sizeof(text), text, D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE, "1234567890+-");
-
- if(d2tk_state_is_changed(state_field))
- {
- d2tk_base_set_again(base);
- }
-
- const d2tk_state_t state = state_dial | state_field;
-
- if(d2tk_state_is_focus_out(state))
- {
- int32_t val;
- if(sscanf(text, "%"SCNi32, &val) == 1)
- {
- *value = val;
- d2tk_clip_int32(min, value, max);
- }
- }
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_dial_int64(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- int64_t min, int64_t *value, int64_t max)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL);
-
- const int64_t oldvalue = *value;
-
- if(d2tk_state_is_scroll_up(state))
- {
- *value += base->scroll.ody;
- d2tk_clip_int64(min, value, max);
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- *value += base->scroll.ody;
- d2tk_clip_int64(min, value, max);
- }
- else if(d2tk_state_is_motion(state))
- {
- const int64_t adx = abs(base->mouse.dx);
- const int64_t ady = abs(base->mouse.dy);
- const int64_t adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
-
- *value += adz;
- d2tk_clip_int64(min, value, max);
- }
-
- if(oldvalue != *value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- float rel = (float)(*value - min) / (max - min);
- d2tk_clip_float(0.f, &rel, 1.f);
-
- d2tk_core_t *core = base->core;
- _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_dial_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- float min, float *value, float max)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL);
-
- const float oldvalue = *value;
-
- if(d2tk_state_is_scroll_up(state))
- {
- const float dv = (max - min);
- const float mul = d2tk_base_get_mod(base) ? 0.01f : 0.1f;
- *value += dv * mul * base->scroll.ody;
- d2tk_clip_float(min, value, max);
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- const float dv = (max - min);
- const float mul = d2tk_base_get_mod(base) ? 0.01f : 0.1f;
- *value += dv * mul * base->scroll.ody;
- d2tk_clip_float(min, value, max);
- }
- else if(d2tk_state_is_motion(state))
- {
- const float adx = abs(base->mouse.dx);
- const float ady = abs(base->mouse.dy);
- const float adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
-
- const float dv = (max - min);
- const float mul = d2tk_base_get_mod(base) ? 0.001f : 0.01f;
- *value += dv * adz * mul;
- d2tk_clip_float(min, value, max);
- }
-
- if(oldvalue != *value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- float rel = (*value - min) / (max - min);
- d2tk_clip_float(0.f, &rel, 1.f);
-
- d2tk_core_t *core = base->core;
- _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_prop_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- float min, float *value, float max)
-{
- const d2tk_coord_t dw = rect->w / 3;
-
- const d2tk_rect_t left = {
- .x = rect->x,
- .y = rect->y,
- .w = dw,
- .h = rect->h
- };
- const d2tk_rect_t right = {
- .x = rect->x + dw,
- .y = rect->y,
- .w = rect->w - dw,
- .h = rect->h
- };
-
- const d2tk_id_t id_left = (1 << 24) | id;
- const d2tk_id_t id_right = (2 << 24) | id;
-
- const d2tk_state_t state_dial = d2tk_base_dial_float(base, id_left, &left,
- min, value, max);
-
- char text [32];
- snprintf(text, sizeof(text), "%+.4f", *value);
-
- const d2tk_state_t state_field = d2tk_base_text_field(base, id_right, &right,
- sizeof(text), text, D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE, "1234567890.+-");
-
- if(d2tk_state_is_changed(state_field))
- {
- d2tk_base_set_again(base);
- }
-
- const d2tk_state_t state = state_dial | state_field;
-
- if(d2tk_state_is_focus_out(state))
- {
- float val;
- if(sscanf(text, "%f", &val) == 1)
- {
- *value = val;
- d2tk_clip_float(min, value, max);
- }
- }
-
- return state;
-}
-
-D2TK_API d2tk_state_t
-d2tk_base_dial_double(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
- double min, double *value, double max)
-{
- d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
- D2TK_FLAG_SCROLL);
-
- const double oldvalue = *value;
-
- if(d2tk_state_is_scroll_up(state))
- {
- const double dv = (max - min);
- const double mul = d2tk_base_get_mod(base) ? 0.01 : 0.1;
- *value += dv * mul * base->scroll.ody;
- d2tk_clip_double(min, value, max);
- }
- else if(d2tk_state_is_scroll_down(state))
- {
- const double dv = (max - min);
- const double mul = d2tk_base_get_mod(base) ? 0.01 : 0.1;
- *value += dv * mul * base->scroll.ody;
- d2tk_clip_double(min, value, max);
- }
- else if(d2tk_state_is_motion(state))
- {
- const double adx = abs(base->mouse.dx);
- const double ady = abs(base->mouse.dy);
- const double adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
-
- const double dv = (max - min);
- const double mul = d2tk_base_get_mod(base) ? 0.001 : 0.01;
- *value += dv * adz * mul;
- d2tk_clip_double(min, value, max);
- }
-
- if(oldvalue != *value)
- {
- state |= D2TK_STATE_CHANGED;
- }
-
- float rel = (*value - min) / (max - min);
- d2tk_clip_float(0.f, &rel, 1.f);
-
- d2tk_core_t *core = base->core;
- _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
-
- return state;
-}
-
-static d2tk_coord_t
-_d2tk_flowmatrix_abs_x(d2tk_flowmatrix_t *flowmatrix, d2tk_coord_t rel_x)
-{
- return flowmatrix->cx + rel_x * flowmatrix->scale;
-}
-
-static d2tk_coord_t
-_d2tk_flowmatrix_abs_y(d2tk_flowmatrix_t *flowmatrix, d2tk_coord_t rel_y)
-{
- return flowmatrix->cy + rel_y * flowmatrix->scale;
-}
-
-static void
-_d2tk_flowmatrix_connect(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
- const d2tk_pos_t *src_pos, const d2tk_pos_t *dst_pos)
-{
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- d2tk_pos_t dst;
- if(!dst_pos) // connect to mouse pointer
- {
- d2tk_base_get_mouse_pos(base, &dst.x, &dst.y);
- }
-
- const d2tk_hash_dict_t dict [] = {
- { flowmatrix, sizeof(d2tk_flowmatrix_t) },
- { src_pos, sizeof(d2tk_pos_t) },
- { dst_pos ? dst_pos : &dst, sizeof(d2tk_pos_t) },
- { style, sizeof(d2tk_style_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const d2tk_coord_t w = flowmatrix->w;
- const d2tk_coord_t r = flowmatrix->r;
- const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, src_pos->x) + w/2 + r;
- const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, src_pos->y);
-
- if(dst_pos)
- {
- dst.x = _d2tk_flowmatrix_abs_x(flowmatrix, dst_pos->x) - w/2 - r;
- dst.y = _d2tk_flowmatrix_abs_y(flowmatrix, dst_pos->y);
- }
-
- const d2tk_coord_t x0 = (x < dst.x) ? x : dst.x;
- const d2tk_coord_t y0 = (y < dst.y) ? y : dst.y;
- const d2tk_coord_t x1 = (x > dst.x) ? x : dst.x;
- const d2tk_coord_t y1 = (y > dst.y) ? y : dst.y;
-
- const d2tk_rect_t bnd = {
- .x = x0 - 1,
- .y = y0 - 1,
- .w = x1 - x0 + 2,
- .h = y1 - y0 + 2
- };
-
- const d2tk_triple_t triple = D2TK_TRIPLE_FOCUS;
-
- const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x, y);
- d2tk_core_line_to(core, dst.x, dst.y);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-}
-
-D2TK_API d2tk_flowmatrix_t *
-d2tk_flowmatrix_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
- d2tk_flowmatrix_t *flowmatrix)
-{
- memset(flowmatrix, 0x0, sizeof(d2tk_flowmatrix_t));
-
- flowmatrix->base = base;
- flowmatrix->id = id;
- flowmatrix->rect = rect;
- flowmatrix->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_FLOW);
- flowmatrix->scale = exp2f(flowmatrix->atom_body->flow.exponent); //FIXME cache this instead
- flowmatrix->cx = flowmatrix->rect->x + flowmatrix->rect->w / 2
- - flowmatrix->atom_body->flow.x * flowmatrix->scale;
- flowmatrix->cy = flowmatrix->rect->y + flowmatrix->rect->h / 2
- - flowmatrix->atom_body->flow.y * flowmatrix->scale;
-
- flowmatrix->w = flowmatrix->scale * 150; //FIXME
- flowmatrix->h = flowmatrix->scale * 25; //FIXME
- flowmatrix->dd = flowmatrix->scale * 40; //FIXME
- flowmatrix->r = flowmatrix->scale * 4; //FIXME
- flowmatrix->s = flowmatrix->scale * 20; //FIXME
-
- d2tk_core_t *core = base->core;
- flowmatrix->ref = d2tk_core_bbox_container_push(core, false, flowmatrix->rect);
-
- return flowmatrix;
-}
-
-D2TK_API bool
-d2tk_flowmatrix_not_end(d2tk_flowmatrix_t *flowmatrix)
-{
- return flowmatrix ? true : false;
-}
-
-D2TK_API d2tk_flowmatrix_t *
-d2tk_flowmatrix_next(d2tk_flowmatrix_t *flowmatrix)
-{
- d2tk_base_t *base = flowmatrix->base;
- float *exponent = &flowmatrix->atom_body->flow.exponent;
- const float old_exponent = *exponent;
-
- const d2tk_state_t state = d2tk_base_is_active_hot(base, flowmatrix->id,
- flowmatrix->rect, D2TK_FLAG_SCROLL_Y);
-
- if(d2tk_state_is_scroll_down(state))
- {
- *exponent -= 0.125f; //FIXME
- }
- else if(d2tk_state_is_scroll_up(state))
- {
- *exponent += 0.125f; //FIXME
- }
- else if(d2tk_state_is_motion(state))
- {
- const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
- const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
-
- flowmatrix->atom_body->flow.x -= adx;
- flowmatrix->atom_body->flow.y -= ady;
- }
-
- d2tk_clip_float(-2.f, exponent, 1.f); //FIXME
-
- if(*exponent != old_exponent)
- {
- const d2tk_coord_t ox = (base->mouse.x - flowmatrix->cx) / flowmatrix->scale;
- const d2tk_coord_t oy = (base->mouse.y - flowmatrix->cy) / flowmatrix->scale;
-
- const float scale = exp2f(*exponent);
-
- const d2tk_coord_t fx = base->mouse.x - (ox * scale);
- const d2tk_coord_t fy = base->mouse.y - (oy * scale);
-
- flowmatrix->atom_body->flow.x = (flowmatrix->rect->x + flowmatrix->rect->w / 2 - fx) / scale;
- flowmatrix->atom_body->flow.y = (flowmatrix->rect->y + flowmatrix->rect->h / 2 - fy) / scale;
-
- d2tk_base_set_again(base);
- }
-
- if(flowmatrix->src_conn)
- {
- if(flowmatrix->dst_conn)
- {
- _d2tk_flowmatrix_connect(base, flowmatrix, &flowmatrix->src_pos,
- &flowmatrix->dst_pos);
- }
- else
- {
- _d2tk_flowmatrix_connect(base, flowmatrix, &flowmatrix->src_pos, NULL);
-
- // invalidate dst_id
- flowmatrix->atom_body->flow.dst_id = 0;
- }
- }
-
- d2tk_core_t *core = base->core;
- d2tk_core_bbox_pop(core, flowmatrix->ref);
-
- return NULL;
-}
-
-static void
-_d2tk_flowmatrix_next_pos(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
-{
- flowmatrix->atom_body->flow.lx += 150; //FIXME
- flowmatrix->atom_body->flow.ly += 25; //FIXME
-
- pos->x = flowmatrix->atom_body->flow.lx;
- pos->y = flowmatrix->atom_body->flow.ly;
-}
-
-D2TK_API void
-d2tk_flowmatrix_set_src(d2tk_flowmatrix_t *flowmatrix, d2tk_id_t id,
- const d2tk_pos_t *pos)
-{
- flowmatrix->src_conn = true;
- flowmatrix->atom_body->flow.src_id = id;
-
- if(pos)
- {
- flowmatrix->src_pos = *pos;
- }
-}
-
-D2TK_API void
-d2tk_flowmatrix_set_dst(d2tk_flowmatrix_t *flowmatrix, d2tk_id_t id,
- const d2tk_pos_t *pos)
-{
- flowmatrix->dst_conn = true;
- flowmatrix->atom_body->flow.dst_id = id;
-
- if(pos)
- {
- flowmatrix->dst_pos = *pos;
- }
-}
-
-D2TK_API d2tk_id_t
-d2tk_flowmatrix_get_src(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
-{
- if(pos)
- {
- *pos = flowmatrix->src_pos;
- }
-
- return flowmatrix->atom_body->flow.src_id;
-}
-
-D2TK_API d2tk_id_t
-d2tk_flowmatrix_get_dst(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
-{
- if(pos)
- {
- *pos = flowmatrix->dst_pos;
- }
-
- return flowmatrix->atom_body->flow.dst_id;
-}
-
-D2TK_API d2tk_flowmatrix_node_t *
-d2tk_flowmatrix_node_begin(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
- d2tk_pos_t *pos, d2tk_flowmatrix_node_t *node)
-{
- node->flowmatrix = flowmatrix;
-
- // derive initial position
- if( (pos->x == 0) && (pos->y == 0) )
- {
- _d2tk_flowmatrix_next_pos(flowmatrix, pos);
- }
-
- const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, pos->x);
- const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, pos->y);
- const d2tk_coord_t w = flowmatrix->w;
- const d2tk_coord_t h = flowmatrix->h;
-
- node->rect.x = x - w/2;
- node->rect.y = y - h/2;
- node->rect.w = w;
- node->rect.h = h;
-
- d2tk_core_t *core = base->core;
- d2tk_coord_t cw;
- d2tk_coord_t ch;
-
- d2tk_core_get_dimensions(core, &cw, &ch);
- if( (node->rect.x >= cw)
- || (node->rect.y >= ch)
- || (node->rect.x <= -node->rect.w)
- || (node->rect.y <= -node->rect.h) )
- {
- return NULL;
- }
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- const d2tk_hash_dict_t dict [] = {
- { flowmatrix, sizeof(d2tk_flowmatrix_t) },
- { pos, sizeof(d2tk_pos_t) },
- { node, sizeof(d2tk_flowmatrix_node_t) },
- { style, sizeof(d2tk_style_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const d2tk_coord_t r = flowmatrix->r;
- const d2tk_coord_t r2 = r*2;
- const d2tk_triple_t triple = D2TK_TRIPLE_NONE; //FIXME
-
- // sink connection point
- {
- const d2tk_coord_t x0 = node->rect.x - r;
-
- const d2tk_rect_t bnd = {
- .x = x0 - r,
- .y = y - r,
- .w = r2,
- .h = r2
- };
-
- const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x0, y, r, 0, 360, true);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x0, y, r, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- // source connection point
- {
- const d2tk_coord_t x0 = node->rect.x + node->rect.w + r;
-
- const d2tk_rect_t bnd = {
- .x = x0 - r,
- .y = y - r,
- .w = r2,
- .h = r2
- };
-
- const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x0, y, r, 0, 360, true);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, x0, y, r, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-
- return node;
-}
-
-D2TK_API bool
-d2tk_flowmatrix_node_not_end(d2tk_flowmatrix_node_t *node)
-{
- return node ? true : false;
-}
-
-D2TK_API d2tk_flowmatrix_node_t *
-d2tk_flowmatrix_node_next(d2tk_flowmatrix_node_t *node, d2tk_pos_t *pos,
- const d2tk_state_t *state)
-{
- d2tk_flowmatrix_t *flowmatrix = node->flowmatrix;
- d2tk_base_t *base = flowmatrix->base;
-
- if(d2tk_state_is_motion(*state))
- {
- const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
- const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
-
- pos->x += adx;
- pos->y += ady;
-
- d2tk_base_set_again(base);
- }
-
- return NULL;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_flowmatrix_node_get_rect(d2tk_flowmatrix_node_t *node)
-{
- return &node->rect;
-}
-
-D2TK_API d2tk_flowmatrix_arc_t *
-d2tk_flowmatrix_arc_begin(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
- unsigned N, unsigned M, const d2tk_pos_t *src, const d2tk_pos_t *dst,
- d2tk_pos_t *pos, d2tk_flowmatrix_arc_t *arc)
-{
- memset(arc, 0x0, sizeof(d2tk_flowmatrix_arc_t));
-
- // derive initial position
- if( (pos->x == 0) && (pos->y == 0) )
- {
- pos->x = (src->x + dst->x) / 2;
- pos->y = (src->y + dst->y) / 2;
- }
-
- arc->flowmatrix = flowmatrix;
- arc->x = 0;
- arc->y = 0;
- arc->N = N+1;
- arc->M = M+1;
- arc->NM = (N+1)*(M+1);
- arc->k = 0;
-
- const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, pos->x);
- const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, pos->y);
- const d2tk_coord_t s = flowmatrix->s;
- arc->c = M_SQRT2 * s;
- arc->c_2 = arc->c / 2;
- arc->c_4 = arc->c / 4;
-
- const d2tk_coord_t x0 = x - M*arc->c_2;
- const d2tk_coord_t x1 = x;
- const d2tk_coord_t x2 = x + (N - M)*arc->c_2;
- const d2tk_coord_t x3 = x + N*arc->c_2;
-
- const d2tk_coord_t y0 = y;
- const d2tk_coord_t y1 = y + M*arc->c_2;
- const d2tk_coord_t y2 = y + N*arc->c_2;
- const d2tk_coord_t y3 = y + (N+M)*arc->c_2;
-
- arc->xo = x1 - arc->c_2;
- arc->yo = y0 + arc->c_4;
- arc->rect.x = arc->xo;
- arc->rect.y = arc->yo;
- arc->rect.w = arc->c;
- arc->rect.h = arc->c_2;
-
- const d2tk_style_t *style = d2tk_base_get_style(base);
-
- const d2tk_hash_dict_t dict [] = {
- { flowmatrix, sizeof(d2tk_flowmatrix_t) },
- { &N, sizeof(unsigned) },
- { &M, sizeof(unsigned) },
- { src, sizeof(d2tk_pos_t) },
- { dst, sizeof(d2tk_pos_t) },
- { pos, sizeof(d2tk_pos_t) },
- { arc, sizeof(d2tk_flowmatrix_arc_t) },
- { style, sizeof(d2tk_style_t) },
- { NULL, 0 }
- };
- const uint64_t hash = d2tk_hash_dict(dict);
-
- d2tk_core_t *core = base->core;
- D2TK_CORE_WIDGET(core, hash, widget)
- {
- const d2tk_coord_t r = flowmatrix->r;
- const d2tk_coord_t ox = flowmatrix->w/2;
- const d2tk_coord_t dd = flowmatrix->dd;
- const d2tk_triple_t triple = D2TK_TRIPLE_NONE; //FIXME
- const d2tk_coord_t b = style->border_width;
- const d2tk_coord_t b2 = b*2;
-
- const d2tk_coord_t xs = _d2tk_flowmatrix_abs_x(flowmatrix, src->x) + ox + r;
- const d2tk_coord_t ys = _d2tk_flowmatrix_abs_y(flowmatrix, src->y);
- const d2tk_coord_t xp = x;
- const d2tk_coord_t yp = y - r;
- const d2tk_coord_t xd = _d2tk_flowmatrix_abs_x(flowmatrix, dst->x) - ox - r;
- const d2tk_coord_t yd = _d2tk_flowmatrix_abs_y(flowmatrix, dst->y);
-
- // sink arc
- {
- const d2tk_coord_t x0 = xs;
- const d2tk_coord_t x1 = xs + dd;
- const d2tk_coord_t x2 = xp - dd;
- const d2tk_coord_t x3 = xp;
-
- const d2tk_coord_t y0 = ys;
- const d2tk_coord_t y1 = ys;
- const d2tk_coord_t y2 = yp;
- const d2tk_coord_t y3 = yp;
-
- d2tk_coord_t xa = INT32_MAX;
- d2tk_coord_t xb = INT32_MIN;
- if(x0 < xa) xa = x0;
- if(x1 < xa) xa = x1;
- if(x2 < xa) xa = x2;
- if(x3 < xa) xa = x3;
- if(x0 > xb) xb = x0;
- if(x1 > xb) xb = x1;
- if(x2 > xb) xb = x2;
- if(x3 > xb) xb = x3;
-
- d2tk_coord_t ya = INT32_MAX;
- d2tk_coord_t yb = INT32_MIN;
- if(y0 < ya) ya = y0;
- if(y1 < ya) ya = y1;
- if(y2 < ya) ya = y2;
- if(y3 < ya) ya = y3;
- if(y0 > yb) yb = y0;
- if(y1 > yb) yb = y1;
- if(y2 > yb) yb = y2;
- if(y3 > yb) yb = y3;
-
- const d2tk_rect_t bnd = {
- .x = xa - b,
- .y = ya - b,
- .w = xb - xa + b2,
- .h = yb - ya + b2
- };
-
- const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_curve_to(core, x1, y1, x2, y2, x3, y3);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, b);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- // source arc
- {
- const d2tk_coord_t x0 = xp;
- const d2tk_coord_t x1 = xp + dd;
- const d2tk_coord_t x2 = xd - dd;
- const d2tk_coord_t x3 = xd;
-
- const d2tk_coord_t y0 = yp;
- const d2tk_coord_t y1 = yp;
- const d2tk_coord_t y2 = yd;
- const d2tk_coord_t y3 = yd;
-
- d2tk_coord_t xa = INT32_MAX;
- d2tk_coord_t xb = INT32_MIN;
- if(x0 < xa) xa = x0;
- if(x1 < xa) xa = x1;
- if(x2 < xa) xa = x2;
- if(x3 < xa) xa = x3;
- if(x0 > xb) xb = x0;
- if(x1 > xb) xb = x1;
- if(x2 > xb) xb = x2;
- if(x3 > xb) xb = x3;
-
- d2tk_coord_t ya = INT32_MAX;
- d2tk_coord_t yb = INT32_MIN;
- if(y0 < ya) ya = y0;
- if(y1 < ya) ya = y1;
- if(y2 < ya) ya = y2;
- if(y3 < ya) ya = y3;
- if(y0 > yb) yb = y0;
- if(y1 > yb) yb = y1;
- if(y2 > yb) yb = y2;
- if(y3 > yb) yb = y3;
-
- const d2tk_rect_t bnd = {
- .x = xa - b,
- .y = ya - b,
- .w = xb - xa + b2,
- .h = yb - ya + b2
- };
-
- const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y0);
- d2tk_core_curve_to(core, x1, y1, x2, y2, x3, y3);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, b);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- // matrix
- {
- const d2tk_rect_t bnd = {
- .x = x0,
- .y = y0,
- .w = x3 - x0,
- .h = y3 - y0
- };
-
- const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
-
- // matrix bounding box
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0, y1);
- d2tk_core_line_to(core, x1, y0);
- d2tk_core_line_to(core, x3, y2);
- d2tk_core_line_to(core, x2, y3);
- d2tk_core_close_path(core);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- // grid lines
- for(unsigned j = 0, o = 0;
- j < M + 1;
- j++, o += arc->c_2)
- {
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0 + o, y1 - o);
- d2tk_core_line_to(core, x2 + o, y3 - o);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
- }
-
- // grid lines
- for(unsigned i = 0, o = 0;
- i < N + 1;
- i++, o += arc->c_2)
- {
- d2tk_core_begin_path(core);
- d2tk_core_move_to(core, x0 + o, y1 + o);
- d2tk_core_line_to(core, x1 + o, y0 + o);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
- }
-
- d2tk_core_bbox_pop(core, ref);
- }
-
- // connection point
- {
- const d2tk_coord_t r2 = r*2;
- const d2tk_rect_t bnd = {
- .x = xp - r,
- .y = yp - r,
- .w = r2,
- .h = r2
- };
-
- const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, xp, yp, r, 0, 360, true);
- d2tk_core_color(core, style->fill_color[triple]);
- d2tk_core_stroke_width(core, 0);
- d2tk_core_fill(core);
-
- d2tk_core_begin_path(core);
- d2tk_core_arc(core, xp, yp, r, 0, 360, true);
- d2tk_core_color(core, style->stroke_color[triple]);
- d2tk_core_stroke_width(core, style->border_width);
- d2tk_core_stroke(core);
-
- d2tk_core_bbox_pop(core, ref);
- }
- }
-
- return arc;
-}
-
-D2TK_API bool
-d2tk_flowmatrix_arc_not_end(d2tk_flowmatrix_arc_t *arc)
-{
- return arc->k < arc->NM - 1;
-}
-
-D2TK_API d2tk_flowmatrix_arc_t *
-d2tk_flowmatrix_arc_next(d2tk_flowmatrix_arc_t *arc, d2tk_pos_t *pos,
- const d2tk_state_t *state)
-{
- d2tk_flowmatrix_t *flowmatrix = arc->flowmatrix;
- d2tk_base_t *base = flowmatrix->base;
-
- if(d2tk_state_is_motion(*state))
- {
- const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
- const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
-
- pos->x += adx;
- pos->y += ady;
-
- d2tk_base_set_again(base);
- }
-
- {
- ++arc->k;
-
- if(++arc->x % arc->N)
- {
- // nothing to do
- }
- else // overflow
- {
- arc->x = 0;
- ++arc->y;
- }
-
- arc->rect.x = arc->xo + (arc->x - arc->y)*arc->c_2;
- arc->rect.y = arc->yo + (arc->x + arc->y)*arc->c_2;
- arc->rect.w = arc->c;
- arc->rect.h = arc->c_2;
-
- if(arc->y == (arc->M - 1)) // source label
- {
- arc->rect.x -= arc->rect.w*1 + arc->c_2;
- arc->rect.y -= arc->c_4;
- arc->rect.w *= 2;
- }
- else if(arc->x == (arc->N - 1)) // sink label
- {
- arc->rect.x += arc->c_2;
- arc->rect.y -= arc->c_4;
- arc->rect.w *= 2;
- }
- }
-
- return arc;
-}
-
-D2TK_API unsigned
-d2tk_flowmatrix_arc_get_index(d2tk_flowmatrix_arc_t *arc)
-{
- return arc->k;
-}
-
-D2TK_API unsigned
-d2tk_flowmatrix_arc_get_index_x(d2tk_flowmatrix_arc_t *arc)
-{
- return arc->x;
-}
-
-D2TK_API unsigned
-d2tk_flowmatrix_arc_get_index_y(d2tk_flowmatrix_arc_t *arc)
-{
- return arc->y;
-}
-
-D2TK_API const d2tk_rect_t *
-d2tk_flowmatrix_arc_get_rect(d2tk_flowmatrix_arc_t *arc __attribute__((unused)))
-{
- return &arc->rect;
-}
-
D2TK_API d2tk_base_t *
d2tk_base_new(const d2tk_core_driver_t *driver, void *data)
{
@@ -3972,6 +725,8 @@ d2tk_base_new(const d2tk_core_driver_t *driver, void *data)
return NULL;
}
+ atomic_init(&base->again, false);
+
base->core = d2tk_core_new(driver, data);
return base;
@@ -3986,6 +741,21 @@ d2tk_base_set_ttls(d2tk_base_t *base, uint32_t sprites, uint32_t memcaches)
D2TK_API void
d2tk_base_free(d2tk_base_t *base)
{
+ for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
+ {
+ d2tk_atom_t *atom = &base->atoms[i];
+
+ atom->id = 0;
+ atom->type = 0;
+ if(atom->event)
+ {
+ atom->event(D2TK_ATOM_EVENT_DEINIT, atom->body);
+ atom->event = NULL;
+ }
+ free(atom->body);
+ atom->body = NULL;
+ }
+
d2tk_core_free(base->core);
free(base);
}
@@ -4000,9 +770,6 @@ d2tk_base_pre(d2tk_base_t *base)
base->mouse.dx = (int32_t)base->mouse.x - base->mouse.ox;
base->mouse.dy = (int32_t)base->mouse.y - base->mouse.oy;
- // reset again flag
- base->again = false;
-
// reset clear-focus flag
base->clear_focus = false;
@@ -4030,25 +797,45 @@ d2tk_base_post(d2tk_base_t *base)
base->focused = false;
}
+ _d2tk_base_clear_chars(base);
+
d2tk_core_post(base->core);
}
D2TK_API void
+d2tk_base_probe(d2tk_base_t *base)
+{
+ for(unsigned i = 0; i < _D2TK_MAX_ATOM; i++)
+ {
+ d2tk_atom_t *atom = &base->atoms[i];
+
+ if(atom->id && atom->type && atom->event)
+ {
+ if(atom->event(D2TK_ATOM_EVENT_PROBE, atom->body))
+ {
+ d2tk_base_set_again(base);
+ break;
+ }
+ }
+ }
+}
+
+D2TK_API void
d2tk_base_clear_focus(d2tk_base_t *base)
{
base->clear_focus = true;
}
-D2TK_API void
+D2TK_API bool
d2tk_base_set_again(d2tk_base_t *base)
{
- base->again = true;
+ return atomic_exchange(&base->again, true);
}
D2TK_API bool
d2tk_base_get_again(d2tk_base_t *base)
{
- return base->again;
+ return atomic_exchange(&base->again, false);
}
D2TK_API void
@@ -4204,3 +991,9 @@ d2tk_base_get_dimensions(d2tk_base_t *base, d2tk_coord_t *w, d2tk_coord_t *h)
{
d2tk_core_get_dimensions(base->core, w, h);
}
+
+D2TK_API void
+d2tk_base_set_full_refresh(d2tk_base_t *base)
+{
+ d2tk_core_set_full_refresh(base->core);
+}
diff --git a/src/base_bitmap.c b/src/base_bitmap.c
new file mode 100644
index 0000000..a542169
--- /dev/null
+++ b/src/base_bitmap.c
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+D2TK_API void
+d2tk_base_bitmap(d2tk_base_t *base, uint32_t w, uint32_t h, uint32_t stride,
+ const uint32_t *argb, uint64_t rev, const d2tk_rect_t *rect,
+ d2tk_align_t align)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(d2tk_rect_t) },
+ { &w, sizeof(uint32_t) },
+ { &h, sizeof(uint32_t) },
+ { &stride, sizeof(uint32_t) },
+ { &rev, sizeof(uint64_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;;
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_bitmap(core, rect, w, h, stride, argb, rev, align);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+}
diff --git a/src/base_button.c b/src/base_button.c
new file mode 100644
index 0000000..bd0df6c
--- /dev/null
+++ b/src/base_button.c
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+static inline void
+_d2tk_base_draw_button(d2tk_core_t *core, ssize_t lbl_len, const char *lbl,
+ d2tk_align_t align, ssize_t path_len, const char *path,
+ const d2tk_rect_t *rect, d2tk_triple_t triple, const d2tk_style_t *style)
+{
+ const bool has_lbl = lbl_len && lbl;
+ const bool has_img = path_len && path;
+
+ if(has_lbl && (lbl_len == -1) ) // zero-terminated string
+ {
+ lbl_len = strlen(lbl);
+ }
+
+ if(has_img && (path_len == -1) ) // zero-terminated string
+ {
+ path_len = strlen(path);
+ }
+
+ const d2tk_hash_dict_t dict [] = {
+ { &triple, sizeof(d2tk_triple_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &align, sizeof(d2tk_align_t) },
+ { (lbl ? lbl : path), (lbl ? lbl_len : path_len) },
+ { path, path_len },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_rect_t bnd_outer;
+ d2tk_rect_t bnd_inner;
+ d2tk_rect_shrink(&bnd_outer, rect, style->padding);
+ d2tk_rect_shrink(&bnd_inner, &bnd_outer, 2*style->padding);
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ if(has_lbl)
+ {
+ const d2tk_coord_t h_2 = rect->h / 2;
+ const d2tk_align_t lbl_align = align;
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd_inner);
+ d2tk_core_font_size(core, h_2);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[triple]);
+ d2tk_core_text(core, &bnd_inner, lbl_len, lbl, lbl_align);
+ d2tk_core_restore(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ if(has_img)
+ {
+ const d2tk_align_t img_align = D2TK_ALIGN_MIDDLE
+ | (has_lbl ? D2TK_ALIGN_RIGHT : D2TK_ALIGN_CENTER);
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_image(core, &bnd_inner, path_len, path, img_align);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_button_label_image(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
+ const char *lbl, d2tk_align_t align, ssize_t path_len, const char *path,
+ const d2tk_rect_t *rect)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
+
+ if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(state))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ _d2tk_base_draw_button(base->core, lbl_len, lbl, align, path_len, path, rect,
+ triple, d2tk_base_get_style(base));
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_button_label(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
+ const char *lbl, d2tk_align_t align, const d2tk_rect_t *rect)
+{
+ return d2tk_base_button_label_image(base, id, lbl_len, lbl,
+ align, 0, NULL, rect);
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_button_image(d2tk_base_t *base, d2tk_id_t id, ssize_t path_len,
+ const char *path, const d2tk_rect_t *rect)
+{
+ return d2tk_base_button_label_image(base, id, 0, NULL,
+ D2TK_ALIGN_NONE, path_len, path, rect);
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_button(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect)
+{
+ return d2tk_base_button_label_image(base, id, 0, NULL,
+ D2TK_ALIGN_NONE, 0, NULL, rect);
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_toggle_label(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len,
+ const char *lbl, d2tk_align_t align, const d2tk_rect_t *rect, bool *value)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
+
+ if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
+ {
+ *value = !*value;
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(*value)
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ _d2tk_base_draw_button(base->core, lbl_len, lbl, align, 0, NULL, rect, triple,
+ d2tk_base_get_style(base));
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_toggle(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ bool *value)
+{
+ return d2tk_base_toggle_label(base, id, 0, NULL,
+ D2TK_ALIGN_NONE, rect, value);
+}
diff --git a/src/base_combo.c b/src/base_combo.c
new file mode 100644
index 0000000..3ab073c
--- /dev/null
+++ b/src/base_combo.c
@@ -0,0 +1,251 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+static inline void
+_d2tk_base_draw_combo(d2tk_core_t *core, ssize_t nitms, const char **itms,
+ const d2tk_rect_t *rect, d2tk_state_t state, int32_t value,
+ const d2tk_style_t *style)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &value, sizeof(int32_t) },
+ { &nitms, sizeof(ssize_t) },
+ { itms, sizeof(const char **) }, //FIXME we should actually cache the labels
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ const d2tk_coord_t w_2 = bnd.w / 2;
+ const d2tk_coord_t w_4 = bnd.w / 4;
+
+ d2tk_rect_t left = bnd;
+ left.x -= w_4;
+ left.w = w_2;
+
+ d2tk_rect_t midd = bnd;
+ midd.x += w_4;
+ midd.w = w_2;
+
+ d2tk_rect_t right = bnd;
+ right.x += w_2 + w_4;
+ right.w = w_2;
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ uint32_t fill_color_2 = style->fill_color[triple];
+ fill_color_2 = (fill_color_2 & 0xffffff00) | (fill_color_2 & 0xff / 2);
+
+ // left filling
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &left);
+ d2tk_core_color(core, fill_color_2);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ // middle filling
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &midd);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ // right filling
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &right);
+ d2tk_core_color(core, fill_color_2);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ // draw lines above and below text
+ const d2tk_coord_t h_8 = bnd.h / 8;
+ const d2tk_coord_t dx = bnd.w / nitms;
+ const d2tk_coord_t x0 = bnd.x;
+ const d2tk_coord_t x1 = bnd.x + value*dx;
+ const d2tk_coord_t x2 = x1 + dx;
+ const d2tk_coord_t x3 = bnd.x + bnd.w;
+ const d2tk_coord_t y0 = bnd.y + h_8;
+ const d2tk_coord_t y1 = bnd.y + bnd.h - h_8;
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_line_to(core, x3, y0);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y1);
+ d2tk_core_line_to(core, x3, y1);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width*2);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x1, y0);
+ d2tk_core_line_to(core, x2, y0);
+ d2tk_core_color(core, style->fill_color[triple | D2TK_TRIPLE_ACTIVE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x1, y1);
+ d2tk_core_line_to(core, x2, y1);
+ d2tk_core_color(core, style->fill_color[triple | D2TK_TRIPLE_ACTIVE]);
+ d2tk_core_stroke_width(core, style->border_width*2);
+ d2tk_core_stroke(core);
+
+ // draw bounding box
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ // left label
+ if(value > 0)
+ {
+ const char *lbl = itms[value - 1];
+ const size_t lbl_len = lbl ? strlen(lbl) : 0;
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &left);
+ d2tk_core_font_size(core, left.h / 2);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_text(core, &left, lbl_len, lbl, D2TK_ALIGN_CENTERED);
+ d2tk_core_restore(core);
+ }
+
+ // middle label
+ {
+ const char *lbl = itms[value];
+ const size_t lbl_len = lbl ? strlen(lbl) : 0;
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &midd);
+ d2tk_core_font_size(core, midd.h / 2);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[triple]);
+ d2tk_core_text(core, &midd, lbl_len, lbl, D2TK_ALIGN_CENTERED);
+ d2tk_core_restore(core);
+ }
+
+ // right label
+ if(value < (nitms-1) )
+ {
+ const char *lbl = itms[value + 1];
+ const size_t lbl_len = lbl ? strlen(lbl) : 0;
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &right);
+ d2tk_core_font_size(core, right.h / 2);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_text(core, &right, lbl_len, lbl, D2TK_ALIGN_CENTERED);
+ d2tk_core_restore(core);
+ }
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_combo(d2tk_base_t *base, d2tk_id_t id, ssize_t nitms,
+ const char **itms, const d2tk_rect_t *rect, int32_t *value)
+{
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL_X | D2TK_FLAG_SCROLL_Y);
+
+ const int32_t old_value = *value;
+
+ if( d2tk_state_is_scroll_up(state)
+ || d2tk_state_is_scroll_right(state)
+ || d2tk_state_is_enter(state) )
+ {
+ *value += 1;
+ }
+
+ if( d2tk_state_is_scroll_down(state)
+ || d2tk_state_is_scroll_left(state))
+ {
+ *value -= 1;
+ }
+
+ if(d2tk_state_is_down(state))
+ {
+ const d2tk_coord_t w_2 = rect->w/2;
+ const d2tk_coord_t w_4 = rect->w/4;
+
+ const d2tk_coord_t x1 = rect->x + w_4;
+ const d2tk_coord_t x2 = x1 + w_2;
+
+ if(base->mouse.x < x1)
+ {
+ *value -= 1;
+ }
+ else if(base->mouse.x > x2)
+ {
+ *value += 1;
+ }
+ }
+
+ d2tk_clip_int32(0, value, nitms-1);
+
+ if(*value != old_value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ d2tk_core_t *core = base->core;
+
+ _d2tk_base_draw_combo(core, nitms, itms, rect, state, *value, style);
+
+ return state;
+}
diff --git a/src/base_cursor.c b/src/base_cursor.c
new file mode 100644
index 0000000..e9be4ed
--- /dev/null
+++ b/src/base_cursor.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+D2TK_API void
+d2tk_base_cursor(d2tk_base_t *base, const d2tk_rect_t *rect)
+{
+ d2tk_core_t *core = base->core;
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(rect) },
+ { style, sizeof(d2tk_style_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const d2tk_coord_t x0 = rect->x;
+ const d2tk_coord_t x1 = x0 + rect->w/2;
+ const d2tk_coord_t x2 = x0 + rect->w;
+ const d2tk_coord_t y0 = rect->y;
+ const d2tk_coord_t y1 = y0 + rect->h/2;
+ const d2tk_coord_t y2 = y0 + rect->h;
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_line_to(core, x1, y2);
+ d2tk_core_line_to(core, x1, y1);
+ d2tk_core_line_to(core, x2, y1);
+ d2tk_core_close_path(core);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_FOCUS]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_line_to(core, x1, y2);
+ d2tk_core_line_to(core, x1, y1);
+ d2tk_core_line_to(core, x2, y1);
+ d2tk_core_close_path(core);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, 2*style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+}
diff --git a/src/base_custom.c b/src/base_custom.c
new file mode 100644
index 0000000..c001e5a
--- /dev/null
+++ b/src/base_custom.c
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+D2TK_API void
+d2tk_base_custom(d2tk_base_t *base, uint32_t size, const void *data,
+ const d2tk_rect_t *rect, d2tk_core_custom_t custom)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(d2tk_rect_t) } ,
+ { data, size }, //FIXME
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;;
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_custom(core, rect, size, data, custom);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+}
diff --git a/src/base_dial.c b/src/base_dial.c
new file mode 100644
index 0000000..989ce36
--- /dev/null
+++ b/src/base_dial.c
@@ -0,0 +1,499 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <inttypes.h>
+#include <math.h>
+#include <stdio.h>
+
+#include "base_internal.h"
+
+D2TK_API d2tk_state_t
+d2tk_base_dial_bool(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ bool *value)
+{
+ const bool oldvalue = *value;
+
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL);
+
+ if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
+ {
+ *value = !*value;
+ }
+ else if(d2tk_state_is_scroll_up(state))
+ {
+ *value = true;
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ *value = false;
+ }
+
+ if(oldvalue != *value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+ d2tk_core_t *core = base->core;
+
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { value, sizeof(bool) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(*value)
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ const d2tk_coord_t d = bnd.h < bnd.w ? bnd.h : bnd.w;
+ const d2tk_coord_t r1 = d / 2;
+ const d2tk_coord_t r0 = d / 3;
+ bnd.x += bnd.w / 2;
+ bnd.y += bnd.h / 2;
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, r1, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, r1, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, r0, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, r0, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ return state;
+}
+
+static inline void
+_d2tk_base_draw_dial(d2tk_core_t *core, const d2tk_rect_t *rect,
+ d2tk_state_t state, float rel, const d2tk_style_t *style)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &rel, sizeof(float) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+ d2tk_triple_t triple_active = D2TK_TRIPLE_ACTIVE;
+ d2tk_triple_t triple_inactive = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(state))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ triple_active |= D2TK_TRIPLE_HOT;
+ triple_inactive |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ triple_active |= D2TK_TRIPLE_FOCUS;
+ triple_inactive |= D2TK_TRIPLE_FOCUS;
+ }
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ const d2tk_coord_t d = bnd.h < bnd.w ? bnd.h : bnd.w;
+ const d2tk_coord_t r1 = d / 2;
+ const d2tk_coord_t r0 = d / 4;
+ bnd.x += bnd.w / 2;
+ bnd.y += bnd.h / 2;
+
+ static const d2tk_coord_t a = 90 + 22; //FIXME
+ static const d2tk_coord_t c = 90 - 22; //FIXME
+ const d2tk_coord_t b = a + (360 - 44)*rel; //FIXME
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, (r1 + r0)/2, a, c, true);
+ d2tk_core_color(core, style->fill_color[triple_inactive]);
+ d2tk_core_stroke_width(core, r1 - r0);
+ d2tk_core_stroke(core);
+
+ if(rel > 0.f)
+ {
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, (r1 + r0)/2, a, b, true);
+ d2tk_core_color(core, style->fill_color[triple_active]);
+ d2tk_core_stroke_width(core, (r1 - r0) * 3/4);
+ d2tk_core_stroke(core);
+ }
+
+ const float phi = (b + 90) / 180.f * M_PI;
+ const d2tk_coord_t rx1 = bnd.x + r0 * sinf(phi);
+ const d2tk_coord_t ry1 = bnd.y - r0 * cosf(phi);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, bnd.x, bnd.y);
+ d2tk_core_line_to(core, rx1, ry1);
+ d2tk_core_close_path(core);
+ d2tk_core_color(core, style->fill_color[triple_active]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, bnd.x, bnd.y, r1, a, c, true);
+ d2tk_core_arc(core, bnd.x, bnd.y, r0, c, a, false);
+ d2tk_core_close_path(core);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_dial_int32(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ int32_t min, int32_t *value, int32_t max)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL);
+
+ const int32_t oldvalue = *value;
+
+ if(d2tk_state_is_scroll_up(state))
+ {
+ *value += base->scroll.ody;
+ d2tk_clip_int32(min, value, max);
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ *value += base->scroll.ody;
+ d2tk_clip_int32(min, value, max);
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ const int32_t adx = abs(base->mouse.dx);
+ const int32_t ady = abs(base->mouse.dy);
+ const int32_t adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
+
+ *value += adz;
+ d2tk_clip_int32(min, value, max);
+ }
+
+ if(oldvalue != *value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ float rel = (float)(*value - min) / (max - min);
+ d2tk_clip_float(0.f, &rel, 1.f);
+
+ d2tk_core_t *core = base->core;
+ _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_prop_int32(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ int32_t min, int32_t *value, int32_t max)
+{
+ const d2tk_coord_t dw = rect->w / 3;
+
+ const d2tk_rect_t left = {
+ .x = rect->x,
+ .y = rect->y,
+ .w = dw,
+ .h = rect->h
+ };
+ const d2tk_rect_t right = {
+ .x = rect->x + dw,
+ .y = rect->y,
+ .w = rect->w - dw,
+ .h = rect->h
+ };
+
+ const d2tk_id_t id_left = (1 << 24) | id;
+ const d2tk_id_t id_right = (2 << 24) | id;
+
+ const d2tk_state_t state_dial = d2tk_base_dial_int32(base, id_left, &left,
+ min, value, max);
+
+ char text [32];
+ snprintf(text, sizeof(text), "%+"PRIi32, *value);
+
+ const d2tk_state_t state_field = d2tk_base_text_field(base, id_right, &right,
+ sizeof(text), text, D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE, "1234567890+-");
+
+ if(d2tk_state_is_changed(state_field))
+ {
+ d2tk_base_set_again(base);
+ }
+
+ const d2tk_state_t state = state_dial | state_field;
+
+ if(d2tk_state_is_focus_out(state))
+ {
+ int32_t val;
+ if(sscanf(text, "%"SCNi32, &val) == 1)
+ {
+ *value = val;
+ d2tk_clip_int32(min, value, max);
+ }
+ }
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_dial_int64(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ int64_t min, int64_t *value, int64_t max)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL);
+
+ const int64_t oldvalue = *value;
+
+ if(d2tk_state_is_scroll_up(state))
+ {
+ *value += base->scroll.ody;
+ d2tk_clip_int64(min, value, max);
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ *value += base->scroll.ody;
+ d2tk_clip_int64(min, value, max);
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ const int64_t adx = abs(base->mouse.dx);
+ const int64_t ady = abs(base->mouse.dy);
+ const int64_t adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
+
+ *value += adz;
+ d2tk_clip_int64(min, value, max);
+ }
+
+ if(oldvalue != *value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ float rel = (float)(*value - min) / (max - min);
+ d2tk_clip_float(0.f, &rel, 1.f);
+
+ d2tk_core_t *core = base->core;
+ _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_dial_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ float min, float *value, float max)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL);
+
+ const float oldvalue = *value;
+
+ if(d2tk_state_is_scroll_up(state))
+ {
+ const float dv = (max - min);
+ const float mul = d2tk_base_get_mod(base) ? 0.01f : 0.1f;
+ *value += dv * mul * base->scroll.ody;
+ d2tk_clip_float(min, value, max);
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ const float dv = (max - min);
+ const float mul = d2tk_base_get_mod(base) ? 0.01f : 0.1f;
+ *value += dv * mul * base->scroll.ody;
+ d2tk_clip_float(min, value, max);
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ const float adx = abs(base->mouse.dx);
+ const float ady = abs(base->mouse.dy);
+ const float adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
+
+ const float dv = (max - min);
+ const float mul = d2tk_base_get_mod(base) ? 0.001f : 0.01f;
+ *value += dv * adz * mul;
+ d2tk_clip_float(min, value, max);
+ }
+
+ if(oldvalue != *value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ float rel = (*value - min) / (max - min);
+ d2tk_clip_float(0.f, &rel, 1.f);
+
+ d2tk_core_t *core = base->core;
+ _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_prop_float(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ float min, float *value, float max)
+{
+ const d2tk_coord_t dw = rect->w / 3;
+
+ const d2tk_rect_t left = {
+ .x = rect->x,
+ .y = rect->y,
+ .w = dw,
+ .h = rect->h
+ };
+ const d2tk_rect_t right = {
+ .x = rect->x + dw,
+ .y = rect->y,
+ .w = rect->w - dw,
+ .h = rect->h
+ };
+
+ const d2tk_id_t id_left = (1 << 24) | id;
+ const d2tk_id_t id_right = (2 << 24) | id;
+
+ const d2tk_state_t state_dial = d2tk_base_dial_float(base, id_left, &left,
+ min, value, max);
+
+ char text [32];
+ snprintf(text, sizeof(text), "%+.4f", *value);
+
+ const d2tk_state_t state_field = d2tk_base_text_field(base, id_right, &right,
+ sizeof(text), text, D2TK_ALIGN_RIGHT | D2TK_ALIGN_MIDDLE, "1234567890.+-");
+
+ if(d2tk_state_is_changed(state_field))
+ {
+ d2tk_base_set_again(base);
+ }
+
+ const d2tk_state_t state = state_dial | state_field;
+
+ if(d2tk_state_is_focus_out(state))
+ {
+ float val;
+ if(sscanf(text, "%f", &val) == 1)
+ {
+ *value = val;
+ d2tk_clip_float(min, value, max);
+ }
+ }
+
+ return state;
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_dial_double(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ double min, double *value, double max)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_SCROLL);
+
+ const double oldvalue = *value;
+
+ if(d2tk_state_is_scroll_up(state))
+ {
+ const double dv = (max - min);
+ const double mul = d2tk_base_get_mod(base) ? 0.01 : 0.1;
+ *value += dv * mul * base->scroll.ody;
+ d2tk_clip_double(min, value, max);
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ const double dv = (max - min);
+ const double mul = d2tk_base_get_mod(base) ? 0.01 : 0.1;
+ *value += dv * mul * base->scroll.ody;
+ d2tk_clip_double(min, value, max);
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ const double adx = abs(base->mouse.dx);
+ const double ady = abs(base->mouse.dy);
+ const double adz = adx > ady ? base->mouse.dx : -base->mouse.dy;
+
+ const double dv = (max - min);
+ const double mul = d2tk_base_get_mod(base) ? 0.001 : 0.01;
+ *value += dv * adz * mul;
+ d2tk_clip_double(min, value, max);
+ }
+
+ if(oldvalue != *value)
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ float rel = (*value - min) / (max - min);
+ d2tk_clip_float(0.f, &rel, 1.f);
+
+ d2tk_core_t *core = base->core;
+ _d2tk_base_draw_dial(core, rect, state, rel, d2tk_base_get_style(base));
+
+ return state;
+}
diff --git a/src/base_flowmatrix.c b/src/base_flowmatrix.c
new file mode 100644
index 0000000..0edc1c5
--- /dev/null
+++ b/src/base_flowmatrix.c
@@ -0,0 +1,806 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <math.h>
+#include <string.h>
+
+#include "base_internal.h"
+
+typedef struct _d2tk_atom_body_flow_t d2tk_atom_body_flow_t;
+
+struct _d2tk_atom_body_flow_t {
+ d2tk_coord_t x;
+ d2tk_coord_t y;
+ d2tk_coord_t lx;
+ d2tk_coord_t ly;
+ float exponent;
+ d2tk_id_t src_id;
+ d2tk_id_t dst_id;
+};
+
+struct _d2tk_flowmatrix_t {
+ d2tk_base_t *base;
+ d2tk_id_t id;
+ const d2tk_rect_t *rect;
+ d2tk_atom_body_flow_t *atom_body;
+ float scale;
+ d2tk_coord_t cx;
+ d2tk_coord_t cy;
+ size_t ref;
+ d2tk_coord_t w;
+ d2tk_coord_t h;
+ d2tk_coord_t dd;
+ d2tk_coord_t r;
+ d2tk_coord_t s;
+ bool src_conn;
+ bool dst_conn;
+ d2tk_pos_t src_pos;
+ d2tk_pos_t dst_pos;
+};
+
+struct _d2tk_flowmatrix_node_t {
+ d2tk_flowmatrix_t *flowmatrix;
+ d2tk_rect_t rect;
+};
+
+struct _d2tk_flowmatrix_arc_t {
+ d2tk_flowmatrix_t *flowmatrix;
+ unsigned x;
+ unsigned y;
+ unsigned N;
+ unsigned M;
+ unsigned NM;
+ unsigned k;
+ d2tk_coord_t c;
+ d2tk_coord_t c_2;
+ d2tk_coord_t c_4;
+ d2tk_coord_t xo;
+ d2tk_coord_t yo;
+ d2tk_rect_t rect;
+};
+
+const size_t d2tk_atom_body_flow_sz = sizeof(d2tk_atom_body_flow_t);
+const size_t d2tk_flowmatrix_sz = sizeof(d2tk_flowmatrix_t);
+const size_t d2tk_flowmatrix_node_sz = sizeof(d2tk_flowmatrix_node_t);
+const size_t d2tk_flowmatrix_arc_sz = sizeof(d2tk_flowmatrix_arc_t);
+
+static d2tk_coord_t
+_d2tk_flowmatrix_abs_x(d2tk_flowmatrix_t *flowmatrix, d2tk_coord_t rel_x)
+{
+ return flowmatrix->cx + rel_x * flowmatrix->scale;
+}
+
+static d2tk_coord_t
+_d2tk_flowmatrix_abs_y(d2tk_flowmatrix_t *flowmatrix, d2tk_coord_t rel_y)
+{
+ return flowmatrix->cy + rel_y * flowmatrix->scale;
+}
+
+static void
+_d2tk_flowmatrix_connect(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
+ const d2tk_pos_t *src_pos, const d2tk_pos_t *dst_pos)
+{
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ d2tk_pos_t dst;
+ if(!dst_pos) // connect to mouse pointer
+ {
+ d2tk_base_get_mouse_pos(base, &dst.x, &dst.y);
+ }
+
+ const d2tk_hash_dict_t dict [] = {
+ { flowmatrix, sizeof(d2tk_flowmatrix_t) },
+ { src_pos, sizeof(d2tk_pos_t) },
+ { dst_pos ? dst_pos : &dst, sizeof(d2tk_pos_t) },
+ { style, sizeof(d2tk_style_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const d2tk_coord_t w = flowmatrix->w;
+ const d2tk_coord_t r = flowmatrix->r;
+ const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, src_pos->x) + w/2 + r;
+ const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, src_pos->y);
+
+ if(dst_pos)
+ {
+ dst.x = _d2tk_flowmatrix_abs_x(flowmatrix, dst_pos->x) - w/2 - r;
+ dst.y = _d2tk_flowmatrix_abs_y(flowmatrix, dst_pos->y);
+ }
+
+ const d2tk_coord_t x0 = (x < dst.x) ? x : dst.x;
+ const d2tk_coord_t y0 = (y < dst.y) ? y : dst.y;
+ const d2tk_coord_t x1 = (x > dst.x) ? x : dst.x;
+ const d2tk_coord_t y1 = (y > dst.y) ? y : dst.y;
+
+ const d2tk_rect_t bnd = {
+ .x = x0 - 1,
+ .y = y0 - 1,
+ .w = x1 - x0 + 2,
+ .h = y1 - y0 + 2
+ };
+
+ const d2tk_triple_t triple = D2TK_TRIPLE_FOCUS;
+
+ const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x, y);
+ d2tk_core_line_to(core, dst.x, dst.y);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+}
+
+D2TK_API d2tk_flowmatrix_t *
+d2tk_flowmatrix_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
+ d2tk_flowmatrix_t *flowmatrix)
+{
+ memset(flowmatrix, 0x0, sizeof(d2tk_flowmatrix_t));
+
+ flowmatrix->base = base;
+ flowmatrix->id = id;
+ flowmatrix->rect = rect;
+ flowmatrix->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_FLOW, NULL);
+ flowmatrix->scale = exp2f(flowmatrix->atom_body->exponent); //FIXME cache this instead
+ flowmatrix->cx = flowmatrix->rect->x + flowmatrix->rect->w / 2
+ - flowmatrix->atom_body->x * flowmatrix->scale;
+ flowmatrix->cy = flowmatrix->rect->y + flowmatrix->rect->h / 2
+ - flowmatrix->atom_body->y * flowmatrix->scale;
+
+ flowmatrix->w = flowmatrix->scale * 150; //FIXME
+ flowmatrix->h = flowmatrix->scale * 25; //FIXME
+ flowmatrix->dd = flowmatrix->scale * 40; //FIXME
+ flowmatrix->r = flowmatrix->scale * 4; //FIXME
+ flowmatrix->s = flowmatrix->scale * 20; //FIXME
+
+ d2tk_core_t *core = base->core;
+ flowmatrix->ref = d2tk_core_bbox_container_push(core, false, flowmatrix->rect);
+
+ return flowmatrix;
+}
+
+D2TK_API bool
+d2tk_flowmatrix_not_end(d2tk_flowmatrix_t *flowmatrix)
+{
+ return flowmatrix ? true : false;
+}
+
+D2TK_API d2tk_flowmatrix_t *
+d2tk_flowmatrix_next(d2tk_flowmatrix_t *flowmatrix)
+{
+ d2tk_base_t *base = flowmatrix->base;
+ float *exponent = &flowmatrix->atom_body->exponent;
+ const float old_exponent = *exponent;
+
+ const d2tk_state_t state = d2tk_base_is_active_hot(base, flowmatrix->id,
+ flowmatrix->rect, D2TK_FLAG_SCROLL_Y);
+
+ if(d2tk_state_is_scroll_down(state))
+ {
+ *exponent -= 0.125f; //FIXME
+ }
+ else if(d2tk_state_is_scroll_up(state))
+ {
+ *exponent += 0.125f; //FIXME
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
+ const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
+
+ flowmatrix->atom_body->x -= adx;
+ flowmatrix->atom_body->y -= ady;
+ }
+
+ d2tk_clip_float(-2.f, exponent, 1.f); //FIXME
+
+ if(*exponent != old_exponent)
+ {
+ const d2tk_coord_t ox = (base->mouse.x - flowmatrix->cx) / flowmatrix->scale;
+ const d2tk_coord_t oy = (base->mouse.y - flowmatrix->cy) / flowmatrix->scale;
+
+ const float scale = exp2f(*exponent);
+
+ const d2tk_coord_t fx = base->mouse.x - (ox * scale);
+ const d2tk_coord_t fy = base->mouse.y - (oy * scale);
+
+ flowmatrix->atom_body->x = (flowmatrix->rect->x + flowmatrix->rect->w / 2 - fx) / scale;
+ flowmatrix->atom_body->y = (flowmatrix->rect->y + flowmatrix->rect->h / 2 - fy) / scale;
+
+ d2tk_base_set_again(base);
+ }
+
+ if(flowmatrix->src_conn)
+ {
+ if(flowmatrix->dst_conn)
+ {
+ _d2tk_flowmatrix_connect(base, flowmatrix, &flowmatrix->src_pos,
+ &flowmatrix->dst_pos);
+ }
+ else
+ {
+ _d2tk_flowmatrix_connect(base, flowmatrix, &flowmatrix->src_pos, NULL);
+
+ // invalidate dst_id
+ flowmatrix->atom_body->dst_id = 0;
+ }
+ }
+
+ d2tk_core_t *core = base->core;
+ d2tk_core_bbox_pop(core, flowmatrix->ref);
+
+ return NULL;
+}
+
+static void
+_d2tk_flowmatrix_next_pos(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
+{
+ flowmatrix->atom_body->lx += 150; //FIXME
+ flowmatrix->atom_body->ly += 25; //FIXME
+
+ pos->x = flowmatrix->atom_body->lx;
+ pos->y = flowmatrix->atom_body->ly;
+}
+
+D2TK_API void
+d2tk_flowmatrix_set_src(d2tk_flowmatrix_t *flowmatrix, d2tk_id_t id,
+ const d2tk_pos_t *pos)
+{
+ flowmatrix->src_conn = true;
+ flowmatrix->atom_body->src_id = id;
+
+ if(pos)
+ {
+ flowmatrix->src_pos = *pos;
+ }
+}
+
+D2TK_API void
+d2tk_flowmatrix_set_dst(d2tk_flowmatrix_t *flowmatrix, d2tk_id_t id,
+ const d2tk_pos_t *pos)
+{
+ flowmatrix->dst_conn = true;
+ flowmatrix->atom_body->dst_id = id;
+
+ if(pos)
+ {
+ flowmatrix->dst_pos = *pos;
+ }
+}
+
+D2TK_API d2tk_id_t
+d2tk_flowmatrix_get_src(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
+{
+ if(pos)
+ {
+ *pos = flowmatrix->src_pos;
+ }
+
+ return flowmatrix->atom_body->src_id;
+}
+
+D2TK_API d2tk_id_t
+d2tk_flowmatrix_get_dst(d2tk_flowmatrix_t *flowmatrix, d2tk_pos_t *pos)
+{
+ if(pos)
+ {
+ *pos = flowmatrix->dst_pos;
+ }
+
+ return flowmatrix->atom_body->dst_id;
+}
+
+D2TK_API d2tk_flowmatrix_node_t *
+d2tk_flowmatrix_node_begin(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
+ d2tk_pos_t *pos, d2tk_flowmatrix_node_t *node)
+{
+ node->flowmatrix = flowmatrix;
+
+ // derive initial position
+ if( (pos->x == 0) && (pos->y == 0) )
+ {
+ _d2tk_flowmatrix_next_pos(flowmatrix, pos);
+ }
+
+ const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, pos->x);
+ const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, pos->y);
+ const d2tk_coord_t w = flowmatrix->w;
+ const d2tk_coord_t h = flowmatrix->h;
+
+ node->rect.x = x - w/2;
+ node->rect.y = y - h/2;
+ node->rect.w = w;
+ node->rect.h = h;
+
+ d2tk_core_t *core = base->core;
+ d2tk_coord_t cw;
+ d2tk_coord_t ch;
+
+ d2tk_core_get_dimensions(core, &cw, &ch);
+ if( (node->rect.x >= cw)
+ || (node->rect.y >= ch)
+ || (node->rect.x <= -node->rect.w)
+ || (node->rect.y <= -node->rect.h) )
+ {
+ return NULL;
+ }
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ const d2tk_hash_dict_t dict [] = {
+ { flowmatrix, sizeof(d2tk_flowmatrix_t) },
+ { pos, sizeof(d2tk_pos_t) },
+ { node, sizeof(d2tk_flowmatrix_node_t) },
+ { style, sizeof(d2tk_style_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const d2tk_coord_t r = flowmatrix->r;
+ const d2tk_coord_t r2 = r*2;
+ const d2tk_triple_t triple = D2TK_TRIPLE_NONE; //FIXME
+
+ // sink connection point
+ {
+ const d2tk_coord_t x0 = node->rect.x - r;
+
+ const d2tk_rect_t bnd = {
+ .x = x0 - r,
+ .y = y - r,
+ .w = r2,
+ .h = r2
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x0, y, r, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x0, y, r, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ // source connection point
+ {
+ const d2tk_coord_t x0 = node->rect.x + node->rect.w + r;
+
+ const d2tk_rect_t bnd = {
+ .x = x0 - r,
+ .y = y - r,
+ .w = r2,
+ .h = r2
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x0, y, r, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x0, y, r, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+
+ return node;
+}
+
+D2TK_API bool
+d2tk_flowmatrix_node_not_end(d2tk_flowmatrix_node_t *node)
+{
+ return node ? true : false;
+}
+
+D2TK_API d2tk_flowmatrix_node_t *
+d2tk_flowmatrix_node_next(d2tk_flowmatrix_node_t *node, d2tk_pos_t *pos,
+ const d2tk_state_t *state)
+{
+ d2tk_flowmatrix_t *flowmatrix = node->flowmatrix;
+ d2tk_base_t *base = flowmatrix->base;
+
+ if(d2tk_state_is_motion(*state))
+ {
+ const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
+ const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
+
+ pos->x += adx;
+ pos->y += ady;
+
+ d2tk_base_set_again(base);
+ }
+
+ return NULL;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_flowmatrix_node_get_rect(d2tk_flowmatrix_node_t *node)
+{
+ return &node->rect;
+}
+
+D2TK_API d2tk_flowmatrix_arc_t *
+d2tk_flowmatrix_arc_begin(d2tk_base_t *base, d2tk_flowmatrix_t *flowmatrix,
+ unsigned N, unsigned M, const d2tk_pos_t *src, const d2tk_pos_t *dst,
+ d2tk_pos_t *pos, d2tk_flowmatrix_arc_t *arc)
+{
+ memset(arc, 0x0, sizeof(d2tk_flowmatrix_arc_t));
+
+ // derive initial position
+ if( (pos->x == 0) && (pos->y == 0) )
+ {
+ pos->x = (src->x + dst->x) / 2;
+ pos->y = (src->y + dst->y) / 2;
+ }
+
+ arc->flowmatrix = flowmatrix;
+ arc->x = 0;
+ arc->y = 0;
+ arc->N = N+1;
+ arc->M = M+1;
+ arc->NM = (N+1)*(M+1);
+ arc->k = 0;
+
+ const d2tk_coord_t x = _d2tk_flowmatrix_abs_x(flowmatrix, pos->x);
+ const d2tk_coord_t y = _d2tk_flowmatrix_abs_y(flowmatrix, pos->y);
+ const d2tk_coord_t s = flowmatrix->s;
+ arc->c = M_SQRT2 * s;
+ arc->c_2 = arc->c / 2;
+ arc->c_4 = arc->c / 4;
+
+ const d2tk_coord_t x0 = x - M*arc->c_2;
+ const d2tk_coord_t x1 = x;
+ const d2tk_coord_t x2 = x + (N - M)*arc->c_2;
+ const d2tk_coord_t x3 = x + N*arc->c_2;
+
+ const d2tk_coord_t y0 = y;
+ const d2tk_coord_t y1 = y + M*arc->c_2;
+ const d2tk_coord_t y2 = y + N*arc->c_2;
+ const d2tk_coord_t y3 = y + (N+M)*arc->c_2;
+
+ arc->xo = x1 - arc->c_2;
+ arc->yo = y0 + arc->c_4;
+ arc->rect.x = arc->xo;
+ arc->rect.y = arc->yo;
+ arc->rect.w = arc->c;
+ arc->rect.h = arc->c_2;
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ const d2tk_hash_dict_t dict [] = {
+ { flowmatrix, sizeof(d2tk_flowmatrix_t) },
+ { &N, sizeof(unsigned) },
+ { &M, sizeof(unsigned) },
+ { src, sizeof(d2tk_pos_t) },
+ { dst, sizeof(d2tk_pos_t) },
+ { pos, sizeof(d2tk_pos_t) },
+ { arc, sizeof(d2tk_flowmatrix_arc_t) },
+ { style, sizeof(d2tk_style_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const d2tk_coord_t r = flowmatrix->r;
+ const d2tk_coord_t ox = flowmatrix->w/2;
+ const d2tk_coord_t dd = flowmatrix->dd;
+ const d2tk_triple_t triple = D2TK_TRIPLE_NONE; //FIXME
+ const d2tk_coord_t b = style->border_width;
+ const d2tk_coord_t b2 = b*2;
+
+ const d2tk_coord_t xs = _d2tk_flowmatrix_abs_x(flowmatrix, src->x) + ox + r;
+ const d2tk_coord_t ys = _d2tk_flowmatrix_abs_y(flowmatrix, src->y);
+ const d2tk_coord_t xp = x;
+ const d2tk_coord_t yp = y - r;
+ const d2tk_coord_t xd = _d2tk_flowmatrix_abs_x(flowmatrix, dst->x) - ox - r;
+ const d2tk_coord_t yd = _d2tk_flowmatrix_abs_y(flowmatrix, dst->y);
+
+ // sink arc
+ {
+ const d2tk_coord_t x0 = xs;
+ const d2tk_coord_t x1 = xs + dd;
+ const d2tk_coord_t x2 = xp - dd;
+ const d2tk_coord_t x3 = xp;
+
+ const d2tk_coord_t y0 = ys;
+ const d2tk_coord_t y1 = ys;
+ const d2tk_coord_t y2 = yp;
+ const d2tk_coord_t y3 = yp;
+
+ d2tk_coord_t xa = INT32_MAX;
+ d2tk_coord_t xb = INT32_MIN;
+ if(x0 < xa) xa = x0;
+ if(x1 < xa) xa = x1;
+ if(x2 < xa) xa = x2;
+ if(x3 < xa) xa = x3;
+ if(x0 > xb) xb = x0;
+ if(x1 > xb) xb = x1;
+ if(x2 > xb) xb = x2;
+ if(x3 > xb) xb = x3;
+
+ d2tk_coord_t ya = INT32_MAX;
+ d2tk_coord_t yb = INT32_MIN;
+ if(y0 < ya) ya = y0;
+ if(y1 < ya) ya = y1;
+ if(y2 < ya) ya = y2;
+ if(y3 < ya) ya = y3;
+ if(y0 > yb) yb = y0;
+ if(y1 > yb) yb = y1;
+ if(y2 > yb) yb = y2;
+ if(y3 > yb) yb = y3;
+
+ const d2tk_rect_t bnd = {
+ .x = xa - b,
+ .y = ya - b,
+ .w = xb - xa + b2,
+ .h = yb - ya + b2
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_curve_to(core, x1, y1, x2, y2, x3, y3);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, b);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ // source arc
+ {
+ const d2tk_coord_t x0 = xp;
+ const d2tk_coord_t x1 = xp + dd;
+ const d2tk_coord_t x2 = xd - dd;
+ const d2tk_coord_t x3 = xd;
+
+ const d2tk_coord_t y0 = yp;
+ const d2tk_coord_t y1 = yp;
+ const d2tk_coord_t y2 = yd;
+ const d2tk_coord_t y3 = yd;
+
+ d2tk_coord_t xa = INT32_MAX;
+ d2tk_coord_t xb = INT32_MIN;
+ if(x0 < xa) xa = x0;
+ if(x1 < xa) xa = x1;
+ if(x2 < xa) xa = x2;
+ if(x3 < xa) xa = x3;
+ if(x0 > xb) xb = x0;
+ if(x1 > xb) xb = x1;
+ if(x2 > xb) xb = x2;
+ if(x3 > xb) xb = x3;
+
+ d2tk_coord_t ya = INT32_MAX;
+ d2tk_coord_t yb = INT32_MIN;
+ if(y0 < ya) ya = y0;
+ if(y1 < ya) ya = y1;
+ if(y2 < ya) ya = y2;
+ if(y3 < ya) ya = y3;
+ if(y0 > yb) yb = y0;
+ if(y1 > yb) yb = y1;
+ if(y2 > yb) yb = y2;
+ if(y3 > yb) yb = y3;
+
+ const d2tk_rect_t bnd = {
+ .x = xa - b,
+ .y = ya - b,
+ .w = xb - xa + b2,
+ .h = yb - ya + b2
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, false, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_curve_to(core, x1, y1, x2, y2, x3, y3);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, b);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ // matrix
+ {
+ const d2tk_rect_t bnd = {
+ .x = x0,
+ .y = y0,
+ .w = x3 - x0,
+ .h = y3 - y0
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
+
+ // matrix bounding box
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y1);
+ d2tk_core_line_to(core, x1, y0);
+ d2tk_core_line_to(core, x3, y2);
+ d2tk_core_line_to(core, x2, y3);
+ d2tk_core_close_path(core);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ // grid lines
+ for(unsigned j = 0, o = 0;
+ j < M + 1;
+ j++, o += arc->c_2)
+ {
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0 + o, y1 - o);
+ d2tk_core_line_to(core, x2 + o, y3 - o);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+ }
+
+ // grid lines
+ for(unsigned i = 0, o = 0;
+ i < N + 1;
+ i++, o += arc->c_2)
+ {
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0 + o, y1 + o);
+ d2tk_core_line_to(core, x1 + o, y0 + o);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+ }
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ // connection point
+ {
+ const d2tk_coord_t r2 = r*2;
+ const d2tk_rect_t bnd = {
+ .x = xp - r,
+ .y = yp - r,
+ .w = r2,
+ .h = r2
+ };
+
+ const size_t ref = d2tk_core_bbox_push(core, true, &bnd);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, xp, yp, r, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, xp, yp, r, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+
+ return arc;
+}
+
+D2TK_API bool
+d2tk_flowmatrix_arc_not_end(d2tk_flowmatrix_arc_t *arc)
+{
+ return arc->k < arc->NM - 1;
+}
+
+D2TK_API d2tk_flowmatrix_arc_t *
+d2tk_flowmatrix_arc_next(d2tk_flowmatrix_arc_t *arc, d2tk_pos_t *pos,
+ const d2tk_state_t *state)
+{
+ d2tk_flowmatrix_t *flowmatrix = arc->flowmatrix;
+ d2tk_base_t *base = flowmatrix->base;
+
+ if(d2tk_state_is_motion(*state))
+ {
+ const d2tk_coord_t adx = base->mouse.dx / flowmatrix->scale;
+ const d2tk_coord_t ady = base->mouse.dy / flowmatrix->scale;
+
+ pos->x += adx;
+ pos->y += ady;
+
+ d2tk_base_set_again(base);
+ }
+
+ {
+ ++arc->k;
+
+ if(++arc->x % arc->N)
+ {
+ // nothing to do
+ }
+ else // overflow
+ {
+ arc->x = 0;
+ ++arc->y;
+ }
+
+ arc->rect.x = arc->xo + (arc->x - arc->y)*arc->c_2;
+ arc->rect.y = arc->yo + (arc->x + arc->y)*arc->c_2;
+ arc->rect.w = arc->c;
+ arc->rect.h = arc->c_2;
+
+ if(arc->y == (arc->M - 1)) // source label
+ {
+ arc->rect.x -= arc->rect.w*1 + arc->c_2;
+ arc->rect.y -= arc->c_4;
+ arc->rect.w *= 2;
+ }
+ else if(arc->x == (arc->N - 1)) // sink label
+ {
+ arc->rect.x += arc->c_2;
+ arc->rect.y -= arc->c_4;
+ arc->rect.w *= 2;
+ }
+ }
+
+ return arc;
+}
+
+D2TK_API unsigned
+d2tk_flowmatrix_arc_get_index(d2tk_flowmatrix_arc_t *arc)
+{
+ return arc->k;
+}
+
+D2TK_API unsigned
+d2tk_flowmatrix_arc_get_index_x(d2tk_flowmatrix_arc_t *arc)
+{
+ return arc->x;
+}
+
+D2TK_API unsigned
+d2tk_flowmatrix_arc_get_index_y(d2tk_flowmatrix_arc_t *arc)
+{
+ return arc->y;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_flowmatrix_arc_get_rect(d2tk_flowmatrix_arc_t *arc __attribute__((unused)))
+{
+ return &arc->rect;
+}
diff --git a/src/base_frame.c b/src/base_frame.c
new file mode 100644
index 0000000..d78bdf0
--- /dev/null
+++ b/src/base_frame.c
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+struct _d2tk_frame_t {
+ d2tk_rect_t rect;
+};
+
+const size_t d2tk_frame_sz = sizeof(d2tk_frame_t);
+
+D2TK_API d2tk_frame_t *
+d2tk_frame_begin(d2tk_base_t *base, const d2tk_rect_t *rect,
+ ssize_t lbl_len, const char *lbl, d2tk_frame_t *frm)
+{
+ const bool has_lbl = lbl_len && lbl;
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+ d2tk_core_t *core = base->core;
+ const d2tk_coord_t h = 17; //FIXME
+
+ if(has_lbl && (lbl_len == -1) ) // zero-terminated string
+ {
+ lbl_len = strlen(lbl);
+ }
+
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { lbl, lbl_len },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_rect_shrink(&frm->rect, rect, 2*style->padding);
+
+ if(has_lbl)
+ {
+ frm->rect.y += h;
+ frm->rect.h -= h;
+ }
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_rect_t bnd_outer;
+ d2tk_rect_shrink(&bnd_outer, rect, style->padding);
+ d2tk_rect_t bnd_inner = bnd_outer;;
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ if(has_lbl)
+ {
+ bnd_inner.h = h;
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, &bnd_inner, style->rounding);
+ d2tk_core_color(core, style->fill_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ bnd_inner.x += style->rounding;
+ bnd_inner.w -= style->rounding*2;
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd_inner);
+ d2tk_core_font_size(core, bnd_inner.h - 2*style->padding);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_text(core, &bnd_inner, lbl_len, lbl,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_MIDDLE);
+ d2tk_core_restore(core);
+ }
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, &bnd_outer, style->rounding);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ return frm;
+}
+
+D2TK_API bool
+d2tk_frame_not_end(d2tk_frame_t *frm)
+{
+ return frm ? true : false;
+}
+
+D2TK_API d2tk_frame_t *
+d2tk_frame_next(d2tk_frame_t *frm __attribute__((unused)))
+{
+ return NULL;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_frame_get_rect(d2tk_frame_t *frm)
+{
+ return &frm->rect;
+}
diff --git a/src/base_image.c b/src/base_image.c
new file mode 100644
index 0000000..cfed2d1
--- /dev/null
+++ b/src/base_image.c
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+D2TK_API void
+d2tk_base_image(d2tk_base_t *base, ssize_t path_len, const char *path,
+ const d2tk_rect_t *rect, d2tk_align_t align)
+{
+ const bool has_img = path_len && path;
+
+ if(has_img && (path_len == -1) ) // zero-terminated string
+ {
+ path_len = strlen(path);
+ }
+
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(d2tk_rect_t) },
+ { (path ? path : NULL), (path ? path_len : 0) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;;
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ if(has_img)
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_image(core, rect, path_len, path, align);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
diff --git a/src/base_internal.h b/src/base_internal.h
new file mode 100644
index 0000000..31ad648
--- /dev/null
+++ b/src/base_internal.h
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdatomic.h>
+
+#include <d2tk/base.h>
+#include <d2tk/hash.h>
+#include "core_internal.h"
+
+#define _D2TK_MAX_ATOM 0x1000
+#define _D2TK_MASK_ATOMS (_D2TK_MAX_ATOM - 1)
+
+typedef enum _d2tk_atom_type_t {
+ D2TK_ATOM_NONE,
+ D2TK_ATOM_SCROLL,
+ D2TK_ATOM_PANE,
+ D2TK_ATOM_FLOW,
+ D2TK_ATOM_FLOW_NODE,
+ D2TK_ATOM_FLOW_ARC,
+#if D2TK_PTY
+ D2TK_ATOM_PTY,
+#endif
+} d2tk_atom_type_t;
+
+typedef enum _d2tk_atom_event_type_t {
+ D2TK_ATOM_EVENT_NONE,
+ D2TK_ATOM_EVENT_PROBE,
+ D2TK_ATOM_EVENT_DEINIT
+} d2tk_atom_event_type_t;
+
+typedef struct _d2tk_flip_t d2tk_flip_t;
+typedef struct _d2tk_atom_t d2tk_atom_t;
+typedef int (*d2tk_atom_event_t)(d2tk_atom_event_type_t event, void *data);
+
+struct _d2tk_flip_t {
+ d2tk_id_t old;
+ d2tk_id_t cur;
+};
+
+struct _d2tk_atom_t {
+ d2tk_id_t id;
+ d2tk_atom_type_t type;
+ void *body;
+ d2tk_atom_event_t event;
+};
+
+struct _d2tk_base_t {
+ d2tk_flip_t hotitem;
+ d2tk_flip_t activeitem;
+ d2tk_flip_t focusitem;
+ d2tk_id_t lastitem;
+
+ bool not_first_time;
+
+ struct {
+ d2tk_coord_t x;
+ d2tk_coord_t y;
+ d2tk_coord_t ox;
+ d2tk_coord_t oy;
+ d2tk_coord_t dx;
+ d2tk_coord_t dy;
+ d2tk_butmask_t mask;
+ } mouse;
+
+ struct {
+ int32_t odx;
+ int32_t ody;
+ int32_t dx;
+ int32_t dy;
+ } scroll;
+
+ struct {
+ size_t nchars;
+ utf8_int32_t chars [32];
+ unsigned keymod;
+ d2tk_keymask_t mask;
+ d2tk_modmask_t mod;
+ } keys;
+
+ struct {
+ char text_in [1024];
+ char text_out [1024];
+ } edit;
+
+ const d2tk_style_t *style;
+
+ atomic_bool again;
+ bool clear_focus;
+ bool focused;
+
+ d2tk_core_t *core;
+
+ d2tk_atom_t atoms [_D2TK_MAX_ATOM];
+};
+
+extern const size_t d2tk_atom_body_flow_sz;
+extern const size_t d2tk_atom_body_pane_sz;
+extern const size_t d2tk_atom_body_scroll_sz;
+#if D2TK_PTY
+extern const size_t d2tk_atom_body_pty_sz;
+#endif
+
+void *
+_d2tk_base_get_atom(d2tk_base_t *base, d2tk_id_t id, d2tk_atom_type_t type,
+ d2tk_atom_event_t event);
+
+d2tk_state_t
+_d2tk_base_is_active_hot_vertical_scroll(d2tk_base_t *base);
+
+d2tk_state_t
+_d2tk_base_is_active_hot_horizontal_scroll(d2tk_base_t *base);
+
+void
+_d2tk_base_clear_chars(d2tk_base_t *base);
diff --git a/src/base_label.c b/src/base_label.c
new file mode 100644
index 0000000..64cbd4b
--- /dev/null
+++ b/src/base_label.c
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+D2TK_API d2tk_state_t
+d2tk_base_label(d2tk_base_t *base, ssize_t lbl_len, const char *lbl,
+ float mul, const d2tk_rect_t *rect, d2tk_align_t align)
+{
+ const bool has_lbl = lbl_len && lbl;
+
+ if(has_lbl && (lbl_len == -1) ) // zero terminated string
+ {
+ lbl_len = strlen(lbl);
+ }
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ const d2tk_hash_dict_t dict [] = {
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &mul, sizeof(float) },
+ { &align, sizeof(d2tk_align_t) },
+ { lbl, lbl_len },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ const d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ if(style->text_fill_color[triple])
+ {
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd);
+ d2tk_core_color(core, style->text_fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+ }
+
+ if(lbl_len > 0)
+ {
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd);
+ d2tk_core_font_size(core, mul*bnd.h);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[triple]);
+ d2tk_core_text(core, &bnd, lbl_len, lbl, align);
+ d2tk_core_restore(core);
+ }
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ return D2TK_STATE_NONE;
+}
diff --git a/src/base_layout.c b/src/base_layout.c
new file mode 100644
index 0000000..770e5df
--- /dev/null
+++ b/src/base_layout.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+struct _d2tk_layout_t {
+ unsigned N;
+ const d2tk_coord_t *frac;
+ d2tk_flag_t flag;
+ d2tk_coord_t dd;
+ d2tk_coord_t rem;
+ unsigned k;
+ d2tk_rect_t rect;
+};
+
+const size_t d2tk_layout_sz = sizeof(d2tk_layout_t);
+
+D2TK_API d2tk_layout_t *
+d2tk_layout_begin(const d2tk_rect_t *rect, unsigned N, const d2tk_coord_t *frac,
+ d2tk_flag_t flag, d2tk_layout_t *lay)
+{
+ lay->N = N;
+ lay->frac = frac;
+ lay->flag = flag;
+
+ unsigned tot = 0;
+ unsigned missing = 0;
+ for(unsigned i = 0; i < N; i++)
+ {
+ tot += frac[i];
+
+ if(frac[i] == 0)
+ {
+ missing += 1;
+ }
+ }
+
+ lay->k = 0;
+ lay->rect.x = rect->x;
+ lay->rect.y = rect->y;
+
+ if(lay->flag & D2TK_FLAG_LAYOUT_Y)
+ {
+ if(lay->flag & D2TK_FLAG_LAYOUT_REL)
+ {
+ lay->dd = tot ? (rect->h / tot) : 0;
+ }
+ else
+ {
+ lay->dd = 1;
+ }
+
+ lay->rem = missing ? (rect->h - tot) / missing : 0;
+
+ lay->rect.h = lay->frac[lay->k]
+ ? lay->dd * lay->frac[lay->k]
+ : lay->rem;
+ lay->rect.w = rect->w;
+ }
+ else // D2TK_FLAG_LAYOUT_X
+ {
+ if(lay->flag & D2TK_FLAG_LAYOUT_REL)
+ {
+ lay->dd = tot ? (rect->w / tot) : 0;
+ }
+ else
+ {
+ lay->dd = 1;
+ }
+
+ lay->rem = missing ? (rect->w - tot) / missing : 0;
+
+ lay->rect.w = lay->frac[lay->k]
+ ? lay->dd * lay->frac[lay->k]
+ : lay->rem;
+ lay->rect.h = rect->h;
+ }
+
+ return lay;
+}
+
+D2TK_API bool
+d2tk_layout_not_end(d2tk_layout_t *lay)
+{
+ return lay;
+}
+
+D2TK_API d2tk_layout_t *
+d2tk_layout_next(d2tk_layout_t *lay)
+{
+ if(++lay->k >= lay->N)
+ {
+ return NULL;
+ }
+
+ if(lay->flag & D2TK_FLAG_LAYOUT_Y)
+ {
+ lay->rect.y += lay->rect.h;
+ lay->rect.h = lay->frac[lay->k]
+ ? lay->dd * lay->frac[lay->k]
+ : lay->rem;
+ }
+ else // D2TK_FLAG_LAYOUT_X
+ {
+ lay->rect.x += lay->rect.w;
+ lay->rect.w = lay->frac[lay->k]
+ ? lay->dd * lay->frac[lay->k]
+ : lay->rem;
+ }
+
+ return lay;
+}
+
+D2TK_API unsigned
+d2tk_layout_get_index(d2tk_layout_t *lay)
+{
+ return lay->k;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_layout_get_rect(d2tk_layout_t *lay)
+{
+ return &lay->rect;
+}
diff --git a/src/base_link.c b/src/base_link.c
new file mode 100644
index 0000000..355d394
--- /dev/null
+++ b/src/base_link.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <string.h>
+
+#include "base_internal.h"
+
+static void
+_d2tk_base_draw_link(d2tk_base_t *base, ssize_t lbl_len, const char *lbl,
+ float mul, const d2tk_rect_t *rect, d2tk_align_t align, d2tk_triple_t triple,
+ const d2tk_style_t *style)
+{
+ const bool has_lbl = lbl_len && lbl;
+
+ if(has_lbl && (lbl_len == -1) ) // zero terminated string
+ {
+ lbl_len = strlen(lbl);
+ }
+
+ const d2tk_hash_dict_t dict [] = {
+ { &triple, sizeof(d2tk_triple_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &mul, sizeof(float) },
+ { &align, sizeof(d2tk_align_t) },
+ { lbl, lbl_len },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ d2tk_core_t *core = base->core;
+
+ //FIXME analyse link and draw underline, hover, etc.
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd);
+ d2tk_core_font_size(core, mul*bnd.h);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[triple]);
+ d2tk_core_text(core, &bnd, lbl_len, lbl, align);
+ d2tk_core_restore(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, bnd.x, bnd.y + bnd.h);
+ d2tk_core_line_to(core, bnd.x + bnd.w, bnd.y + bnd.h);
+ if(triple & D2TK_TRIPLE_FOCUS)
+ {
+ d2tk_core_color(core, style->stroke_color[triple]);
+ }
+ else
+ {
+ d2tk_core_color(core, style->fill_color[triple]);
+ }
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_link(d2tk_base_t *base, d2tk_id_t id, ssize_t lbl_len, const char *lbl,
+ float mul, const d2tk_rect_t *rect, d2tk_align_t align)
+{
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
+
+ if(d2tk_state_is_down(state) || d2tk_state_is_enter(state))
+ {
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(state))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ _d2tk_base_draw_link(base, lbl_len, lbl, mul, rect, align, triple,
+ d2tk_base_get_style(base));
+
+ return state;
+}
diff --git a/src/base_meter.c b/src/base_meter.c
new file mode 100644
index 0000000..6d8d080
--- /dev/null
+++ b/src/base_meter.c
@@ -0,0 +1,233 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include "base_internal.h"
+
+static inline void
+_d2tk_base_draw_meter(d2tk_core_t *core, const d2tk_rect_t *rect,
+ d2tk_state_t state, int32_t value, const d2tk_style_t *style)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &value, sizeof(int32_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+#define N 4
+#define N_1 (N - 1)
+#define L 11
+
+#define dBFS3_min -18 // -54 dBFS
+#define dBFS3_max 2 // +6 dBFS
+#define dBFS3_range (dBFS3_max - dBFS3_min)
+
+ static const int32_t dBFS3_off [N] = {
+ dBFS3_min,
+ -2, // -6 dBFS
+ 0, // +0 dBFS
+ dBFS3_max
+ };
+
+ static const uint32_t rgba [N] = {
+ 0x00ffffff, // cyan
+ 0x00ff00ff, // green
+ 0xffff00ff, // yellow
+ 0xff0000ff // red
+ };
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(state))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+ bnd.h /= 2;
+
+ const d2tk_coord_t dx = bnd.w / dBFS3_range;
+ const d2tk_coord_t y0 = bnd.y;
+ const d2tk_coord_t y1 = y0 + bnd.h;
+ const d2tk_coord_t ym = (y0 + y1)/2;
+
+ const d2tk_point_t p [N] = {
+ D2TK_POINT(bnd.x + (dBFS3_off[0] - dBFS3_min)*dx, ym),
+ D2TK_POINT(bnd.x + (dBFS3_off[1] - dBFS3_min)*dx, ym),
+ D2TK_POINT(bnd.x + (dBFS3_off[2] - dBFS3_min)*dx, ym),
+ D2TK_POINT(bnd.x + (dBFS3_off[3] - dBFS3_min)*dx, ym)
+ };
+
+ // dependent on value, e.g. linear gradient
+ {
+ const d2tk_coord_t xv = bnd.x + (value - dBFS3_min*3)*dx/3;
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ for(unsigned i = 0; i < N_1; i++)
+ {
+ const d2tk_coord_t x0 = p[i].x;
+ d2tk_coord_t x1 = p[i+1].x;
+ bool do_break = false;
+
+ if(x1 > xv)
+ {
+ x1 = xv;
+ do_break = true;
+ }
+
+ const d2tk_rect_t bnd2 = {
+ .x = x0,
+ .y = y0,
+ .w = x1 - x0,
+ .h = bnd.h
+ };
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd2);
+ d2tk_core_linear_gradient(core, &p[i], &rgba[i]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ if(do_break)
+ {
+ break;
+ }
+ }
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ // independent on value, eg scale + border
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ for(int32_t dBFS3 = dBFS3_min + 1; dBFS3 < dBFS3_max; dBFS3++)
+ {
+ const d2tk_coord_t x = bnd.x + (dBFS3 - dBFS3_min)*dx;
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x, y0);
+ d2tk_core_line_to(core, x, y1);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+ }
+
+ {
+ const d2tk_rect_t bnd2 = {
+ .x = bnd.x,
+ .y = y0,
+ .w = p[N_1].x - p[0].x,
+ .h = bnd.h
+ };
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd2);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+ }
+
+ static const unsigned lbls [L] = {
+ +2,
+ +1,
+ +0,
+ -1,
+ -2,
+ -4,
+ -8,
+ -12,
+ -15, // unit
+ -16
+ };
+
+ for(unsigned i = 0; i<L; i++)
+ {
+ const int32_t dBFS3 = lbls[i] - 1;
+ const d2tk_coord_t x = bnd.x + (dBFS3 - dBFS3_min)*dx;
+ const bool is_unit = (i == 8);
+
+ const d2tk_rect_t bnd2 = {
+ .x = x,
+ .y = y0 + bnd.h,
+ .w = is_unit ? 3*dx : dx,
+ .h = bnd.h
+ };
+
+ char lbl [16];
+ const ssize_t lbl_len = is_unit
+ ? snprintf(lbl, sizeof(lbl), " dBFS")
+ : snprintf(lbl, sizeof(lbl), "%+"PRIi32, (dBFS3+1)*3);
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd2);
+ d2tk_core_font_size(core, bnd2.h);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[triple]);
+ d2tk_core_text(core, &bnd2, lbl_len, lbl,
+ D2TK_ALIGN_BOTTOM | (is_unit ? D2TK_ALIGN_LEFT: D2TK_ALIGN_RIGHT));
+ d2tk_core_restore(core);
+ }
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+#undef dBFS3_range
+#undef dBFS3_max
+#undef dBFS3_min
+
+#undef L
+#undef N
+#undef N_1
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_meter(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ const int32_t *value)
+{
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ const d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_NONE);
+
+ d2tk_core_t *core = base->core;
+
+ _d2tk_base_draw_meter(core, rect, state, *value, style);
+
+ return state;
+}
diff --git a/src/base_pane.c b/src/base_pane.c
new file mode 100644
index 0000000..07b6b5d
--- /dev/null
+++ b/src/base_pane.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <math.h>
+
+#include "base_internal.h"
+
+typedef struct _d2tk_atom_body_pane_t d2tk_atom_body_pane_t;
+
+struct _d2tk_atom_body_pane_t {
+ float fraction;
+};
+
+struct _d2tk_pane_t {
+ d2tk_atom_body_pane_t *atom_body;
+ unsigned k;
+ d2tk_rect_t rect [2];
+};
+
+const size_t d2tk_atom_body_pane_sz = sizeof(d2tk_atom_body_pane_t);
+const size_t d2tk_pane_sz = sizeof(d2tk_pane_t);
+
+static void
+_d2tk_draw_pane(d2tk_core_t *core, d2tk_state_t state, const d2tk_rect_t *sub,
+ const d2tk_style_t *style, d2tk_flag_t flags)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { sub, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &flags, sizeof(d2tk_flag_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ const d2tk_coord_t s = 10; //FIXME
+ const d2tk_coord_t r = (s - 2) / 2; //FIXME
+
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(state))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ d2tk_coord_t x0, x1, x2, y0, y1, y2;
+
+ if(flags & D2TK_FLAG_PANE_X)
+ {
+ x0 = sub->x + sub->w/2;
+ x1 = x0;
+ x2 = x0;
+
+ y0 = sub->y;
+ y1 = y0 + sub->h/2;
+ y2 = y0 + sub->h;
+ }
+ else // flags & D2TK_FLAG_PANE_Y
+ {
+ x0 = sub->x;
+ x1 = x0 + sub->w/2;
+ x2 = x0 + sub->w;
+
+ y0 = sub->y + sub->h/2;
+ y1 = y0;
+ y2 = y0;
+ }
+
+ const size_t ref = d2tk_core_bbox_push(core, true, sub);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_line_to(core, x2, y2);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x1, y1, r, 0, 360, true);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_arc(core, x1, y1, r, 0, 360, true);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);\
+ }
+}
+
+D2TK_API d2tk_pane_t *
+d2tk_pane_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
+ d2tk_flag_t flags, float fmin, float fmax, float fstep, d2tk_pane_t *pane)
+{
+ pane->k = 0;
+ pane->rect[0] = *rect;
+ pane->rect[1] = *rect;
+ pane->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_PANE, NULL);
+
+ float *fraction = &pane->atom_body->fraction;
+ d2tk_rect_t sub = *rect;
+
+ const d2tk_coord_t s = 10; //FIXME
+
+ d2tk_clip_float(fmin, &pane->atom_body->fraction, fmax);
+
+ if(flags & D2TK_FLAG_PANE_X)
+ {
+ pane->rect[0].w *= pane->atom_body->fraction;
+
+ sub.x += pane->rect[0].w;
+ sub.w = s;
+
+ const d2tk_coord_t rsvd = pane->rect[0].w + s;
+ pane->rect[1].x += rsvd;
+ pane->rect[1].w -= rsvd;
+ }
+ else if(flags & D2TK_FLAG_PANE_Y)
+ {
+ pane->rect[0].h *= pane->atom_body->fraction;
+
+ sub.y += pane->rect[0].h;
+ sub.h = s;
+
+ const d2tk_coord_t rsvd = pane->rect[0].h + s;
+ pane->rect[1].y += rsvd;
+ pane->rect[1].h -= rsvd;
+ }
+
+ d2tk_state_t state = D2TK_STATE_NONE;
+
+ if(flags & D2TK_FLAG_PANE_X)
+ {
+ state |= d2tk_base_is_active_hot(base, id, &sub, D2TK_FLAG_NONE);
+ }
+ else if(flags & D2TK_FLAG_PANE_Y)
+ {
+ state |= d2tk_base_is_active_hot(base, id, &sub, D2TK_FLAG_NONE);
+ }
+
+ const float old_fraction = *fraction;
+
+ if(flags & D2TK_FLAG_PANE_X)
+ {
+ if(d2tk_state_is_scroll_left(state))
+ {
+ *fraction -= fstep;
+ }
+ else if(d2tk_state_is_scroll_right(state))
+ {
+ *fraction += fstep;
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ *fraction = roundf((float)(base->mouse.x - rect->x) / rect->w / fstep) * fstep;
+ }
+ }
+ else if(flags & D2TK_FLAG_PANE_Y)
+ {
+ if(d2tk_state_is_scroll_up(state))
+ {
+ *fraction -= fstep;
+ }
+ else if(d2tk_state_is_scroll_down(state))
+ {
+ *fraction += fstep;
+ }
+ else if(d2tk_state_is_motion(state))
+ {
+ *fraction = roundf((float)(base->mouse.y - rect->y) / rect->h / fstep) * fstep;
+ }
+ }
+
+ if(old_fraction != *fraction)
+ {
+ state |= D2TK_STATE_CHANGED;
+ d2tk_base_set_again(base);
+ }
+
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ d2tk_core_t *core = base->core;
+
+ _d2tk_draw_pane(core, state, &sub, style, flags);
+
+ return pane;
+}
+
+D2TK_API bool
+d2tk_pane_not_end(d2tk_pane_t *pane)
+{
+ return pane ? true : false;
+}
+
+D2TK_API d2tk_pane_t *
+d2tk_pane_next(d2tk_pane_t *pane)
+{
+ return (pane->k++ == 0) ? pane : NULL;
+}
+
+D2TK_API float
+d2tk_pane_get_fraction(d2tk_pane_t *pane)
+{
+ return pane->atom_body->fraction;
+}
+
+D2TK_API unsigned
+d2tk_pane_get_index(d2tk_pane_t *pane)
+{
+ return pane->k;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_pane_get_rect(d2tk_pane_t *pane)
+{
+ return &pane->rect[pane->k];
+}
diff --git a/src/base_pty.c b/src/base_pty.c
new file mode 100644
index 0000000..2255fb3
--- /dev/null
+++ b/src/base_pty.c
@@ -0,0 +1,910 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+#include <vterm.h>
+#include <pty.h>
+#include <sys/wait.h>
+#include <poll.h>
+
+#include "base_internal.h"
+
+#define DEFAULT_FG 0xddddddff
+#define DEFAULT_BG 0x222222ff
+
+#define DEFAULT_FG_LIGHT 0xdddddd7f
+#define DEFAULT_BG_LIGHT 0x2222227f
+
+#define NROWS_MAX 512
+#define NCOLS_MAX 512
+
+#define MAX(x, y) (x > y ? y : x)
+
+typedef struct _cell_t cell_t;
+typedef struct _d2tk_atom_body_pty_t d2tk_atom_body_pty_t;
+
+struct _cell_t {
+ char lbl [8];
+ size_t lbl_len;
+ bool bold;
+ bool italic;
+ bool reverse;
+ bool cursor;
+ uint32_t fg;
+ uint32_t bg;
+};
+
+struct _d2tk_atom_body_pty_t {
+ d2tk_coord_t height;
+
+ d2tk_coord_t ncols;
+ d2tk_coord_t nrows;
+ unsigned bell;
+
+ int fd;
+ pid_t kid;
+
+ VTerm *vterm;
+ VTermScreen *screen;
+ VTermState *state;
+
+ uint32_t dark;
+ uint32_t light;
+
+ bool cursor_visible;
+ int cursor_shape;
+
+ cell_t cells [NROWS_MAX][NCOLS_MAX];
+};
+
+const size_t d2tk_atom_body_pty_sz = sizeof(d2tk_atom_body_pty_t);
+
+static inline uint32_t
+_term_dark(d2tk_atom_body_pty_t *vpty)
+{
+ return vpty->dark;
+}
+
+static inline uint32_t
+_term_light(d2tk_atom_body_pty_t *vpty)
+{
+ return vpty->light;
+}
+
+static inline int
+_term_read(d2tk_atom_body_pty_t *vpty,
+ void (*cb)(const char *buf, size_t len, void *data), void *data)
+{
+ char buf [4096];
+ int count = 0;
+
+ while(true)
+ {
+ const ssize_t len = read(vpty->fd, buf, sizeof(buf));
+
+ if(len == -1)
+ {
+ if(errno != EAGAIN)
+ {
+ fprintf(stderr, "read failed '%s'\n", strerror(errno));
+ }
+ break;
+ }
+
+ if(len == 0)
+ {
+ break;;
+ }
+
+ cb(buf, len, data);
+ count += 1;
+ }
+
+ return count;
+}
+
+static inline int
+_term_done(d2tk_atom_body_pty_t *vpty)
+{
+ if(vpty->kid == 0)
+ {
+ return 1;
+ }
+
+ if(waitpid(vpty->kid, NULL, WNOHANG) == vpty->kid)
+ {
+ vpty->kid = 0;
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+_term_output(const char *buf, size_t len, void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ const ssize_t writ = write(vpty->fd, buf, len);
+
+ if(writ == -1)
+ {
+ fprintf(stderr, "write failed '%s'\n", strerror(errno));
+ return;
+ }
+}
+
+static int
+_screen_settermprop(VTermProp prop, VTermValue *val, void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ switch(prop)
+ {
+ case VTERM_PROP_CURSORVISIBLE: // bool
+ {
+ vpty->cursor_visible = val->boolean;
+ } break;
+ case VTERM_PROP_CURSORSHAPE: // number
+ {
+ vpty->cursor_shape = val->number;
+ } break;
+
+ default:
+ {
+ // nothing to do
+ } break;
+ }
+
+ return 0;
+}
+
+static int
+_screen_resize(int nrows, int ncols, void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ if( (nrows == vpty->nrows) && (ncols == vpty->ncols) )
+ {
+ return 0;
+ }
+
+ const struct winsize winsize = {
+ .ws_row = nrows,
+ .ws_col = ncols,
+ .ws_xpixel = 0,
+ .ws_ypixel = 0
+ };
+
+ if(ioctl(vpty->fd, TIOCSWINSZ, &winsize) == -1)
+ {
+ fprintf(stderr, "ioctl failed '%s'\n", strerror(errno));
+ return 1;
+ }
+
+ vpty->nrows = nrows;
+ vpty->ncols = ncols;
+
+ return 0;
+}
+
+static int
+_screen_bell(void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ vpty->bell += 1;
+
+ return 0;
+}
+
+static const VTermScreenCallbacks screen_callbacks = {
+ .settermprop = _screen_settermprop,
+ .bell = _screen_bell,
+ .resize = _screen_resize
+};
+
+static void
+_term_clone(FILE *stderr_save, void *data)
+{
+ char **argv = data;
+
+ execvp(argv[0], argv);
+
+ fprintf(stderr_save, "cannot exec(%s) - %s\n", argv[0], strerror(errno));
+}
+
+static int
+_term_init(d2tk_atom_body_pty_t *vpty, d2tk_clone_t clone, void *data,
+ d2tk_coord_t height, d2tk_coord_t ncols, d2tk_coord_t nrows)
+{
+ vpty->height = height;
+ vpty->nrows = nrows;
+ vpty->ncols = ncols;
+
+ struct termios termios = {
+ .c_iflag = ICRNL|IXON,
+ .c_oflag = OPOST|ONLCR
+#ifdef TAB0
+ |TAB0
+#endif
+ ,
+ .c_cflag = CS8|CREAD,
+ .c_lflag = ISIG|ICANON|IEXTEN|ECHO|ECHOE|ECHOK,
+ /* c_cc later */
+ };
+
+#ifdef IUTF8
+ termios.c_iflag |= IUTF8;
+#endif
+#ifdef NL0
+ termios.c_oflag |= NL0;
+#endif
+#ifdef CR0
+ termios.c_oflag |= CR0;
+#endif
+#ifdef BS0
+ termios.c_oflag |= BS0;
+#endif
+#ifdef VT0
+ termios.c_oflag |= VT0;
+#endif
+#ifdef FF0
+ termios.c_oflag |= FF0;
+#endif
+#ifdef ECHOCTL
+ termios.c_lflag |= ECHOCTL;
+#endif
+#ifdef ECHOKE
+ termios.c_lflag |= ECHOKE;
+#endif
+
+ cfsetspeed(&termios, 115200);
+
+ termios.c_cc[VINTR] = 0x1f & 'C';
+ termios.c_cc[VQUIT] = 0x1f & '\\';
+ termios.c_cc[VERASE] = 0x7f;
+ termios.c_cc[VKILL] = 0x1f & 'U';
+ termios.c_cc[VEOF] = 0x1f & 'D';
+ termios.c_cc[VEOL] = _POSIX_VDISABLE;
+ termios.c_cc[VEOL2] = _POSIX_VDISABLE;
+ termios.c_cc[VSTART] = 0x1f & 'Q';
+ termios.c_cc[VSTOP] = 0x1f & 'S';
+ termios.c_cc[VSUSP] = 0x1f & 'Z';
+ termios.c_cc[VREPRINT] = 0x1f & 'R';
+ termios.c_cc[VWERASE] = 0x1f & 'W';
+ termios.c_cc[VLNEXT] = 0x1f & 'V';
+ termios.c_cc[VMIN] = 1;
+ termios.c_cc[VTIME] = 0;
+
+ const struct winsize winsize = {
+ .ws_row = vpty->nrows,
+ .ws_col = vpty->ncols,
+ .ws_xpixel = 0,
+ .ws_ypixel = 0
+ };
+
+ const int stderr_save_fileno = dup(STDERR_FILENO);
+
+ vpty->kid = forkpty(&vpty->fd, NULL, &termios, &winsize);
+ if(vpty->kid == -1)
+ {
+ vpty->kid = 0;
+ return 1;
+ }
+ else if(vpty->kid == 0) // child
+ {
+ fcntl(stderr_save_fileno, F_SETFD, fcntl(stderr_save_fileno, F_GETFD) | FD_CLOEXEC);
+ FILE *stderr_save = fdopen(stderr_save_fileno, "a");
+
+ /* Restore the ISIG signals back to defaults */
+ signal(SIGINT, SIG_DFL);
+ signal(SIGQUIT, SIG_DFL);
+ signal(SIGSTOP, SIG_DFL);
+ signal(SIGCONT, SIG_DFL);
+
+ putenv("TERM=xterm-256color");
+ //putenv("COLORTERM=truecolor");
+
+ if(!clone)
+ {
+ clone = _term_clone;
+ }
+
+ clone(stderr_save, data);
+ _exit(EXIT_FAILURE);
+ }
+
+ fcntl(vpty->fd, F_SETFL, fcntl(vpty->fd, F_GETFL) | O_NONBLOCK);
+ close(stderr_save_fileno);
+
+ vpty->vterm = vterm_new(vpty->nrows, vpty->ncols);
+ vterm_set_utf8(vpty->vterm, 1);
+ vterm_output_set_callback(vpty->vterm, _term_output, vpty);
+
+ vpty->state = vterm_obtain_state(vpty->vterm);
+
+ vpty->screen = vterm_obtain_screen(vpty->vterm);
+ vterm_screen_set_callbacks(vpty->screen, &screen_callbacks, vpty);
+ vterm_screen_reset(vpty->screen, 1);
+
+ return 0;
+}
+
+static int
+_term_probe(d2tk_atom_body_pty_t *vpty)
+{
+ int again = 0;
+
+ struct pollfd fds = {
+ .fd = vpty->fd,
+ .events = POLLIN
+ };
+
+ switch(poll(&fds, 1, 0))
+ {
+ case -1:
+ {
+ //printf("[%s] error: %s\n", __func__, strerror(errno));
+ } break;
+ case 0:
+ {
+ //printf("[%s] timeout\n", __func__);
+ } break;
+ default:
+ {
+ //printf("[%s] ready\n", __func__);
+ again = 1;
+ } break;
+ }
+
+ return again;
+}
+
+static int
+_term_deinit(d2tk_atom_body_pty_t *vpty)
+{
+ if(!vpty)
+ {
+ return 1;
+ }
+
+ if(vpty->kid != 0)
+ {
+ kill(vpty->kid, SIGTERM);
+
+#define RETRIES 100 // over the course of 100 ms
+ for(int i = 0; i < RETRIES; i++)
+ {
+ if(waitpid(vpty->kid, NULL, WNOHANG) == vpty->kid)
+ {
+ vpty->kid = 0;
+ break;
+ }
+
+ usleep(1000000 / RETRIES);
+ }
+#undef RETRIES
+ }
+
+ if(vpty->kid != 0)
+ {
+ fprintf(stderr, "[%s] sending SIGKILL to pid %i\n", __func__, vpty->kid);
+ kill(vpty->kid, SIGKILL);
+ waitpid(vpty->kid, NULL, 0);
+ vpty->kid = 0;
+ }
+
+ if(vpty->vterm)
+ {
+ vterm_free(vpty->vterm);
+ }
+
+ memset(vpty, 0x0, sizeof(d2tk_atom_body_pty_t));
+
+ return 0;
+}
+
+static int
+_term_event(d2tk_atom_event_type_t event, void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ switch(event)
+ {
+ case D2TK_ATOM_EVENT_PROBE:
+ {
+ return _term_probe(vpty);
+ } break;
+ case D2TK_ATOM_EVENT_DEINIT:
+ {
+ return _term_deinit(vpty);
+ } break;
+
+ case D2TK_ATOM_EVENT_NONE:
+ // fall-through
+ default:
+ {
+ // nothing to do
+ } break;
+ }
+
+ return 0;
+}
+
+static inline void
+_term_set_dark_light(d2tk_atom_body_pty_t *vpty, uint32_t fg)
+{
+ const int32_t r0 = (vpty->light >> 24) & 0xff;
+ const int32_t g0 = (vpty->light >> 16) & 0xff;
+ const int32_t b0 = (vpty->light >> 8) & 0xff;
+ const int32_t d0 = (r0 - g0) + (r0 - b0);
+
+ const int32_t r1 = (fg >> 24) & 0xff;
+ const int32_t g1 = (fg >> 16) & 0xff;
+ const int32_t b1 = (fg >> 8) & 0xff;
+ const int32_t d1 = (r1 - g1) + (r1 - b1);
+
+ if(d1 > d0)
+ {
+ vpty->light = fg;
+ vpty->dark = ( (r1 >> 1) << 24)
+ | ( (g1 >> 1) << 16)
+ | ( (b1 >> 1) << 8)
+ | 0xff;
+ }
+}
+
+static inline void
+_term_update(d2tk_atom_body_pty_t *vpty)
+{
+ VTermPos cursor;
+ VTermPos pos;
+
+ memset(&cursor, 0x0, sizeof(cursor));
+ vterm_state_get_cursorpos(vpty->state, &cursor);
+
+ memset(vpty->cells, 0x0, sizeof(vpty->cells));
+
+ for(int y = 0; y < vpty->nrows; y++)
+ {
+ pos.row = y;
+
+ for(int x = 0; x < vpty->ncols; x++)
+ {
+ cell_t *tar = &vpty->cells[y][x];
+
+ pos.col = x;
+
+ VTermScreenCell cell;
+ memset(&cell, 0x0, sizeof(cell));
+ vterm_screen_get_cell(vpty->screen, pos, &cell);
+
+ if( cell.chars[0] && (cell.width == 1) )
+ {
+ if(cell.chars[0] != ' ')
+ {
+ const char *tail = utf8catcodepoint(tar->lbl,
+ cell.chars[0], sizeof(tar->lbl));
+
+ tar->lbl_len = tail - tar->lbl;
+ }
+ }
+
+ if(cell.attrs.bold)
+ {
+ tar->bold = true;
+ }
+
+ if(cell.attrs.italic)
+ {
+ tar->italic = true;
+ }
+
+ uint32_t fg_rgba = 0x0;
+ uint32_t bg_rgba = 0x0;
+
+ if(VTERM_COLOR_IS_RGB(&cell.fg))
+ {
+ const VTermColor tmp = cell.fg;
+
+ fg_rgba = (tmp.rgb.red << 24)
+ | (tmp.rgb.green << 16)
+ | (tmp.rgb.blue << 8)
+ | 0xff;
+ }
+ else if(VTERM_COLOR_IS_INDEXED(&cell.fg))
+ {
+ VTermColor tmp = cell.fg;
+ vterm_screen_convert_color_to_rgb(vpty->screen, &tmp);
+
+ fg_rgba = (tmp.rgb.red << 24)
+ | (tmp.rgb.green << 16)
+ | (tmp.rgb.blue << 8)
+ | 0xff;
+ }
+ else if(VTERM_COLOR_IS_DEFAULT_FG(&cell.fg))
+ {
+ fg_rgba = DEFAULT_FG;
+ }
+ else if(VTERM_COLOR_IS_DEFAULT_BG(&cell.fg))
+ {
+ fg_rgba = DEFAULT_BG;
+ }
+
+ if(VTERM_COLOR_IS_RGB(&cell.bg))
+ {
+ const VTermColor tmp = cell.bg;
+
+ bg_rgba = (tmp.rgb.red << 24)
+ | (tmp.rgb.green << 16)
+ | (tmp.rgb.blue << 8)
+ | 0xff;
+ }
+ else if(VTERM_COLOR_IS_INDEXED(&cell.bg))
+ {
+ VTermColor tmp = cell.bg;
+ vterm_screen_convert_color_to_rgb(vpty->screen, &tmp);
+
+ bg_rgba = (tmp.rgb.red << 24)
+ | (tmp.rgb.green << 16)
+ | (tmp.rgb.blue << 8)
+ | 0xff;
+ }
+ else if(VTERM_COLOR_IS_DEFAULT_FG(&cell.bg))
+ {
+ bg_rgba = 0xffffffff;
+ }
+ else if(VTERM_COLOR_IS_DEFAULT_BG(&cell.bg))
+ {
+ bg_rgba = 0x000000ff;
+ }
+
+ tar->cursor = (y == cursor.row) && (x == cursor.col) && vpty->cursor_visible;
+ tar->reverse = cell.attrs.reverse;
+ tar->fg = fg_rgba;
+ tar->bg = bg_rgba;
+
+ _term_set_dark_light(vpty, fg_rgba);
+ }
+ }
+}
+
+static void
+_term_resize(d2tk_atom_body_pty_t *vpty, d2tk_coord_t ncols,
+ d2tk_coord_t nrows)
+{
+ if( (nrows != vpty->nrows) || (ncols != vpty->ncols) )
+ {
+ vterm_set_size(vpty->vterm, nrows, ncols);
+ }
+}
+
+static inline void
+_term_input_cb(const char *buf, size_t len, void *data)
+{
+ d2tk_atom_body_pty_t *vpty = data;
+
+ vterm_input_write(vpty->vterm, buf, len);
+}
+
+static inline void
+_term_input(d2tk_atom_body_pty_t *vpty)
+{
+ if(_term_read(vpty, _term_input_cb, vpty) )
+ {
+ _term_update(vpty);
+ }
+}
+
+static inline d2tk_state_t
+_term_behave(d2tk_base_t *base, d2tk_atom_body_pty_t *vpty,
+ d2tk_id_t id, const d2tk_rect_t *rect)
+{
+ const d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect,
+ D2TK_FLAG_NONE);
+
+ VTermModifier mod = VTERM_MOD_NONE;
+
+ if(d2tk_state_is_focused(state))
+ {
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_ENTER, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_ENTER, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_TAB, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_TAB, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_BACKSPACE, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_BACKSPACE, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_ESCAPE, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_ESCAPE, mod);
+ }
+
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_UP, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_UP, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_DOWN, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_DOWN, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_LEFT, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_LEFT, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_RIGHT, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_RIGHT, mod);
+ }
+
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_INS, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_INS, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_DEL, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_DEL, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_HOME, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_HOME, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_END, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_END, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_PAGEUP, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_PAGEUP, mod);
+ }
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_PAGEDOWN, true))
+ {
+ vterm_keyboard_key(vpty->vterm, VTERM_KEY_PAGEDOWN, mod);
+ }
+
+ {
+ ssize_t len = 0;
+ const utf8_int32_t *utf8 = NULL;
+
+ d2tk_base_get_utf8(base, &len, &utf8);
+
+ for(ssize_t i = 0; i < len; i++)
+ {
+ vterm_keyboard_unichar(vpty->vterm, utf8[i], mod);
+ }
+ }
+ }
+
+ if(d2tk_base_get_modmask(base, D2TK_MODMASK_SHIFT, false))
+ {
+ mod |= VTERM_MOD_SHIFT;
+ }
+ if(d2tk_base_get_modmask(base, D2TK_MODMASK_ALT, false))
+ {
+ mod |= VTERM_MOD_ALT;
+ }
+ if(d2tk_base_get_modmask(base, D2TK_MODMASK_CTRL, false))
+ {
+ mod |= VTERM_MOD_CTRL;
+ }
+
+ if(d2tk_state_is_focus_in(state))
+ {
+ vterm_state_focus_in(vpty->state);
+ }
+
+ if(d2tk_state_is_focus_out(state))
+ {
+ vterm_state_focus_out(vpty->state);
+ }
+
+ if(d2tk_state_is_hot(state))
+ {
+ d2tk_coord_t mx, my;
+ int dx, dy;
+ d2tk_base_get_mouse_pos(base, &mx, &my);
+ d2tk_base_get_mouse_scroll(base, &dx, &dy, false);
+
+ const int row = (my - rect->y) * vpty->nrows / rect->h;
+ const int col = (mx - rect->x) * vpty->ncols / rect->w;
+
+ vterm_mouse_move(vpty->vterm, row, col, mod);
+
+ vterm_mouse_button(vpty->vterm, 1,
+ d2tk_base_get_butmask(base, D2TK_BUTMASK_LEFT, false), mod);
+ vterm_mouse_button(vpty->vterm, 2,
+ d2tk_base_get_butmask(base, D2TK_BUTMASK_MIDDLE, false), mod);
+ vterm_mouse_button(vpty->vterm, 3,
+ d2tk_base_get_butmask(base, D2TK_BUTMASK_RIGHT, false), mod);
+
+ if(dy > 0)
+ {
+ vterm_mouse_button(vpty->vterm, 4, true, mod);
+ }
+ else if(dy < 0)
+ {
+ vterm_mouse_button(vpty->vterm, 5, true, mod);
+ }
+ }
+
+ return state;
+}
+
+static inline void
+_term_draw(d2tk_base_t *base, d2tk_atom_body_pty_t *vpty,
+ const d2tk_rect_t *rect, bool focus)
+{
+ D2TK_BASE_TABLE(rect, vpty->ncols, vpty->nrows, D2TK_FLAG_TABLE_REL, tab)
+ {
+ const int x = d2tk_table_get_index_x(tab);
+ const int y = d2tk_table_get_index_y(tab);
+ const d2tk_rect_t *trect = d2tk_table_get_rect(tab);
+
+ const d2tk_style_t *old_style = d2tk_base_get_style(base);
+ d2tk_style_t style = *old_style;
+ cell_t *cell = &vpty->cells[y][x];
+
+ style.border_width = 0;
+ style.padding = 0;
+ style.rounding = 0;
+
+ uint32_t fg = cell->fg;
+ uint32_t bg = cell->bg;
+
+ if(cell->cursor)
+ {
+ // draw box cursor
+ if(vpty->cursor_shape == VTERM_PROP_CURSORSHAPE_BLOCK)
+ {
+ fg = focus ? DEFAULT_BG : DEFAULT_BG_LIGHT;
+ bg = focus ? DEFAULT_FG : DEFAULT_FG_LIGHT;
+ }
+ }
+ else if(cell->reverse)
+ {
+ const uint32_t tmp = fg;
+
+ fg = bg;
+ bg = tmp;
+ }
+
+ style.text_fill_color[D2TK_TRIPLE_NONE] = bg;
+ style.text_stroke_color[D2TK_TRIPLE_NONE] = fg;
+
+ if(cell->bold)
+ {
+ style.font_face = "FiraCode-Bold.ttf";
+ }
+ else if(cell->italic)
+ {
+ style.font_face = "FiraCode-Light.ttf";
+ }
+ else
+ {
+ style.font_face = "FiraCode-Regular.ttf";
+ }
+
+ d2tk_base_set_style(base, &style);
+
+ d2tk_base_label(base, cell->lbl_len, cell->lbl, 1.f, trect,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_TOP);
+
+ d2tk_base_set_style(base, old_style);
+
+ if(cell->cursor)
+ {
+ style.font_face = "FiraCode-Bold.ttf";
+ style.text_fill_color[D2TK_TRIPLE_NONE] = 0x0;
+ style.text_stroke_color[D2TK_TRIPLE_NONE] = focus
+ ? DEFAULT_FG
+ : DEFAULT_FG_LIGHT;
+ d2tk_base_set_style(base, &style);
+
+ // draw underline cursor overlay
+ if(vpty->cursor_shape == VTERM_PROP_CURSORSHAPE_UNDERLINE)
+ {
+ static const char lbl [2] = "_";
+
+ d2tk_base_label(base, sizeof(lbl), lbl, 1.f, trect,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_TOP);
+ }
+ // draw bar cursor overlay
+ else if(vpty->cursor_shape == VTERM_PROP_CURSORSHAPE_BAR_LEFT)
+ {
+ static const char lbl [2] = "|";
+
+ d2tk_rect_t bnd = *trect;
+ bnd.x -= bnd.w/2;
+
+ d2tk_base_label(base, sizeof(lbl), lbl, 1.f, &bnd,
+ D2TK_ALIGN_LEFT | D2TK_ALIGN_TOP);
+ }
+
+ d2tk_base_set_style(base, old_style);
+ }
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_pty(d2tk_base_t *base, d2tk_id_t id, d2tk_clone_t clone, void *data,
+ d2tk_coord_t height, const d2tk_rect_t *rect, bool reinit)
+{
+ d2tk_atom_body_pty_t *vpty = _d2tk_base_get_atom(base, id, D2TK_ATOM_PTY,
+ _term_event);
+
+ const d2tk_coord_t width = height / 2;
+ const d2tk_coord_t ncols = rect->w / width;
+ const d2tk_coord_t nrows = rect->h / height;
+
+ if(reinit)
+ {
+ _term_deinit(vpty);
+ }
+
+ if(vpty->height == 0)
+ {
+ if(_term_init(vpty, clone, data, height, ncols, nrows) != 0)
+ {
+ fprintf(stderr, "[%s] _term_init failed\n", __func__);
+ }
+ }
+
+ const d2tk_style_t *old_style = d2tk_base_get_style(base);
+ d2tk_style_t style = *old_style;
+
+ style.fill_color[D2TK_TRIPLE_ACTIVE] = _term_dark(vpty);
+ style.fill_color[D2TK_TRIPLE_ACTIVE_HOT] = _term_light(vpty);
+ style.fill_color[D2TK_TRIPLE_ACTIVE_FOCUS] = _term_dark(vpty);
+ style.fill_color[D2TK_TRIPLE_ACTIVE_HOT_FOCUS] = _term_light(vpty);
+ style.font_face = "FiraCode-Regular.ttf";
+ d2tk_base_set_style(base, &style);
+
+ _term_resize(vpty, ncols, nrows);
+
+ d2tk_state_t state = _term_behave(base, vpty, id, rect);
+
+ _term_input(vpty);
+
+ _term_draw(base, vpty, rect, d2tk_state_is_focused(state));
+
+ if(_term_done(vpty))
+ {
+ _term_deinit(vpty);
+
+ state |= D2TK_STATE_CLOSE;
+ }
+
+ if(vpty->bell)
+ {
+ state |= D2TK_STATE_BELL;
+
+ vpty->bell = 0;
+ }
+
+ d2tk_base_set_style(base, old_style);
+
+ return state;
+}
diff --git a/src/base_scrollbar.c b/src/base_scrollbar.c
new file mode 100644
index 0000000..c98b038
--- /dev/null
+++ b/src/base_scrollbar.c
@@ -0,0 +1,331 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+typedef struct _d2tk_atom_body_scroll_t d2tk_atom_body_scroll_t;
+
+struct _d2tk_atom_body_scroll_t {
+ float offset [2];
+};
+
+struct _d2tk_scrollbar_t {
+ d2tk_id_t id;
+ d2tk_flag_t flags;
+ int32_t max [2];
+ int32_t num [2];
+ d2tk_atom_body_scroll_t *atom_body;
+ const d2tk_rect_t *rect;
+ d2tk_rect_t sub;
+};
+
+const size_t d2tk_atom_body_scroll_sz = sizeof(d2tk_atom_body_scroll_t);
+const size_t d2tk_scrollbar_sz = sizeof(d2tk_scrollbar_t);
+
+D2TK_API d2tk_scrollbar_t *
+d2tk_scrollbar_begin(d2tk_base_t *base, const d2tk_rect_t *rect, d2tk_id_t id,
+ d2tk_flag_t flags, const uint32_t max [2], const uint32_t num [2],
+ d2tk_scrollbar_t *scrollbar)
+{
+ scrollbar->id = id;
+ scrollbar->flags = flags;
+ scrollbar->max[0] = max[0];
+ scrollbar->max[1] = max[1];
+ scrollbar->num[0] = num[0];
+ scrollbar->num[1] = num[1];
+ scrollbar->rect = rect;
+ scrollbar->sub = *rect;
+ scrollbar->atom_body = _d2tk_base_get_atom(base, id, D2TK_ATOM_SCROLL, NULL);
+
+ const d2tk_coord_t s = 10; //FIXME
+
+ if(flags & D2TK_FLAG_SCROLL_X)
+ {
+ scrollbar->sub.h -= s;
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_Y)
+ {
+ scrollbar->sub.w -= s;
+ }
+
+ return scrollbar;
+}
+
+D2TK_API bool
+d2tk_scrollbar_not_end(d2tk_scrollbar_t *scrollbar)
+{
+ return scrollbar ? true : false;
+}
+
+static void
+_d2tk_draw_scrollbar(d2tk_core_t *core, d2tk_state_t hstate, d2tk_state_t vstate,
+ const d2tk_rect_t *hbar, const d2tk_rect_t *vbar, const d2tk_style_t *style,
+ d2tk_flag_t flags)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &hstate, sizeof(d2tk_state_t) },
+ { &vstate, sizeof(d2tk_state_t) },
+ { hbar, sizeof(d2tk_rect_t) },
+ { vbar, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &flags, sizeof(d2tk_flag_t) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ if(flags & D2TK_FLAG_SCROLL_X)
+ {
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(hstate))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(hstate))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(hstate))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ const size_t ref = d2tk_core_bbox_push(core, true, hbar);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, hbar, style->rounding);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, hbar, style->rounding);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);\
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_Y)
+ {
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_active(vstate))
+ {
+ triple |= D2TK_TRIPLE_ACTIVE;
+ }
+
+ if(d2tk_state_is_hot(vstate))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(vstate))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ const size_t ref = d2tk_core_bbox_push(core, true, vbar);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, vbar, style->rounding);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_rounded_rect(core, vbar, style->rounding);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
+
+D2TK_API d2tk_scrollbar_t *
+d2tk_scrollbar_next(d2tk_base_t *base, d2tk_scrollbar_t *scrollbar)
+{
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+ const d2tk_coord_t s = 10; //FIXME
+ const d2tk_coord_t s2 = s*2;
+
+ const d2tk_id_t id = scrollbar->id;
+ d2tk_flag_t flags = scrollbar->flags;
+ const int32_t *max = scrollbar->max;
+ const int32_t *num = scrollbar->num;
+ const d2tk_rect_t *rect = scrollbar->rect;
+ float *scroll = scrollbar->atom_body->offset;
+
+ const int32_t rel_max [2] = {
+ max[0] - num[0],
+ max[1] - num[1]
+ };
+ d2tk_rect_t hbar = {
+ .x = rect->x,
+ .y = rect->y + rect->h - s,
+ .w = rect->w,
+ .h = s
+ };
+ d2tk_rect_t vbar = {
+ .x = rect->x + rect->w - s,
+ .y = rect->y,
+ .w = s,
+ .h = rect->h
+ };
+ d2tk_rect_t sub = *rect;
+ d2tk_state_t hstate = D2TK_STATE_NONE;
+ d2tk_state_t vstate = D2TK_STATE_NONE;
+
+ const d2tk_id_t hid = (1 << 24) | id;
+ const d2tk_id_t vid = (2 << 24) | id;
+
+ if(max[0] < num[0])
+ {
+ flags &= ~D2TK_FLAG_SCROLL_X;
+ }
+
+ if(max[1] < num[1])
+ {
+ flags &= ~D2TK_FLAG_SCROLL_Y;
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_X)
+ {
+ sub.h -= s;
+
+ hstate |= d2tk_base_is_active_hot(base, hid, &hbar, D2TK_FLAG_SCROLL_X);
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_Y)
+ {
+ sub.w -= s;
+
+ vstate |= d2tk_base_is_active_hot(base, vid, &vbar, D2TK_FLAG_SCROLL_Y);
+ }
+
+ if(d2tk_base_is_hit(base, &sub))
+ {
+ if(flags & D2TK_FLAG_SCROLL_X)
+ {
+ hstate |= _d2tk_base_is_active_hot_horizontal_scroll(base);
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_Y)
+ {
+ vstate |= _d2tk_base_is_active_hot_vertical_scroll(base);
+ }
+ }
+
+ const float old_scroll [2] = {
+ scroll[0],
+ scroll[1]
+ };
+
+ if(flags & D2TK_FLAG_SCROLL_X)
+ {
+ int32_t dw = hbar.w * num[0] / max[0];
+ d2tk_clip_int32(s2, &dw, dw);
+ const float w = (float)(hbar.w - dw) / rel_max[0];
+
+ if(d2tk_state_is_scroll_right(hstate))
+ {
+ scroll[0] += base->scroll.odx;
+ }
+ else if(d2tk_state_is_scroll_left(hstate))
+ {
+ scroll[0] += base->scroll.odx;
+ }
+ else if(d2tk_state_is_motion(hstate))
+ {
+ const float adx = base->mouse.dx;
+
+ scroll[0] += adx / w;
+ }
+
+ // always do clipping, as max may have changed in due course
+ d2tk_clip_float(0, &scroll[0], rel_max[0]);
+
+ hbar.w = dw;
+ hbar.x += scroll[0]*w;
+ }
+
+ if(flags & D2TK_FLAG_SCROLL_Y)
+ {
+ int32_t dh = vbar.h * num[1] / max[1];
+ d2tk_clip_int32(s2, &dh, dh);
+ const float h = (float)(vbar.h - dh) / rel_max[1];
+
+ if(d2tk_state_is_scroll_down(vstate))
+ {
+ scroll[1] -= base->scroll.ody;
+ }
+ else if(d2tk_state_is_scroll_up(vstate))
+ {
+ scroll[1] -= base->scroll.ody;
+ }
+ else if(d2tk_state_is_motion(vstate))
+ {
+ const float ady = base->mouse.dy;
+
+ scroll[1] += ady / h;
+ }
+
+ // always do clipping, as max may have changed in due course
+ d2tk_clip_float(0, &scroll[1], rel_max[1]);
+
+ vbar.h = dh;
+ vbar.y += scroll[1]*h;
+ }
+
+ if( (old_scroll[0] != scroll[0]) || (old_scroll[1] != scroll[1]) )
+ {
+ d2tk_base_set_again(base);
+ }
+
+ d2tk_core_t *core = base->core;
+
+ _d2tk_draw_scrollbar(core, hstate, vstate, &hbar, &vbar, style, flags);
+
+ //return state; //FIXME
+ return NULL;
+}
+
+D2TK_API float
+d2tk_scrollbar_get_offset_y(d2tk_scrollbar_t *scrollbar)
+{
+ return scrollbar->atom_body->offset[1];
+}
+
+D2TK_API float
+d2tk_scrollbar_get_offset_x(d2tk_scrollbar_t *scrollbar)
+{
+ return scrollbar->atom_body->offset[0];
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_scrollbar_get_rect(d2tk_scrollbar_t *scrollbar)
+{
+ return &scrollbar->sub;
+}
diff --git a/src/base_table.c b/src/base_table.c
new file mode 100644
index 0000000..eefa6e1
--- /dev/null
+++ b/src/base_table.c
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include "base_internal.h"
+
+struct _d2tk_table_t {
+ unsigned x;
+ unsigned y;
+ unsigned N;
+ unsigned NM;
+ unsigned k;
+ unsigned x0;
+ d2tk_rect_t rect;
+};
+
+const size_t d2tk_table_sz = sizeof(d2tk_table_t);
+
+D2TK_API d2tk_table_t *
+d2tk_table_begin(const d2tk_rect_t *rect, unsigned N, unsigned M,
+ d2tk_flag_t flag, d2tk_table_t *tab)
+{
+ if( (N == 0) || (M == 0) )
+ {
+ return NULL;
+ }
+
+ unsigned w;
+ unsigned h;
+
+ tab->x = 0;
+ tab->y = 0;
+ tab->k = 0;
+ tab->x0 = rect->x;
+
+ if(flag & D2TK_FLAG_TABLE_REL)
+ {
+ w = rect->w / N;
+ h = rect->h / M;
+ }
+ else
+ {
+ w = N;
+ h = M;
+
+ N = rect->w / N;
+ M = rect->h / M;
+ }
+
+ tab->N = N;
+ tab->NM = N*M;
+
+ tab->rect.x = rect->x;
+ tab->rect.y = rect->y;
+ tab->rect.w = w;
+ tab->rect.h = h;
+
+ return tab;
+}
+
+D2TK_API bool
+d2tk_table_not_end(d2tk_table_t *tab)
+{
+ return tab && (tab->k < tab->NM);
+}
+
+D2TK_API d2tk_table_t *
+d2tk_table_next(d2tk_table_t *tab)
+{
+ ++tab->k;
+
+ if(++tab->x % tab->N)
+ {
+ tab->rect.x += tab->rect.w;
+ }
+ else // overflow
+ {
+ tab->x = 0;
+ ++tab->y;
+
+ tab->rect.x = tab->x0;
+ tab->rect.y += tab->rect.h;
+ }
+
+ return tab;
+}
+
+D2TK_API unsigned
+d2tk_table_get_index(d2tk_table_t *tab)
+{
+ return tab->k;
+}
+
+D2TK_API unsigned
+d2tk_table_get_index_x(d2tk_table_t *tab)
+{
+ return tab->x;
+}
+
+D2TK_API unsigned
+d2tk_table_get_index_y(d2tk_table_t *tab)
+{
+ return tab->y;
+}
+
+D2TK_API const d2tk_rect_t *
+d2tk_table_get_rect(d2tk_table_t *tab)
+{
+ return &tab->rect;
+}
diff --git a/src/base_textfield.c b/src/base_textfield.c
new file mode 100644
index 0000000..9d0fbc1
--- /dev/null
+++ b/src/base_textfield.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2018-2019 Hanspeter Portner (dev@open-music-kontrollers.ch)
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the Artistic License 2.0 as published by
+ * The Perl Foundation.
+ *
+ * This source is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Artistic License 2.0 for more details.
+ *
+ * You should have received a copy of the Artistic License 2.0
+ * along the source as a COPYING file. If not, obtain it from
+ * http://www.perlfoundation.org/artistic_license_2_0.
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "base_internal.h"
+
+static void
+_d2tk_base_draw_text_field(d2tk_core_t *core, d2tk_state_t state,
+ const d2tk_rect_t *rect, const d2tk_style_t *style, char *value,
+ d2tk_align_t align)
+{
+ const d2tk_hash_dict_t dict [] = {
+ { &state, sizeof(d2tk_state_t) },
+ { rect, sizeof(d2tk_rect_t) },
+ { style, sizeof(d2tk_style_t) },
+ { &align, sizeof(d2tk_align_t) },
+ { value, strlen(value) },
+ { NULL, 0 }
+ };
+ const uint64_t hash = d2tk_hash_dict(dict);
+
+ D2TK_CORE_WIDGET(core, hash, widget)
+ {
+ d2tk_triple_t triple = D2TK_TRIPLE_NONE;
+
+ if(d2tk_state_is_hot(state))
+ {
+ triple |= D2TK_TRIPLE_HOT;
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ triple |= D2TK_TRIPLE_FOCUS;
+ }
+
+ d2tk_rect_t bnd;
+ d2tk_rect_shrink(&bnd, rect, style->padding);
+
+ const d2tk_coord_t h_8 = bnd.h / 8;
+
+ {
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ // draw background
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd);
+ d2tk_core_color(core, style->fill_color[triple]);
+ d2tk_core_stroke_width(core, 0);
+ d2tk_core_fill(core);
+
+ // draw lines above and below text
+ const d2tk_coord_t x0 = bnd.x;
+ const d2tk_coord_t x1 = bnd.x + bnd.w;
+ const d2tk_coord_t y0 = bnd.y + h_8;
+ const d2tk_coord_t y1 = bnd.y + bnd.h - h_8;
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y0);
+ d2tk_core_line_to(core, x1, y0);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_begin_path(core);
+ d2tk_core_move_to(core, x0, y1);
+ d2tk_core_line_to(core, x1, y1);
+ d2tk_core_color(core, style->stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ // draw bounding box
+ d2tk_core_begin_path(core);
+ d2tk_core_rect(core, &bnd);
+ d2tk_core_color(core, style->stroke_color[triple]);
+ d2tk_core_stroke_width(core, style->border_width);
+ d2tk_core_stroke(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+
+ const size_t valuelen= strlen(value);
+ if(valuelen)
+ {
+ const d2tk_coord_t h_2 = bnd.h / 2;
+
+ d2tk_rect_t bnd2;
+ d2tk_rect_shrink_x(&bnd2, &bnd, h_8);
+
+ const size_t ref = d2tk_core_bbox_push(core, true, rect);
+
+ d2tk_core_save(core);
+ d2tk_core_scissor(core, &bnd2);
+ d2tk_core_font_size(core, h_2);
+ d2tk_core_font_face(core, strlen(style->font_face), style->font_face);
+ d2tk_core_color(core, style->text_stroke_color[D2TK_TRIPLE_NONE]);
+ d2tk_core_text(core, &bnd2, valuelen, value, align);
+ d2tk_core_restore(core);
+
+ d2tk_core_bbox_pop(core, ref);
+ }
+ }
+}
+
+D2TK_API d2tk_state_t
+d2tk_base_text_field(d2tk_base_t *base, d2tk_id_t id, const d2tk_rect_t *rect,
+ size_t maxlen, char *value, d2tk_align_t align, const char *accept)
+{
+ const d2tk_style_t *style = d2tk_base_get_style(base);
+
+ d2tk_state_t state = d2tk_base_is_active_hot(base, id, rect, D2TK_FLAG_NONE);
+
+ if(d2tk_state_is_focus_in(state))
+ {
+ _d2tk_base_clear_chars(base); // eat keys
+
+ // copy text from value to edit.text_in
+ strncpy(base->edit.text_in, value, maxlen);
+ }
+
+ if(d2tk_state_is_focused(state))
+ {
+ // use edit.text_in
+ value = base->edit.text_in;
+
+ if(d2tk_base_get_keymask(base, D2TK_KEYMASK_BACKSPACE, true))
+ {
+ const ssize_t ulen = utf8len(value) - 1;
+ char *head = value;
+ utf8_int32_t codepoint;
+
+ for(ssize_t i = 0; i < ulen; i++)
+ {
+ head = utf8codepoint(head, &codepoint);
+ }
+
+ head[0] = '\0';
+ //_d2tk_base_clear_chars(base); // eat key
+ }
+ else if(d2tk_base_get_keymask(base, D2TK_KEYMASK_DEL, true))
+ {
+ memset(value, 0x0, maxlen);
+ //_d2tk_base_clear_chars(base); // eat key
+ }
+
+ if(base->keys.nchars)
+ {
+ const utf8_int32_t *head = base->keys.chars;
+
+ const ssize_t len = strlen(value);
+ char *tail = &value[len];
+
+ utf8_int32_t codepoint;
+
+ for(size_t i = 0; i < base->keys.nchars; i++)
+ {
+ codepoint = head[i];
+
+ if(accept && !utf8chr(accept, codepoint))
+ {
+ continue;
+ }
+
+ const ssize_t left = maxlen - (tail - value);
+ if(left > 0)
+ {
+ tail = utf8catcodepoint(tail, codepoint, left);
+ }
+ }
+
+ _d2tk_base_clear_chars(base); // eat keys
+ }
+
+ char *buf = alloca(maxlen + 1);
+ if(buf)
+ {
+ snprintf(buf, maxlen, "%s|", value);
+ value = buf;
+ }
+ }
+
+ if(d2tk_state_is_focus_out(state))
+ {
+ // copy text from edit.text_out to value
+ strncpy(value, base->edit.text_out, maxlen);
+
+ state |= D2TK_STATE_CHANGED;
+ }
+
+ //FIXME handle d2tk_state_is_enter(state)
+
+ d2tk_core_t *core = base->core;
+
+ _d2tk_base_draw_text_field(core, state, rect, style, value, align);
+
+ return state;
+}
diff --git a/src/core.c b/src/core.c
index eb2ed97..fbf9864 100644
--- a/src/core.c
+++ b/src/core.c
@@ -260,7 +260,7 @@ _d2tk_mem_reset(d2tk_mem_t *mem)
memset(mem->buf, 0x0, mem->size);
}
-static uintptr_t *
+static inline uintptr_t *
_d2tk_core_get_memcache(d2tk_core_t *core, uint64_t hash)
{
for(unsigned i = 0; i < _D2TK_MEMCACHES_MAX; i++)
@@ -484,7 +484,74 @@ _d2tk_bitmap_fill(d2tk_core_t *core, const d2tk_clip_t *clip)
bitmap->nfills++;
}
-static void
+static inline d2tk_com_t *
+_d2tk_com_begin(d2tk_com_t *com)
+{
+ d2tk_com_t *bbox = (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
+ + D2TK_PAD_SIZE(sizeof(d2tk_body_bbox_t)));
+
+ return bbox;
+}
+
+static inline d2tk_com_t *
+_d2tk_com_get_end(d2tk_com_t *com)
+{
+ return (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
+ + com->size);
+}
+
+static inline bool
+_d2tk_com_not_end(d2tk_com_t *end, d2tk_com_t *bbox)
+{
+ return bbox < end;
+}
+
+static inline d2tk_com_t *
+_d2tk_com_next(d2tk_com_t *bbox)
+{
+ d2tk_com_t *nxt = (d2tk_com_t *)((uint8_t *)bbox + sizeof(d2tk_com_t)
+ + D2TK_PAD_SIZE(bbox->size));
+
+ return nxt;
+}
+
+const d2tk_com_t *
+d2tk_com_begin_const(const d2tk_com_t *com)
+{
+ return _d2tk_com_begin((d2tk_com_t *)com);
+}
+
+const d2tk_com_t *
+d2tk_com_get_end_const(const d2tk_com_t *com)
+{
+ return _d2tk_com_get_end((d2tk_com_t *)com);
+}
+
+bool
+d2tk_com_not_end_const(const d2tk_com_t *end, const d2tk_com_t *bbox)
+{
+ return _d2tk_com_not_end((d2tk_com_t *)end, (d2tk_com_t *)bbox);
+}
+
+const d2tk_com_t *
+d2tk_com_next_const(const d2tk_com_t *bbox)
+{
+ return _d2tk_com_next((d2tk_com_t *)bbox);
+}
+
+#define D2TK_COM_FOREACH_FROM(COM, FROM, BBOX) \
+ for(d2tk_com_t *(BBOX) = (FROM), \
+ *__end = _d2tk_com_get_end((COM)); \
+ _d2tk_com_not_end(__end, (BBOX)); \
+ (BBOX) = _d2tk_com_next((BBOX)))
+
+#define D2TK_COM_FOREACH(COM, BBOX) \
+ for(d2tk_com_t *(BBOX) = _d2tk_com_begin((COM)), \
+ *__end = _d2tk_com_get_end((COM)); \
+ _d2tk_com_not_end(__end, (BBOX)); \
+ (BBOX) = _d2tk_com_next((BBOX)))
+
+static inline void
_d2tk_bbox_mask(d2tk_core_t *core, d2tk_com_t *com)
{
d2tk_body_bbox_t *body = &com->body->bbox;
@@ -518,7 +585,7 @@ d2tk_core_get_pixels(d2tk_core_t *core, d2tk_rect_t *rect)
return core->bitmap.pixels;
}
-static bool
+static inline bool
_d2tk_bitmap_query(d2tk_core_t *core, d2tk_body_bbox_t *body)
{
const d2tk_clip_t *clip = &body->clip;
@@ -561,7 +628,7 @@ _d2tk_mem_compact(d2tk_mem_t *mem)
}
}
-static d2tk_com_t *
+static inline d2tk_com_t *
_d2tk_mem_get_com(d2tk_mem_t *mem)
{
return (d2tk_com_t *)&mem->buf[0];
@@ -1162,56 +1229,6 @@ d2tk_core_stroke_width(d2tk_core_t *core, d2tk_coord_t width)
}
}
-const d2tk_com_t *
-d2tk_com_begin_const(const d2tk_com_t *com)
-{
- d2tk_com_t *bbox = (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
- + D2TK_PAD_SIZE(sizeof(d2tk_body_bbox_t)));
-
- return bbox;
-}
-
-bool
-d2tk_com_not_end_const(const d2tk_com_t *com, const d2tk_com_t *bbox)
-{
- return bbox < (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
- + com->size);
-}
-
-const d2tk_com_t *
-d2tk_com_next_const(const d2tk_com_t *bbox)
-{
- d2tk_com_t *nxt = (d2tk_com_t *)((uint8_t *)bbox + sizeof(d2tk_com_t)
- + D2TK_PAD_SIZE(bbox->size));
-
- return nxt;
-}
-
-d2tk_com_t *
-d2tk_com_begin(d2tk_com_t *com)
-{
- d2tk_com_t *bbox = (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
- + D2TK_PAD_SIZE(sizeof(d2tk_body_bbox_t)));
-
- return bbox;
-}
-
-bool
-d2tk_com_not_end(d2tk_com_t *com, d2tk_com_t *bbox)
-{
- return bbox < (d2tk_com_t *)((uint8_t *)com + sizeof(d2tk_com_t)
- + com->size);
-}
-
-d2tk_com_t *
-d2tk_com_next(d2tk_com_t *bbox)
-{
- d2tk_com_t *nxt = (d2tk_com_t *)((uint8_t *)bbox + sizeof(d2tk_com_t)
- + D2TK_PAD_SIZE(bbox->size));
-
- return nxt;
-}
-
D2TK_API void
d2tk_core_pre(d2tk_core_t *core)
{
@@ -1223,9 +1240,8 @@ d2tk_core_pre(d2tk_core_t *core)
&D2TK_RECT(0, 0, core->w, core->h));
}
-static bool
-_d2tk_com_equal(const d2tk_com_t *curcom, const d2tk_com_t *oldcom,
- bool check_for_container)
+static inline bool
+_d2tk_com_equal_container(const d2tk_com_t *curcom, const d2tk_com_t *oldcom)
{
const d2tk_body_bbox_t *curbbox = &curcom->body->bbox;
const d2tk_body_bbox_t *oldbbox = &oldcom->body->bbox;
@@ -1239,7 +1255,7 @@ _d2tk_com_equal(const d2tk_com_t *curcom, const d2tk_com_t *oldcom,
{
return true;
}
- else if(check_for_container && curbbox->container && oldbbox->container)
+ else if(curbbox->container && oldbbox->container)
{
return true;
}
@@ -1248,11 +1264,29 @@ _d2tk_com_equal(const d2tk_com_t *curcom, const d2tk_com_t *oldcom,
return false;
}
-static void
+static inline bool
+_d2tk_com_equal(const d2tk_com_t *curcom, const d2tk_com_t *oldcom)
+{
+ const d2tk_body_bbox_t *curbbox = &curcom->body->bbox;
+ const d2tk_body_bbox_t *oldbbox = &oldcom->body->bbox;
+
+ if( (curcom->instr == oldcom->instr)
+ && (curbbox->clip.x0 == oldbbox->clip.x0)
+ && (curbbox->clip.y0 == oldbbox->clip.y0)
+ && (curcom->size == oldcom->size)
+ && (curbbox->hash == oldbbox->hash) )
+ {
+ return true;
+ }
+
+ return false;
+}
+
+static inline void
_d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
{
// look for (dis)appeared instructions
- d2tk_com_t *tmpcom = d2tk_com_begin(curcom_ref);
+ d2tk_com_t *tmpcom = _d2tk_com_begin(curcom_ref);
D2TK_COM_FOREACH(oldcom_ref, oldcom)
{
@@ -1271,11 +1305,11 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
}
// check for matching size, instruction, hash and position
- if(_d2tk_com_equal(curcom, oldcom, true))
+ if(_d2tk_com_equal_container(curcom, oldcom))
{
for(d2tk_com_t *curcom2 = tmpcom;
curcom2 != curcom;
- curcom2 = d2tk_com_next(curcom2))
+ curcom2 = _d2tk_com_next(curcom2))
{
if(curcom2->instr != D2TK_INSTR_BBOX)
{
@@ -1305,7 +1339,7 @@ _d2tk_diff(d2tk_core_t *core, d2tk_com_t *curcom_ref, d2tk_com_t *oldcom_ref)
}
match = true;
- tmpcom = d2tk_com_next(curcom);
+ tmpcom = _d2tk_com_next(curcom);
break;
}
@@ -1378,7 +1412,7 @@ d2tk_core_post(d2tk_core_t *core)
_d2tk_sprites_free(core);
_d2tk_memcaches_free(core);
}
- else if(!_d2tk_com_equal(curcom, oldcom, false))
+ else if(!_d2tk_com_equal(curcom, oldcom))
{
_d2tk_diff(core, curcom, oldcom);
}
@@ -1576,7 +1610,7 @@ d2tk_core_set_dimensions(d2tk_core_t *core, d2tk_coord_t w, d2tk_coord_t h)
{
core->w = w;
core->h = h;
- core->full_refresh = true;
+ d2tk_core_set_full_refresh(core);
_d2tk_bitmap_resize(core, w, h);
}
@@ -1593,3 +1627,9 @@ d2tk_core_get_dimensions(d2tk_core_t *core, d2tk_coord_t *w, d2tk_coord_t *h)
*h = core->h;
}
}
+
+D2TK_API void
+d2tk_core_set_full_refresh(d2tk_core_t *core)
+{
+ core->full_refresh = true;
+}
diff --git a/src/core_internal.h b/src/core_internal.h
index 951c605..d281832 100644
--- a/src/core_internal.h
+++ b/src/core_internal.h
@@ -268,36 +268,21 @@ d2tk_core_get_sprite(d2tk_core_t *core, uint64_t hash, uint8_t type);
const d2tk_com_t *
d2tk_com_begin_const(const d2tk_com_t *com);
+const d2tk_com_t *
+d2tk_com_get_end_const(const d2tk_com_t *com);
+
bool
-d2tk_com_not_end_const(const d2tk_com_t *com, const d2tk_com_t *bbox);
+d2tk_com_not_end_const(const d2tk_com_t *end, const d2tk_com_t *bbox);
const d2tk_com_t *
d2tk_com_next_const(const d2tk_com_t *bbox);
#define D2TK_COM_FOREACH_CONST(COM, BBOX) \
- for(const d2tk_com_t *(BBOX) = d2tk_com_begin_const((COM)); \
- d2tk_com_not_end_const((COM), (BBOX)); \
+ for(const d2tk_com_t *(BBOX) = d2tk_com_begin_const((COM)), \
+ *__end = d2tk_com_get_end_const((COM)); \
+ d2tk_com_not_end_const(__end, (BBOX)); \
(BBOX) = d2tk_com_next_const((BBOX)))
-d2tk_com_t *
-d2tk_com_begin(d2tk_com_t *com);
-
-bool
-d2tk_com_not_end(d2tk_com_t *com, d2tk_com_t *bbox);
-
-d2tk_com_t *
-d2tk_com_next(d2tk_com_t *bbox);
-
-#define D2TK_COM_FOREACH(COM, BBOX) \
- for(d2tk_com_t *(BBOX) = d2tk_com_begin((COM)); \
- d2tk_com_not_end((COM), (BBOX)); \
- (BBOX) = d2tk_com_next((BBOX)))
-
-#define D2TK_COM_FOREACH_FROM(COM, FROM, BBOX) \
- for(d2tk_com_t *(BBOX) = (FROM); \
- d2tk_com_not_end((COM), (BBOX)); \
- (BBOX) = d2tk_com_next((BBOX)))
-
uint32_t *
d2tk_core_get_pixels(d2tk_core_t *core, d2tk_rect_t *rect);
diff --git a/src/frontend_fbdev.c b/src/frontend_fbdev.c
index e63ecff..e5121ee 100644
--- a/src/frontend_fbdev.c
+++ b/src/frontend_fbdev.c
@@ -574,6 +574,9 @@ d2tk_fbdev_step(d2tk_fbdev_t *fbdev)
case LIBINPUT_EVENT_TABLET_PAD_BUTTON:
case LIBINPUT_EVENT_TABLET_PAD_RING:
case LIBINPUT_EVENT_TABLET_PAD_STRIP:
+#if D2TK_INPUT_1_15
+ case LIBINPUT_EVENT_TABLET_PAD_KEY:
+#endif
{
struct libinput_event_tablet_pad *evtp =
libinput_event_get_tablet_pad_event(ev);
diff --git a/src/frontend_pugl.c b/src/frontend_pugl.c
index 22340a0..c176d37 100644
--- a/src/frontend_pugl.c
+++ b/src/frontend_pugl.c
@@ -68,11 +68,6 @@ _d2tk_pugl_expose(d2tk_pugl_t *dpugl)
dpugl->config->expose(dpugl->config->data, w, h);
d2tk_base_post(base);
-
- if(d2tk_base_get_again(base))
- {
- puglPostRedisplay(dpugl->view);
- }
}
static void
@@ -123,6 +118,8 @@ _d2tk_pugl_event_func(PuglView *view, const PuglEvent *e)
// fall-through
case PUGL_FOCUS_OUT:
{
+ d2tk_base_set_full_refresh(base);
+
puglPostRedisplay(dpugl->view);
} break;
@@ -131,6 +128,7 @@ _d2tk_pugl_event_func(PuglView *view, const PuglEvent *e)
{
_d2tk_pugl_modifiers(dpugl, e->crossing.state);
d2tk_base_set_mouse_pos(base, e->crossing.x, e->crossing.y);
+ d2tk_base_set_full_refresh(base);
puglPostRedisplay(dpugl->view);
} break;
@@ -399,6 +397,7 @@ _d2tk_pugl_event_func(PuglView *view, const PuglEvent *e)
case PUGL_TEXT:
{
d2tk_base_append_utf8(base, e->text.character);
+
puglPostRedisplay(dpugl->view);
} break;
case PUGL_NOTHING:
@@ -413,6 +412,13 @@ _d2tk_pugl_event_func(PuglView *view, const PuglEvent *e)
D2TK_API int
d2tk_pugl_step(d2tk_pugl_t *dpugl)
{
+ d2tk_base_probe(dpugl->base);
+
+ if(d2tk_base_get_again(dpugl->base))
+ {
+ puglPostRedisplay(dpugl->view);
+ }
+
const PuglStatus stat = puglDispatchEvents(dpugl->world);
(void)stat;
diff --git a/src/mum.c b/src/hash.c
index 358a3dc..f02b7f2 100644
--- a/src/mum.c
+++ b/src/hash.c
@@ -24,16 +24,37 @@
#define SEED 12345
+typedef union _d2tk_hash_item_t {
+ uint64_t u64;
+ uint8_t u8 [8];
+} d2tk_hash_item_t;
+
__attribute__((always_inline))
static inline uint64_t
-_d2tk_hash(uint64_t hash, const void *key, size_t len)
+_d2tk_hash(const void *key, size_t len, uint64_t hash)
{
- return _mum_hash_aligned(hash + len, key, len);
+ if(len <= sizeof(uint64_t))
+ {
+ d2tk_hash_item_t item = { .u64 = 0 };
+ memcpy(item.u8, key, len);
+
+ return mum_hash_step(hash, item.u64);
+ }
+
+ return mum_hash(key, len, hash);
}
D2TK_API uint64_t
d2tk_hash(const void *key, size_t len)
{
+ if(len <= sizeof(uint64_t))
+ {
+ d2tk_hash_item_t item = { .u64 = 0 };
+ memcpy(item.u8, key, len);
+
+ return mum_hash64(item.u64, SEED);
+ }
+
return mum_hash(key, len, SEED);
}
@@ -43,13 +64,13 @@ d2tk_hash_foreach(const void *key, size_t len, ...)
va_list args;
uint64_t hash = mum_hash_init(SEED);
- hash = _d2tk_hash(hash, key, len);
+ hash = _d2tk_hash(key, len, hash);
va_start(args, len);
while( (key = va_arg(args, const void *)) )
{
- hash = _d2tk_hash(hash, key, va_arg(args, size_t));
+ hash = _d2tk_hash(key, va_arg(args, size_t), hash);
}
va_end(args);
@@ -64,7 +85,7 @@ d2tk_hash_dict(const d2tk_hash_dict_t *dict)
for( ; dict->key; dict++)
{
- hash = _d2tk_hash(hash, dict->key, dict->len);
+ hash = _d2tk_hash(dict->key, dict->len, hash);
}
return mum_hash_finish(hash);
diff --git a/test/base.c b/test/base.c
index 365f904..7405a31 100644
--- a/test/base.c
+++ b/test/base.c
@@ -1074,6 +1074,66 @@ _test_layout_relative_x()
}
static void
+_test_layout_relative_zero_x()
+{
+#define N 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ const d2tk_coord_t frac [N] = {
+ 0, 0, 0, 0
+ };
+ unsigned i = 0;
+ D2TK_BASE_LAYOUT(&rect, N, frac, D2TK_FLAG_LAYOUT_X | D2TK_FLAG_LAYOUT_REL, lay)
+ {
+ const d2tk_rect_t *bnd = d2tk_layout_get_rect(lay);
+ const unsigned k = d2tk_layout_get_index(lay);
+
+ assert(k == i++);
+ assert(bnd);
+ //FIXME bnd->x/y/w/h
+ }
+
+ d2tk_base_free(base);
+#undef N
+}
+
+static void
+_test_layout_relative_all_x()
+{
+#define N 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ const d2tk_coord_t frac [N] = {
+ 1, 1, 1, 1
+ };
+ unsigned i = 0;
+ D2TK_BASE_LAYOUT(&rect, N, frac, D2TK_FLAG_LAYOUT_X | D2TK_FLAG_LAYOUT_REL, lay)
+ {
+ const d2tk_rect_t *bnd = d2tk_layout_get_rect(lay);
+ const unsigned k = d2tk_layout_get_index(lay);
+
+ assert(k == i++);
+ assert(bnd);
+ //FIXME bnd->x/y/w/h
+ }
+
+ d2tk_base_free(base);
+#undef N
+}
+
+static void
_test_layout_relative_y()
{
#define N 4
@@ -1104,6 +1164,66 @@ _test_layout_relative_y()
}
static void
+_test_layout_relative_zero_y()
+{
+#define N 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ const d2tk_coord_t frac [N] = {
+ 0, 0, 0, 0
+ };
+ unsigned i = 0;
+ D2TK_BASE_LAYOUT(&rect, N, frac, D2TK_FLAG_LAYOUT_Y | D2TK_FLAG_LAYOUT_REL, lay)
+ {
+ const d2tk_rect_t *bnd = d2tk_layout_get_rect(lay);
+ const unsigned k = d2tk_layout_get_index(lay);
+
+ assert(k == i++);
+ assert(bnd);
+ //FIXME bnd->x/y/w/h
+ }
+
+ d2tk_base_free(base);
+#undef N
+}
+
+static void
+_test_layout_relative_all_y()
+{
+#define N 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ const d2tk_coord_t frac [N] = {
+ 1, 1, 1, 1
+ };
+ unsigned i = 0;
+ D2TK_BASE_LAYOUT(&rect, N, frac, D2TK_FLAG_LAYOUT_Y | D2TK_FLAG_LAYOUT_REL, lay)
+ {
+ const d2tk_rect_t *bnd = d2tk_layout_get_rect(lay);
+ const unsigned k = d2tk_layout_get_index(lay);
+
+ assert(k == i++);
+ assert(bnd);
+ //FIXME bnd->x/y/w/h
+ }
+
+ d2tk_base_free(base);
+#undef N
+}
+
+static void
_test_layout_absolute_x()
{
#define N 4
@@ -1314,6 +1434,12 @@ _test_state()
assert(d2tk_state_is_over(D2TK_STATE_OVER));
assert(!d2tk_state_is_over(D2TK_STATE_NONE));
+
+ assert(d2tk_state_is_close(D2TK_STATE_CLOSE));
+ assert(!d2tk_state_is_close(D2TK_STATE_NONE));
+
+ assert(d2tk_state_is_bell(D2TK_STATE_BELL));
+ assert(!d2tk_state_is_bell(D2TK_STATE_NONE));
}
static void
@@ -1401,7 +1527,9 @@ _test_scrollbar_x()
#define H 128
#define h 32
- D2TK_BASE_SCROLLBAR(base, &rect, D2TK_ID, D2TK_FLAG_SCROLL_X, H, 0, h, 0, scroll)
+ const uint32_t max [2] = { H, 0 };
+ const uint32_t num [2] = { h, 0 };
+ D2TK_BASE_SCROLLBAR(base, &rect, D2TK_ID, D2TK_FLAG_SCROLL_X, max, num, scroll)
{
const d2tk_rect_t *bnd = d2tk_scrollbar_get_rect(scroll);
const float xo = d2tk_scrollbar_get_offset_x(scroll);
@@ -1431,7 +1559,9 @@ _test_scrollbar_y()
#define V 128
#define v 32
- D2TK_BASE_SCROLLBAR(base, &rect, D2TK_ID, D2TK_FLAG_SCROLL_Y, 0, V, 0, v, scroll)
+ const uint32_t max [2] = { 0, V };
+ const uint32_t num [2] = { 0, v };
+ D2TK_BASE_SCROLLBAR(base, &rect, D2TK_ID, D2TK_FLAG_SCROLL_Y, max, num, scroll)
{
const d2tk_rect_t *bnd = d2tk_scrollbar_get_rect(scroll);
const float xo = d2tk_scrollbar_get_offset_x(scroll);
@@ -1749,6 +1879,27 @@ _test_meter()
}
static void
+_test_meter_down()
+{
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, true);
+
+ const int32_t val = -32;
+ const d2tk_state_t state = d2tk_base_meter(base, D2TK_ID, &rect, &val);
+ assert(state == (D2TK_STATE_DOWN | D2TK_STATE_ACTIVE | D2TK_STATE_HOT
+ | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN | D2TK_STATE_OVER) );
+
+ d2tk_base_free(base);
+}
+
+static void
_test_combo()
{
#define nitms 4
@@ -1791,11 +1942,39 @@ _test_combo_scroll_up()
const char *itms [nitms] = {
"1", "2", "3", "4"
};
- int32_t val = 0;
+ int32_t val = 1;
+ const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
+ assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
+ | D2TK_STATE_SCROLL_UP | D2TK_STATE_OVER | D2TK_STATE_CHANGED) );
+ assert(val == 2);
+ //FIXME test toggling
+
+ d2tk_base_free(base);
+#undef ntims
+}
+
+static void
+_test_combo_scroll_right()
+{
+#define nitms 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_add_mouse_scroll(base, 1.f, 0.f);
+
+ const char *itms [nitms] = {
+ "1", "2", "3", "4"
+ };
+ int32_t val = 1;
const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
- |D2TK_STATE_SCROLL_UP | D2TK_STATE_CHANGED | D2TK_STATE_OVER) );
- assert(val == 1);
+ | D2TK_STATE_SCROLL_RIGHT | D2TK_STATE_OVER | D2TK_STATE_CHANGED) );
+ assert(val == 2);
//FIXME test toggling
d2tk_base_free(base);
@@ -1819,10 +1998,38 @@ _test_combo_scroll_down()
const char *itms [nitms] = {
"1", "2", "3", "4"
};
- int32_t val = 0;
+ int32_t val = 1;
+ const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
+ assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
+ | D2TK_STATE_SCROLL_DOWN | D2TK_STATE_OVER | D2TK_STATE_CHANGED) );
+ assert(val == 0);
+ //FIXME test toggling
+
+ d2tk_base_free(base);
+#undef ntims
+}
+
+static void
+_test_combo_scroll_left()
+{
+#define nitms 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_add_mouse_scroll(base, -1.f, 0.f);
+
+ const char *itms [nitms] = {
+ "1", "2", "3", "4"
+ };
+ int32_t val = 1;
const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
- | D2TK_STATE_SCROLL_DOWN | D2TK_STATE_OVER) );
+ | D2TK_STATE_SCROLL_LEFT | D2TK_STATE_OVER | D2TK_STATE_CHANGED) );
assert(val == 0);
//FIXME test toggling
@@ -1831,6 +2038,95 @@ _test_combo_scroll_down()
}
static void
+_test_combo_mouse_down_dec()
+{
+#define nitms 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_set_mouse_pos(base, 0, 0);
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, true);
+
+ const char *itms [nitms] = {
+ "1", "2", "3", "4"
+ };
+ int32_t val = 1;
+ const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
+ assert(state == (D2TK_STATE_DOWN | D2TK_STATE_ACTIVE | D2TK_STATE_HOT
+ | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN | D2TK_STATE_OVER
+ | D2TK_STATE_CHANGED) );
+ assert(val == 0);
+ //FIXME test toggling
+
+ d2tk_base_free(base);
+#undef ntims
+}
+
+static void
+_test_combo_mouse_down_inc()
+{
+#define nitms 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_set_mouse_pos(base, DIM_W-1, DIM_H-1);
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, true);
+
+ const char *itms [nitms] = {
+ "1", "2", "3", "4"
+ };
+ int32_t val = 1;
+ const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
+ assert(state == (D2TK_STATE_DOWN | D2TK_STATE_ACTIVE | D2TK_STATE_HOT
+ | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN | D2TK_STATE_OVER
+ | D2TK_STATE_CHANGED) );
+ assert(val == 2);
+ //FIXME test toggling
+
+ d2tk_base_free(base);
+#undef ntims
+}
+
+static void
+_test_combo_mouse_down_equ()
+{
+#define nitms 4
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_base_set_mouse_pos(base, DIM_W/2, DIM_H/2);
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, true);
+
+ const char *itms [nitms] = {
+ NULL, NULL, NULL, NULL
+ };
+ int32_t val = 3;
+ const d2tk_state_t state = d2tk_base_combo(base, D2TK_ID, nitms, itms, &rect, &val);
+ assert(state == (D2TK_STATE_DOWN | D2TK_STATE_ACTIVE | D2TK_STATE_HOT
+ | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN | D2TK_STATE_OVER) );
+ assert(val == 3);
+ //FIXME test toggling
+
+ d2tk_base_free(base);
+#undef ntims
+}
+
+static void
_test_text_field()
{
d2tk_mock_ctx_t ctx = {
@@ -1877,6 +2173,48 @@ _test_label()
}
static void
+_test_label_null()
+{
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ const char *lbl= NULL;
+ const d2tk_state_t state = d2tk_base_label(base, -1, lbl, 0.8f, &rect,
+ D2TK_ALIGN_LEFT);
+ assert(state == D2TK_STATE_NONE);
+
+ d2tk_base_free(base);
+}
+
+static void
+_test_label_filled()
+{
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
+ assert(base);
+
+ d2tk_style_t style = *d2tk_base_get_default_style();
+ style.text_fill_color[D2TK_TRIPLE_NONE] = 0xff0000ff;
+ d2tk_base_set_style(base, &style);
+
+ const char *lbl= "label";
+ const d2tk_state_t state = d2tk_base_label(base, -1, lbl, 0.8f, &rect,
+ D2TK_ALIGN_LEFT);
+ assert(state == D2TK_STATE_NONE);
+
+ d2tk_base_free(base);
+}
+
+static void
_test_link()
{
d2tk_mock_ctx_t ctx = {
@@ -1884,13 +2222,46 @@ _test_link()
};
d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
+ assert(base);
+
+ {
+ const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W/2, DIM_H);
+ const char *lbl= "link1";
+ const d2tk_state_t state = d2tk_base_link(base, D2TK_ID, -1, lbl, 0.8f,
+ &rect, D2TK_ALIGN_LEFT);
+ assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
+ | D2TK_STATE_OVER) );
+ }
+
+ {
+ const d2tk_rect_t rect = D2TK_RECT(DIM_W/2, 0, DIM_W/2, DIM_H);
+ const char *lbl= "link2";
+ const d2tk_state_t state = d2tk_base_link(base, D2TK_ID, -1, lbl, 0.8f,
+ &rect, D2TK_ALIGN_LEFT);
+ assert(state == (D2TK_STATE_NONE) );
+ }
+
+ d2tk_base_free(base);
+}
+
+static void
+_test_link_down()
+{
+ d2tk_mock_ctx_t ctx = {
+ .check = NULL
+ };
+
+ d2tk_base_t *base = d2tk_base_new(&d2tk_mock_driver_lazy, &ctx);
const d2tk_rect_t rect = D2TK_RECT(0, 0, DIM_W, DIM_H);
assert(base);
+ d2tk_base_set_butmask(base, D2TK_BUTMASK_LEFT, true);
+
const char *lbl= "link";
const d2tk_state_t state = d2tk_base_link(base, D2TK_ID, -1, lbl, 0.8f, &rect,
D2TK_ALIGN_LEFT);
- assert(state == (D2TK_STATE_HOT | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN
+ assert(state == (D2TK_STATE_DOWN | D2TK_STATE_ACTIVE | D2TK_STATE_HOT
+ | D2TK_STATE_FOCUS | D2TK_STATE_FOCUS_IN | D2TK_STATE_CHANGED
| D2TK_STATE_OVER) );
d2tk_base_free(base);
@@ -2142,7 +2513,11 @@ main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
_test_frame_with_label();
_test_frame_wo_label();
_test_layout_relative_x();
+ _test_layout_relative_zero_x();
+ _test_layout_relative_all_x();
_test_layout_relative_y();
+ _test_layout_relative_zero_y();
+ _test_layout_relative_all_y();
_test_layout_absolute_x();
_test_layout_absolute_y();
_test_clip();
@@ -2164,12 +2539,21 @@ main(int argc __attribute__((unused)), char **argv __attribute__((unused)))
_test_bitmap();
_test_custom();
_test_meter();
+ _test_meter_down();
_test_combo();
_test_combo_scroll_up();
+ _test_combo_scroll_right();
_test_combo_scroll_down();
+ _test_combo_scroll_left();
+ _test_combo_mouse_down_dec();
+ _test_combo_mouse_down_inc();
+ _test_combo_mouse_down_equ();
_test_text_field();
_test_label();
+ _test_label_null();
+ _test_label_filled();
_test_link();
+ _test_link_down();
_test_dial_bool();
_test_dial_int32();
_test_dial_int64();
diff --git a/ttf/FiraCode-Bold.ttf b/ttf/FiraCode-Bold.ttf
new file mode 100644
index 0000000..5030383
--- /dev/null
+++ b/ttf/FiraCode-Bold.ttf
Binary files differ
diff --git a/ttf/FiraCode-Light.ttf b/ttf/FiraCode-Light.ttf
new file mode 100644
index 0000000..95913af
--- /dev/null
+++ b/ttf/FiraCode-Light.ttf
Binary files differ
diff --git a/ttf/FiraCode-Medium.ttf b/ttf/FiraCode-Medium.ttf
new file mode 100644
index 0000000..eca9e18
--- /dev/null
+++ b/ttf/FiraCode-Medium.ttf
Binary files differ
diff --git a/ttf/FiraCode-Regular.ttf b/ttf/FiraCode-Regular.ttf
new file mode 100644
index 0000000..97c1159
--- /dev/null
+++ b/ttf/FiraCode-Regular.ttf
Binary files differ