From c32e91b7f412d2a06d1e020eb4be2bf44e600f96 Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Fri, 14 May 2021 11:10:03 +0200 Subject: [PATCH] first version - and it works too! --- .gitignore | 16 ++ CMakeLists.txt | 53 +++++++ assets-cg/icon-sel.png | Bin 0 -> 12269 bytes assets-cg/icon-uns.png | Bin 0 -> 8723 bytes include/ft/all-tests.h | 21 +++ include/ft/test.h | 93 ++++++++++++ include/ft/util.h | 16 ++ include/ft/widgets/fbar.h | 36 +++++ include/ft/widgets/fbrowser.h | 59 ++++++++ include/ft/widgets/flist.h | 79 ++++++++++ include/ft/widgets/gscreen.h | 110 ++++++++++++++ src/ctype/trivialties.c | 28 ++++ src/main.c | 142 ++++++++++++++++++ src/string/core.c | 168 +++++++++++++++++++++ src/string/memarray.c | 199 +++++++++++++++++++++++++ src/string/memarray.h | 96 ++++++++++++ src/test.c | 114 ++++++++++++++ src/widgets/fbar.c | 187 +++++++++++++++++++++++ src/widgets/fbrowser.c | 273 ++++++++++++++++++++++++++++++++++ src/widgets/flist.c | 217 +++++++++++++++++++++++++++ src/widgets/gscreen.c | 188 +++++++++++++++++++++++ 21 files changed, 2095 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 assets-cg/icon-sel.png create mode 100644 assets-cg/icon-uns.png create mode 100644 include/ft/all-tests.h create mode 100644 include/ft/test.h create mode 100644 include/ft/util.h create mode 100644 include/ft/widgets/fbar.h create mode 100644 include/ft/widgets/fbrowser.h create mode 100644 include/ft/widgets/flist.h create mode 100644 include/ft/widgets/gscreen.h create mode 100644 src/ctype/trivialties.c create mode 100644 src/main.c create mode 100644 src/string/core.c create mode 100644 src/string/memarray.c create mode 100644 src/string/memarray.h create mode 100644 src/test.c create mode 100644 src/widgets/fbar.c create mode 100644 src/widgets/fbrowser.c create mode 100644 src/widgets/flist.c create mode 100644 src/widgets/gscreen.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b24b5b2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Build files +/build-fx +/build-cg +/*.g1a +/*.g3a + +# Development files +/assets-cg/icon.xcf + +# Python bytecode +__pycache__/ + +# Common IDE files +*.sublime-project +*.sublime-workspace +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0ecd459 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,53 @@ +# Configure with [fxsdk build-fx] or [fxsdk build-cg], which provide the +# toolchain file and module path of the fxSDK + +cmake_minimum_required(VERSION 3.15) +project(FxLibcTest) + +include(GenerateG1A) +include(GenerateG3A) +include(Fxconv) +find_package(Gint 2.4 REQUIRED) +find_package(JustUI 1.0 REQUIRED) + +# FXLIBC_INSTALL: Additional folders to read includes and libs from + +set(SOURCES + src/main.c + src/test.c + src/widgets/fbar.c + src/widgets/fbrowser.c + src/widgets/flist.c + src/widgets/gscreen.c + # ctype + src/ctype/trivialties.c + # string + src/string/memarray.c + src/string/core.c +) +# fx-9860G-only assets and fx-CG-50-only assets +set(ASSETS_fx +) +set(ASSETS_cg +) + +fxconv_declare_assets(${ASSETS} ${ASSETS_fx} ${ASSETS_cg} WITH_METADATA) + +add_executable(fxlibctest ${SOURCES} ${ASSETS} ${ASSETS_${FXSDK_PLATFORM}}) +target_compile_options(fxlibctest PRIVATE -Wall -Wextra -Os) +target_link_options(fxlibctest PRIVATE -Wl,-Map=map -Wl,--print-memory-usage) +target_include_directories(fxlibctest PRIVATE include/) +target_link_libraries(fxlibctest JustUI::JustUI Gint::Gint -lc) + +foreach(FOLDER IN LISTS FXLIBC_INSTALL) + target_include_directories(fxlibctest PRIVATE "${FOLDER}/include") + target_link_directories(fxlibctest PRIVATE "${FOLDER}/lib") +endforeach() + +if("${FXSDK_PLATFORM_LONG}" STREQUAL fx9860G) + generate_g1a(TARGET fxlibctest OUTPUT "FxLibcT.g1a" + NAME "FxLibc test" ICON assets-fx/icon.png) +elseif("${FXSDK_PLATFORM_LONG}" STREQUAL fxCG50) + generate_g3a(TARGET fxlibctest OUTPUT "FxLibcT.g3a" + NAME "FxLibc test" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png) +endif() diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png new file mode 100644 index 0000000000000000000000000000000000000000..44f0445d63f3ca76f5ccb26a81ab7406071a5275 GIT binary patch literal 12269 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 Hu0mjfzp|T; literal 0 HcmV?d00001 diff --git a/assets-cg/icon-uns.png b/assets-cg/icon-uns.png new file mode 100644 index 0000000000000000000000000000000000000000..3bee2eff70deb97cd26855dd478caf8789d6553f GIT binary patch literal 8723 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 + +/* ctype */ +extern ft_test ft_ctype_trivial_success; +extern ft_test ft_ctype_trivial_failure; +extern ft_test ft_ctype_trivial_empty; + +/* string */ +extern ft_test ft_string_memset; +extern ft_test ft_string_memcpy; +extern ft_test ft_string_memmove; +extern ft_test ft_string_memcmp; + +#endif /* _FT_ALL_TESTS_H_ */ diff --git a/include/ft/test.h b/include/ft/test.h new file mode 100644 index 0000000..21bc3f8 --- /dev/null +++ b/include/ft/test.h @@ -0,0 +1,93 @@ +//--- +// ft.test: Testing tools and status reports +//--- + +#ifndef _FT_TEST_H_ +#define _FT_TEST_H_ + +#include +#include + +//--- +// Test framework +//--- + +/* ft_test: A series of assertions grouped together in a single function */ +typedef struct ft_test { + /* Test name */ + char const *name; + /* Test function */ + void (*function)(struct ft_test *t); + /* Optional visualization widget; called when the visualization is + requested. The widget can be interactive, but it must not accept EXIT or + MENU events. It is destroyed atfer use. */ + jwidget *(*widget)(struct ft_test *test); + /* Test log; this can be displayed by a scrolling widget */ + char *log; + /* Number of passed, skipped and failed assertions in the test */ + uint16_t passed; + uint16_t skipped; + uint16_t failed; +} ft_test; + +/* ft_test_init(): Init counters and logs for a test */ +void ft_test_init(ft_test *test); + +/* ft_test_run(): Reset and run a test */ +void ft_test_run(ft_test *test); + +/* ft_test_done(): Check whether a test is done */ +bool ft_test_done(ft_test *test); + +/* ft_assert(): Record an assertion in a test */ +void ft_assert(ft_test *test, int expression, + char const *file, int line, char const *expression_str); +#define ft_assert(test, expression) \ + ft_assert(test, expression, __FILE__, __LINE__, #expression) + +/* ft_log(): Write some data in a test's log */ +__attribute__((format(printf, 2, 3))) +void ft_log(ft_test *test, char const *format, ...); + +//--- +// Test lists (to organize by header) +//--- + +/* ft_list: A named list of tests with aggregating statistics */ +typedef struct { + /* List name (header name) */ + char const *name; + /* NULL-terminated list of children */ + ft_test **children; + /* Number of passed, skipped and failed assertions in the children */ + uint16_t passed; + uint16_t skipped; + uint16_t failed; +} ft_list; + +/* ft_list_init(): Init counters and logs for a NULL-terminated test list */ +void ft_list_init(ft_list *list); + +/* ft_list_run(): Reset and run a list of tests (aggregates automatically) */ +void ft_list_run(ft_list *list); + +/* ft_list_aggregate(): Update list results based on the children's results */ +void ft_list_aggregate(ft_list *l); + +//--- +// Utilities +//--- + +/* Colors for each test category */ +#define FT_TEST_EMPTY C_RGB(20,20,20) +#define FT_TEST_PASSED C_RGB(6,25,8) +#define FT_TEST_SKIPPED C_RGB(31,20,9) +#define FT_TEST_PENDING C_RGB(15,23,27) +#define FT_TEST_FAILED C_RGB(31,10,7) + +/* ft_test_color(): Color for the summary of a test */ +int ft_test_color(ft_test const *t); +/* ft_list_color(): Color for the summary of a list of tests */ +int ft_list_color(ft_list const *t); + +#endif /* _FT_TEST_H_ */ diff --git a/include/ft/util.h b/include/ft/util.h new file mode 100644 index 0000000..9422eb3 --- /dev/null +++ b/include/ft/util.h @@ -0,0 +1,16 @@ +//--- +// fxlibtest.util: Random utilities +//--- + +#ifndef _FT_UTIL_H_ +#define _FT_UTIL_H_ + +#ifdef FX9860G +#define _(fx,cg) (fx) +#endif + +#ifdef FXCG50 +#define _(fx, cg) (cg) +#endif + +#endif /* _FT_UTIL_H_ */ diff --git a/include/ft/widgets/fbar.h b/include/ft/widgets/fbar.h new file mode 100644 index 0000000..61bb88f --- /dev/null +++ b/include/ft/widgets/fbar.h @@ -0,0 +1,36 @@ +//--- +// ft.widgets.fbar: A colored bar summarizing test results +//--- + +#ifndef _FT_WIDGETS_FBAR_H_ +#define _FT_WIDGETS_FBAR_H_ + +#include +#include + +/* fbar: A bar summarizing test results with colored sections + + This widget is a simple display widget. It displays either a summary for a + list or a summary for a whole header set (list of lists). + + The length of the pending section is based on the proportion of pending + tests. The length of the other sections is based on the number of assertions + in each category. */ +typedef struct { + jwidget widget; + + /* Current set of tests or set of lists (only one can be non-NULL) */ + ft_test **tests; + ft_list *lists; +} fbar; + +/* fbar_create(): Create an fbar with no data tied */ +fbar *fbar_create(void *parent); + +/* fbar_set_tests(): Show statistics for a list */ +void fbar_set_tests(fbar *b, ft_test **tests); + +/* fbar_set_lists(): Show statistics for a full header set */ +void fbar_set_lists(fbar *b, ft_list *lists); + +#endif /* _FT_WIDGETS_FBAR_H_ */ diff --git a/include/ft/widgets/fbrowser.h b/include/ft/widgets/fbrowser.h new file mode 100644 index 0000000..e185ae8 --- /dev/null +++ b/include/ft/widgets/fbrowser.h @@ -0,0 +1,59 @@ +//--- +// ft.widgets.browser: A dual-list setup to browse categorized tests +//--- + +#ifndef _FT_WIDGETS_BROWSER_H_ +#define _FT_WIDGETS_BROWSER_H_ + +#include +#include +#include +#include + +/* fbrowser: A test browser that shows categorized tests and summaries + + This widget is the main frame of this application. It consists of list of + headers on the left, and a list of tests on the right. Summaries of all + headers and of all tests within a single header are shown. */ +typedef struct { + jwidget widget; + /* Header list and test list */ + flist *headers; + flist *tests; + /* Main bar at the top, and secondary bar above test list */ + fbar *bar_top; + fbar *bar_right; + /* Widgets that help with the structure */ + jwidget *rstack; + /* Current set of headers and tests */ + ft_list *data_headers; + ft_test **data_tests; +} fbrowser; + +/* A test entry has been validated */ +extern uint16_t FBROWSER_VALIDATED; + +/* fbrowser_create(): Create a test browser */ +fbrowser *fbrowser_create(void *parent); + +/* fbrowser_set_headers(): Select a set of headers + If (select = true), this also selects the first header.*/ +void fbrowser_set_headers(fbrowser *b, ft_list *list, bool select); + +/* fbrowser_set_tests(): Select a set of tests + You should not call this function; fbrowser synchronizes the set of tests + with the currently selected header. */ +void fbrowser_set_tests(fbrowser *b, ft_test **tests); + +/* fbrowser_current_header(): Currently-selected header + This function returns a pointer to the currently-selected list, which is + NULL if the summary is selected on the left panel, and non-NULL otherwise. + This is defined even when the focus is on the right panel. */ +ft_list *fbrowser_current_header(fbrowser *b); + +/* fbrowser_current_test(): Currently-selected test + This function returns a pointer to the currently-selected test, which is + non-NULL only when the focus is on the right panel. */ +ft_test *fbrowser_current_test(fbrowser *b); + +#endif /* _FT_WIDGETS_BROWSER_H_ */ diff --git a/include/ft/widgets/flist.h b/include/ft/widgets/flist.h new file mode 100644 index 0000000..0c198b2 --- /dev/null +++ b/include/ft/widgets/flist.h @@ -0,0 +1,79 @@ +//--- +// ft.widgets.flist: Scrolling list with selectable elements +//--- + +#ifndef _FT_WIDGETS_FLIST_H_ +#define _FT_WIDGETS_FLIST_H_ + +#include +#include +#include + +/* flist: A scrolling list with a selection cursor + + This widget is a list of horizontal entries of a fixed height. The rendering + of entries and data storage is delegated to caller code. This widget only + handles the geometry and has no idea about the contents of the entries. */ +typedef struct flist { + jwidget widget; + /* Renderer function */ + gint_call_t renderer; + /* Number of rows, number of rows visible on-screen, current top row, and + currently-selected row (-1 if none) */ + uint16_t rows; + uint16_t visible; + uint16_t top; + int16_t selected; + /* Row height */ + uint16_t row_height; + /* Parameters available during rendering */ + int16_t x, y; + int16_t row; +} flist; + +/* The cursor has moved to a different entry */ +extern uint16_t FLIST_SELECTED; +/* The current entry has been validated with EXE */ +extern uint16_t FLIST_VALIDATED; + +/* flist_create(): Create a scrolling list + Rows are dynamic, this function only sets the rendering function, which + should have the following prototype: + + void render(flist *f, ...); + + The GINT_CALL() for the renderer can use argument 2 to 4, the first will be + overridden by the flist code during the call. + + The renderer can access the dimensions and row_height setting of the flist + widget, as well as draw_x, draw_y, draw_row and selected. Note however that + a couple of pixels may be reserved on the right to render a scrollbar. */ +flist *flist_create(gint_call_t renderer, void *parent); + +/* flist_set_renderer(): Change renderer after creation */ +void flist_set_renderer(flist *l, gint_call_t renderer); + +//--- +// Configuration of rows +//--- + +/* flist_set_rows(): Set the number of rows */ +void flist_set_rows(flist *l, int rows); + +/* flist_set_row_height(): Fix the row height for every row */ +void flist_set_row_height(flist *l, int row_height); + +//--- +// Movement +//--- + +/* flist_scroll_to(): Scroll to the specified offset */ +void flist_scroll_to(flist *l, int offset); + +/* flist_select(): Select an entry and scroll to it if needed */ +void flist_select(flist *l, int entry); + +/* flist_end(): Offset of the end of the list */ +int flist_end(flist *l); + +#endif /* _FT_WIDGETS_FLIST_H_ */ diff --git a/include/ft/widgets/gscreen.h b/include/ft/widgets/gscreen.h new file mode 100644 index 0000000..d5e3f68 --- /dev/null +++ b/include/ft/widgets/gscreen.h @@ -0,0 +1,110 @@ +//--- +// gintctl.widgets.gscreen: gintctl's extended standard scene +//--- + +#ifndef _GINTCTL_WIDGETS_GSCREEN +#define _GINTCTL_WIDGETS_GSCREEN + +#include +#include +#include +#include +#include + +struct gscreen_tab; + +/* gscreen: gintctl's standard screen. + + The gscreen is a scene with a stylized title bar, an optional function key + bar at the bottom, and a stacked layout of tabs in the middle. Each tab can + have specific properties set, and the gscreen handles some of the focus + management caused by hiding and showing a lot of widgets in tabs. + + The scene of a gscreen can be accessed with (the_gscreen->scene). */ +typedef struct { + jscene *scene; + /* Number of tabs */ + int tab_count; + /* Information on tabs */ + struct gscreen_tab *tabs; + /* Fixed widets */ + jlabel *title; + jfkeys *fkeys; + /* Current function bar level */ + int8_t fkey_level; + +} gscreen; + +struct gscreen_tab { + bool title_visible, fkeys_visible; + /* Most recent focused widget one */ + jwidget *focus; +}; + +/* gscreen_create(): Set up a standard scene. + If (title = NULL), the scene has no title bar, if (fkeys = NULL) it has no + function bar. To show a title/function bar on some tabs but not all, create + one here and use gscreen_set_tab_{title,fkeys}_visible(). */ + +#ifdef FX9860G +gscreen *gscreen_create(char const *title, bopti_image_t const *fkeys); +#define gscreen_create2(short, img, long, fkeys) gscreen_create(short, img) +#endif + +#ifdef FXCG50 +gscreen *gscreen_create(char const *title, char const *fkeys); +#define gscreen_create2(short, img, long, fkeys) gscreen_create(long, fkeys) +#endif + +void gscreen_destroy(gscreen *s); + +//--- +// Function bar settings +//--- + +/* gscreen_set_fkeys_level(): Select the function key bar */ +void gscreen_set_fkeys_level(gscreen *s, int level); + +//--- +// Tab settings +//--- + +/* gscreen_add_tab(): Add a tab to the stacked layout + The child's parent will be changed. The widget will also have a stretch of + (1, 1, false) set by default. If not NULL, the last parameter indicates + which widget to focus when the tab is first shown. */ +void gscreen_add_tab(gscreen *scene, void *widget, void *focus); + +/* gcreen_add_tabs(): Add several tabs at once, with no special parameters */ +void gscreen_add_tabs(gscreen *s, ...); +#define gscreen_add_tabs(...) gscreen_add_tabs(__VA_ARGS__, NULL) + +/* gscreen_set_tab_title_visible(): Set whether title bar is shown on a tab */ +void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible); + +/* gscreen_set_tab_fkeys_visible(): Set whether fkeys are shown on a tab */ +void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible); + +//--- +// Tab navigation +//--- + +/* gscreen_show_tab(): Show a tab from the stack + Returns true if the tab changed, false if it was already active or if it is + an invalid tab widget. */ +bool gscreen_show_tab(gscreen *s, int tab); + +/* gscreen_current_tab(): Give the currently visible tab */ +int gscreen_current_tab(gscreen *s); + +/* gscreen_in(): Check if we're in a specific tab */ +bool gscreen_in(gscreen *s, int tab); + +//--- +// Focus management +//--- + +/* Set focus for the current tab */ +void gscreen_focus(gscreen *s, void *widget); + +#endif /* _GINTCTL_WIDGETS_GSCREEN */ diff --git a/src/ctype/trivialties.c b/src/ctype/trivialties.c new file mode 100644 index 0000000..9e3313f --- /dev/null +++ b/src/ctype/trivialties.c @@ -0,0 +1,28 @@ +#include +#include + +static void _ctype_trivial_success(ft_test *t) +{ + ft_assert(t, 1); + ft_assert(t, 1); +} + +ft_test ft_ctype_trivial_success = { + .name = "Trivialty (always succeeds)", + .function = _ctype_trivial_success, +}; + +static void _ctype_trivial_failure(ft_test *t) +{ + ft_assert(t, 0); +} + +ft_test ft_ctype_trivial_failure = { + .name = "Trivialty (always fails)", + .function = _ctype_trivial_failure, +}; + +ft_test ft_ctype_trivial_empty = { + .name = "Trivialty (no function)", + .function = NULL, +}; diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..740e523 --- /dev/null +++ b/src/main.c @@ -0,0 +1,142 @@ +#include +#include + +#include +#include +#include +#include +#include + +/* We don't initialize the test result fields below */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + +ft_list headers_libc[] = { + { "", (ft_test*[]){ + &ft_ctype_trivial_success, + &ft_ctype_trivial_failure, + &ft_ctype_trivial_empty, + NULL, + }}, + { "", NULL }, + { "", NULL }, + { "", NULL }, + { "", NULL },/*(ft_test[]){ + { "Formatted printing basics", NULL, NULL }, + { "Format types and options", NULL, NULL }, + { NULL }, + }}, */ + { "", NULL }, + { "", (ft_test*[]){ + &ft_string_memset, + &ft_string_memcpy, + &ft_string_memmove, + NULL, + }}, + { "", NULL }, + { "", NULL }, + { "", NULL }, + { "", NULL }, + { NULL } +}; +ft_list headers_posix[] = { + { "", NULL }, + { NULL } +}; + +#pragma GCC diagnostic pop + +int main(void) +{ + // Initialize test results + for(int i = 0; headers_libc[i].name; i++) + ft_list_init(&headers_libc[i]); + for(int i = 0; headers_posix[i].name; i++) + ft_list_init(&headers_posix[i]); + + // Create GUI + gscreen *scr = gscreen_create2("FxLibc tests", NULL, + "FxLibc regression and performance tests", "/LIBC;/POSIX;;;;#RUN"); + fbrowser *browser = fbrowser_create(NULL); + int selected_set = 0; + + gscreen_add_tab(scr, browser, NULL); + fbrowser_set_headers(browser, headers_libc, false); + + jwidget *results = jwidget_create(NULL); + gscreen_add_tab(scr, results, NULL); + gscreen_set_tab_fkeys_visible(scr, 1, false); + + // Event handling + while(1) { + jevent e = jscene_run(scr->scene); + int tab = gscreen_current_tab(scr); + + int key = 0; + if(e.type == JSCENE_KEY || e.key.type == KEYEV_DOWN) key = e.key.key; + + if(e.type == JSCENE_PAINT) { + dclear(C_WHITE); + jscene_render(scr->scene); + dupdate(); + } + + // Switch lists on F1/F2 + if(tab == 0 && key == KEY_F1 && selected_set != 0) { + fbrowser_set_headers(browser, headers_libc, false); + selected_set = 0; + } + if(tab == 0 && key == KEY_F2 && selected_set != 1) { + fbrowser_set_headers(browser, headers_posix, false); + selected_set = 1; + } + + // Run tests + if(tab == 0 && key == KEY_F6) { + ft_list *header = fbrowser_current_header(browser); + ft_test *test = fbrowser_current_test(browser); + + if(test) { + ft_test_run(test); + ft_list_aggregate(header); + browser->widget.update = 1; + } + else if(header) { + ft_list_run(header); + browser->widget.update = 1; + } + else { + for(int i = 0; headers_libc[i].name; i++) + ft_list_run(&headers_libc[i]); + for(int i = 0; headers_posix[i].name; i++) + ft_list_run(&headers_posix[i]); + browser->widget.update = 1; + } + } + + // Browse test results + if(tab == 0 && e.type == FBROWSER_VALIDATED && e.source == browser) { + ft_test *test = fbrowser_current_test(browser); + if(ft_test_done(test) && test->widget) { + jwidget *w = test->widget(test); + if(w) { + jwidget_add_child(results, w); + gscreen_show_tab(scr, 1); + jscene_set_focused_widget(scr->scene, w); + } + } + } + + // Close test results + if(tab == 1 && key == KEY_EXIT) { + jscene_set_focused_widget(scr->scene, browser->tests); + gscreen_show_tab(scr, 0); + while(results->child_count > 0) { + jwidget_destroy(results->children[0]); + } + } + } + + gscreen_destroy(scr); + return 1; +} diff --git a/src/string/core.c b/src/string/core.c new file mode 100644 index 0000000..e8ff5de --- /dev/null +++ b/src/string/core.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include "memarray.h" + +//--- +// Utilities +//--- + +/* Fill buffer with non-zero and position-sensitive data */ +static void fill(uint8_t *buf, int size, int start) +{ + for(int i = 0; i < size; i++) buf[i] = start+i; +} +/* Clear buffer */ +static void clear(uint8_t *buf, int size) +{ + for(int i = 0; i < size; i++) buf[i] = 0; +} +/* Check buffer equality (returns zero on equal) */ +static int cmp(uint8_t *left, uint8_t *right, int size) +{ + for(int i = 0; i < size; i++) if(left[i] != right[i]) return 1; + return 0; +} + +//--- +// Naive functions (baseline) +//--- + +static void *naive_memcpy(void *_dst, void const *_src, size_t len) +{ + uint8_t *dst = _dst; + uint8_t const *src = _src; + + while(len--) *dst++ = *src++; + return _dst; +} +static void *naive_memset(void *_dst, int byte, size_t len) +{ + uint8_t *dst = _dst; + + while(len--) *dst++ = byte; + return _dst; +} +static void *naive_memmove(void *dst, void const *src, size_t len) +{ + uint8_t tmp[len]; + naive_memcpy(tmp, src, len); + naive_memcpy(dst, tmp, len); + return dst; +} +static int naive_memcmp(void const *_s1, void const *_s2, size_t len) +{ + uint8_t const *s1 = _s1, *s2 = _s2; + + for(size_t i = 0; i < len; i++) + { + if(s1[i] != s2[i]) return s1[i] - s2[i]; + } + return 0; +} + +//--- +// memset +//--- + +static int _string_memset_func(memarray_args_t const *args) +{ + fill(args->full_buf1, args->full_size, 0); + fill(args->full_buf2, args->full_size, 0); + + memset(args->buf1, 0, args->size); + naive_memset(args->buf2, 0, args->size); + + return cmp(args->full_buf1, args->full_buf2, args->full_size); +} + +uint8_t _string_memset_rc[MEMARRAY_RC_SINGLE]; + +static void _string_memset(ft_test *t) +{ + memarray_single(_string_memset_rc, _string_memset_func); + memarray_assert(_string_memset_rc, t); +} + +static jwidget *_string_memset_widget(GUNUSED ft_test *t) +{ + return memarray_widget(_string_memset_rc); +} + +ft_test ft_string_memset = { + .name = "Configurations of memset", + .function = _string_memset, + .widget = _string_memset_widget, +}; + +//--- +// memcpy +//--- + +static int _string_memcpy_func(memarray_args_t const *args) +{ + fill(args->full_left1, args->full_size, 0); + fill(args->full_left2, args->full_size, 0); + clear(args->full_right1, args->full_size); + clear(args->full_right2, args->full_size); + + memcpy(args->right1, args->left1, args->size); + naive_memcpy(args->right2, args->left2, args->size); + + return cmp(args->full_right1, args->full_right2, args->full_size); +} + +uint8_t _string_memcpy_rc[MEMARRAY_RC_DOUBLE]; + +static void _string_memcpy(ft_test *t) +{ + memarray_double(_string_memcpy_rc, false, _string_memcpy_func); + memarray_assert(_string_memcpy_rc, t); +} + +static jwidget *_string_memcpy_widget(GUNUSED ft_test *t) +{ + return memarray_widget(_string_memcpy_rc); +} + +ft_test ft_string_memcpy = { + .name = "Configurations of memcpy", + .function = _string_memcpy, + .widget = _string_memcpy_widget, +}; + +//--- +// memmove +//--- + +static int _string_memmove_func(memarray_args_t const *args) +{ + fill(args->full_left1, args->full_size, 0); + fill(args->full_left2, args->full_size, 0); + fill(args->full_right1, args->full_size, 0); + fill(args->full_right2, args->full_size, 0); + + memmove(args->right1, args->left1, args->size); + naive_memmove(args->right2, args->left2, args->size); + + return cmp(args->full_right1, args->full_right2, args->full_size); +} + +uint8_t _string_memmove_rc[MEMARRAY_RC_DOUBLE_OVERLAP]; + +static void _string_memmove(ft_test *t) +{ + memarray_double(_string_memmove_rc, true, _string_memmove_func); + memarray_assert(_string_memmove_rc, t); +} + +static jwidget *_string_memmove_widget(GUNUSED ft_test *t) +{ + return memarray_widget(_string_memmove_rc); +} + +ft_test ft_string_memmove = { + .name = "Configurations of memmove", + .function = _string_memmove, + .widget = _string_memmove_widget, +}; diff --git a/src/string/memarray.c b/src/string/memarray.c new file mode 100644 index 0000000..2deb3c1 --- /dev/null +++ b/src/string/memarray.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include "memarray.h" + +//--- +// Utilities +//--- + +/* Code of exception that occurs during a memory access */ +static uint32_t exception = 0; +/* Exception-catching function */ +static int catch_exc(uint32_t code) +{ + if(code == 0x100 || code == 0x0e0) + { + exception = code; + gint_exc_skip(1); + return 0; + } + return 1; +} + +/* memory_protected_call(): Wrapper that protects against access errors */ +static int memory_protected_call(gint_call_t function) +{ + exception = 0; + gint_exc_catch(catch_exc); + int rc = gint_call(function); + gint_exc_catch(NULL); + return rc || (exception != 0); +} + +//--- +// Public API +//--- + +void memarray_set(uint8_t *rc, int position, int value) +{ + int index = 1 + (position >> 3); + uint8_t byte = 0x80 >> (position & 7); + + if(index >= rc[0]) return; + if(value) rc[index] |= byte; + else rc[index] &= ~byte; +} + +int memarray_get(uint8_t *rc, int position) +{ + int index = 1 + (position >> 3); + uint8_t byte = 0x80 >> (position & 7); + + if(index >= rc[0]) return -1; + return (rc[index] & byte) != 0; +} + +void memarray_assert(uint8_t *rc, ft_test *test) +{ + for(int i = 0; true; i++) { + int v = memarray_get(rc, i); + if(v < 0) return; + ft_assert(test, v == 0); + } +} + +void memarray_single(uint8_t *rc, memarray_func_t f) +{ + uint8_t buf1[128], buf2[128]; + + int counter = 0; + rc[0] = MEMARRAY_RC_SINGLE; + + memarray_args_t args = { + .full_buf1 = buf1, + .full_buf2 = buf2, + .full_size = 128, + }; + gint_call_t call = GINT_CALL(f, (void *)&args); + + for(int buf_al = 0; buf_al < 4; buf_al++) + for(int len_al = 0; len_al < 4; len_al++) + { + args.buf1 = buf1 + 8 + buf_al; + args.buf2 = buf2 + 8 + buf_al; + args.size = 12 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + + args.buf1 = buf1 + 8 + buf_al; + args.buf2 = buf2 + 8 + buf_al; + args.size = 92 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + } +} + +void memarray_double(uint8_t *rc, bool overlap, memarray_func_t f) +{ + uint8_t left1[128], left2[128], right1[128], right2[128]; + + int counter = 0; + rc[0] = overlap ? MEMARRAY_RC_DOUBLE_OVERLAP : MEMARRAY_RC_DOUBLE; + + memarray_args_t args = { + .full_left1 = left1, + .full_left2 = left2, + .full_right1 = right1, + .full_right2 = right2, + .full_size = 128, + }; + gint_call_t call = GINT_CALL(f, (void *)&args); + + for(int left_al = 0; left_al < 4; left_al++) + for(int right_al = 0; right_al < 4; right_al++) + for(int len_al = 0; len_al < 4; len_al++) + { + args.left1 = left1 + 8 + left_al; + args.left2 = left2 + 8 + left_al; + args.right1 = right1 + 8 + right_al; + args.right2 = right2 + 8 + right_al; + args.size = 12 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + + args.left1 = left1 + 8 + left_al; + args.left2 = left2 + 8 + left_al; + args.right1 = right1 + 8 + right_al; + args.right2 = right2 + 8 + right_al; + args.size = 92 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + + if(!overlap) continue; + + /* In overlap mode, only use left1 and left2 to create an overlap */ + args.left1 = left1 + left_al; + args.left2 = left2 + left_al; + args.right1 = left1 + 28 + right_al; + args.right2 = left2 + 28 + right_al; + args.size = 92 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + + args.left1 = left1 + 28 + left_al; + args.left2 = left2 + 28 + left_al; + args.right1 = left1 + right_al; + args.right2 = left2 + right_al; + args.size = 92 + len_al; + memarray_set(rc, counter++, memory_protected_call(call)); + } +} + +//--- +// Visualization widget +//--- + +static void paint_results(int x, int y, uint8_t *rc) +{ + dprint_opt(x+222, y, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_TOP, + "Source align and destination align"); + for(int i = 0; i < 16; i++) + dprint(x + 74 + 18*i + 3*(i/4), y+12, C_BLACK, "%d%d", i/4, i & 3); + + int sizes = 0; + if(rc[0] == MEMARRAY_RC_SINGLE) sizes = 2; + if(rc[0] == MEMARRAY_RC_DOUBLE) sizes = 2; + if(rc[0] == MEMARRAY_RC_DOUBLE_OVERLAP) sizes = 4; + + char const *size_names[4] = { "Small", "Large", "Over-L", "Over-R" }; + for(int i = 0; i < 4; i++) { + dprint_opt(x+55, y + 43 + 42*i, C_BLACK, C_NONE, DTEXT_RIGHT, + DTEXT_MIDDLE, "%s", size_names[i]); + } + + for(int i = 0; i < 16; i++) + dprint_opt(x+64, y + 25 + 10*i + 2*(i/4), C_BLACK, C_NONE, + DTEXT_CENTER, DTEXT_TOP, "%d", i & 3); + + for(int row = 0; row < 16; row++) + for(int col = 0; col < 16; col++) + { + int x1 = x + 72 + 18*col + 3*(col/4); + int y1 = y + 24 + 10*row + 2*(row/4); + + int value = -1; + if(row < 4*sizes) value = memarray_get(rc, 4*sizes * col + row); + int fg = (value == -1) ? C_WHITE : (value == 0) ? C_GREEN : C_RED; + drect_border(x1, y1, x1+19, y1+10, fg, 1, C_BLACK); + } +} + +jwidget *memarray_widget(uint8_t *rc) +{ + jpainted *p = jpainted_create(paint_results, rc, 377, 185, NULL); + if(!p) return NULL; + jwidget_set_margin(p, 4, 4, 4, 4); + return &p->widget; +} diff --git a/src/string/memarray.h b/src/string/memarray.h new file mode 100644 index 0000000..02636eb --- /dev/null +++ b/src/string/memarray.h @@ -0,0 +1,96 @@ +//--- +// string.memarray: Alignment/size combination array for core memory functions +// +// This header provides tools to check memory functions in all size/alignment +// configurations. Up to two buffers can be involved (source and destination), +// and for each buffer all 4n + {0,1,2,3} alignments are tested. Different +// sizes are also tested, each with all possible 4n + {0,1,2,3} values. +// +// By default, two size configurations are tested: +// * 12..15 bytes (intended to use naive, minimum-logic methods) +// * 92..95 bytes (intended to use elaborate methods) +// If two buffers are involved, overlapping sections can also be requested: +// * (32-[64)-32] (approximately; alignment slightly alters values) +// * [32-(64]-32) (approximately; alignment slightly alters values) +// +// This header defines two functions memarray_single() and memarray_double() +// that repeatedly invoke a user-supplied test function with different +// combinations of alignments and sizes. The test function is guarded against +// misaligned memory accesses (which are detected and counted as failures) to +// avoid application crashes. +// +// When invoked, the test function is supplied with a memarray_args_t structure +// that gives buffer addresses and sizes for a single combination. Each buffer +// is provided twice ("name1" and "name2") so that tests requiring a baseline +// (eg. comparing optimized memcpy with naive memcpy) can run the baseline on +// the duplicate buffers. +// +// Test results are recorded in bitmaps based on the return value of the test +// function (0 for success, 1 for failure); this header provides memarray_set() +// and memarray_get() to use the bitmaps (although rarely needed), as well as +// memarray_assert() to record results into an ft_test and memarray_widget() to +// generate result visualizations. +//--- + +#ifndef _STRING_MEMARRAY_H +#define _STRING_MEMARRAY_H + +#include +#include +#include + +/* Size of result buffers to allocate for each configuration; one byte is + reserved to indicate the configuration for the bitmap functions */ + +/* Single buffer: 4 alignments * 8 sizes = 32 tests -> 4 bytes */ +#define MEMARRAY_RC_SINGLE 5 +/* Double buffer : 4 * 4 alignments * 8 sizes = 128 tests -> 16 bytes */ +#define MEMARRAY_RC_DOUBLE 17 +/* Double with overlap : 4 * 4 alignments * 16 sizes = 256 tests -> 32 bytes */ +#define MEMARRAY_RC_DOUBLE_OVERLAP 33 + +/* Arguments to test functions: + * In single mode, buf1 and buf2 are used + * In double mode, left1, left2, right1 and right2 are used */ +typedef struct { + /* Pointers with varying alignment, to test with */ + union { void *buf1, *left1; }; + union { void *buf2, *left2; }; + void *right1; + void *right2; + /* Size of the operation to perform */ + size_t size; + /* Pointers to the orignal buffers (which occupy a larger interval of + memory than the pointers above). This is useful to initialize and check + an area slightly larger than the tested area, to make sure that tested + functions don't write outside their assigned bounds */ + union { void *full_buf1, *full_left1; }; + union { void *full_buf2, *full_left2; }; + void *full_right1; + void *full_right2; + /* Size of the original buffers (128) */ + size_t full_size; + +} memarray_args_t; + +/* Type of test functions; should return 0 for success, 1 for failure */ +typedef int (*memarray_func_t)(memarray_args_t const *args); + +/* Run a test; the result buffer must have a suitable size allocated */ +void memarray_single(uint8_t *rc, memarray_func_t f); +void memarray_double(uint8_t *rc, bool do_overlaps, memarray_func_t f); + +/* Set a bit in the result buffer; if position is out of bounds, no-op */ +void memarray_set(uint8_t *rc, int position, int value); +/* Get a bit in the result buffer; if position is out of bounds, returns -1 */ +int memarray_get(uint8_t *rc, int position); + +/* Assert that every value in the result array is 0 */ +void memarray_assert(uint8_t *rc, ft_test *test); + +/* Create a widget to visualize the contents of a result buffer. The result + pointer is not tied to the widget so it will continue to exist even after + the widget is destroyed. */ +jwidget *memarray_widget(uint8_t *buffer); + +#endif /* _STRING_MEMARRAY_H */ diff --git a/src/test.c b/src/test.c new file mode 100644 index 0000000..1f388dc --- /dev/null +++ b/src/test.c @@ -0,0 +1,114 @@ +#include +#include +#undef ft_assert + +//--- +// Test framework +//--- + +void ft_test_init(ft_test *test) +{ + test->passed = 0; + test->skipped = 0; + test->failed = 0; +} + +void ft_test_run(ft_test *test) +{ + ft_test_init(test); + if(!test->function) return; + test->function(test); +} + +bool ft_test_done(ft_test *test) +{ + return test->passed + test->skipped + test->failed > 0; +} + +void ft_assert(ft_test *test, int expression, char const *file, int line, + char const *str) +{ + if(expression) { + test->passed++; + } + else { + test->failed++; + ft_log(test, "%s:%d: assertion '%s' failed", file, line, str); + } +} + +void ft_log(ft_test *test, char const *format, ...) +{ +} + +//--- +// Test lists (to organize by header) +//--- + +void ft_list_init(ft_list *list) +{ + list->passed = 0; + list->skipped = 0; + list->failed = 0; + + if(!list->children) return; + for(int i = 0; list->children[i]; i++) { + ft_test_init(list->children[i]); + } +} + +void ft_list_run(ft_list *list) +{ + ft_list_init(list); + + if(!list->children) return; + for(int i = 0; list->children[i]; i++) { + ft_test_run(list->children[i]); + } + + ft_list_aggregate(list); +} + +void ft_list_aggregate(ft_list *l) +{ + l->passed = 0; + l->skipped = 0; + l->failed = 0; + + if(!l->children) return; + for(int i = 0; l->children[i]; i++) { + ft_test const *t = l->children[i]; + l->passed += t->passed; + l->skipped += t->skipped; + l->failed += t->failed; + } +} + +//--- +// Utilities +//--- + +int ft_test_color(ft_test const *t) +{ + if(t->failed > 0) return FT_TEST_FAILED; + if(t->skipped > 0) return FT_TEST_SKIPPED; + if(t->passed > 0) return FT_TEST_PASSED; + if(t->function) return FT_TEST_PENDING; + return FT_TEST_EMPTY; +} + +int ft_list_color(ft_list const *l) +{ + if(l->failed > 0) return FT_TEST_FAILED; + if(l->skipped > 0) return FT_TEST_SKIPPED; + + if(l->children) { + for(int i = 0; l->children[i]; i++) { + if(l->children[i]->function && l->children[i]->passed == 0) + return FT_TEST_PENDING; + } + } + + if(l->passed > 0) return FT_TEST_PASSED; + return FT_TEST_EMPTY; +} diff --git a/src/widgets/fbar.c b/src/widgets/fbar.c new file mode 100644 index 0000000..08adc35 --- /dev/null +++ b/src/widgets/fbar.c @@ -0,0 +1,187 @@ +#include +#include +#include +#include + +/* Type identifier for fbar */ +static int fbar_type_id = -1; + +fbar *fbar_create(void *parent) +{ + if(fbar_type_id < 0) return NULL; + + fbar *b = malloc(sizeof *b); + if(!b) return NULL; + + jwidget_init(&b->widget, fbar_type_id, parent); + b->tests = NULL; + b->lists = NULL; + + jwidget_set_margin(b, 0, 8, 2, 8); + + return b; +} + +void fbar_set_tests(fbar *b, ft_test **tests) +{ + b->tests = tests; + b->lists = NULL; + b->widget.update = 1; +} + +void fbar_set_lists(fbar *b, ft_list *lists) +{ + b->tests = NULL; + b->lists = lists; + b->widget.update = 1; +} + +//--- +// Counting statistics +//--- + +struct stats { + /* Number of tests done, pending and empty (proportion of full bar) */ + int16_t done; + int16_t pending; + int16_t empty; + /* Number of assertions (proportion of the done section of the bar) */ + int16_t passed; + int16_t skipped; + int16_t failed; +}; + +static void stats_init(struct stats *st) +{ + memset(st, 0, sizeof *st); +} + +static void stats_add_test(struct stats *st, ft_test *t) +{ + st->passed += t->passed; + st->skipped += t->skipped; + st->failed += t->failed; + + if(!t->function) st->empty++; + else if(t->passed + t->skipped + t->failed == 0) st->pending++; + else st->done++; +} + +static void stats_add_list(struct stats *st, ft_list *l) +{ + if(!l->children) return; + for(int i = 0; l->children[i]; i++) + stats_add_test(st, l->children[i]); +} + +//--- +// Polymorphic widget operations +//--- + +static void fbar_poly_csize(void *b0) +{ + fbar *b = b0; + b->widget.w = 120; + b->widget.h = 13; +} + +static void fbar_poly_render(void *b0, int x, int y) +{ + fbar *b = b0; + int w = jwidget_content_width(b); + int h = jwidget_content_height(b); + + struct stats st, px; + stats_init(&st); + stats_init(&px); + + if(b->tests) for(int i = 0; b->tests[i]; i++) { + stats_add_test(&st, b->tests[i]); + } + else if(b->lists) for(int i = 0; b->lists[i].name; i++) { + stats_add_list(&st, &b->lists[i]); + } + + /* Width of bar for each of the 3 main sections */ + int main = st.done + st.pending + st.empty; + if(main == 0) { + drect_border(x, y, x+w-1, y+h-1, C_WHITE, 1, C_BLACK); + return; + } + + /* Try go guarantee at least 10 pixels per section */ + int guaranteeable_min = 10 * (!!st.done + !!st.pending + !!st.empty); + bool guaranteed = (w > guaranteeable_min); + + if(guaranteed) w -= guaranteeable_min; + px.done = (st.done * w) / main; + px.pending = (st.pending * w) / main; + px.empty = w - px.done - px.pending; + if(guaranteed) { + if(st.done) px.done += 10; + if(st.pending) px.pending += 10; + if(st.empty) px.empty += 10; + w += guaranteeable_min; + } + + /* Width of bar for each of the subsections of done */ + int assertions = st.passed + st.skipped + st.failed; + if(assertions == 0) assertions = 1; + + guaranteeable_min = 10 * (!!st.passed + !!st.skipped + !!st.failed); + guaranteed = (px.done > guaranteeable_min); + + if(guaranteed) px.done -= guaranteeable_min; + px.passed = (st.passed * px.done) / assertions; + px.skipped = (st.skipped * px.done) / assertions; + px.failed = px.done - px.passed - px.skipped; + if(guaranteed) { + if(st.passed) px.passed += 10; + if(st.skipped) px.skipped += 10; + if(st.failed) px.failed += 10; + px.done += guaranteeable_min; + } + + /* Draw */ + #define block(px, val, fg) if(px) { \ + drect(rx, y, rx+px-1, y+h-1, fg); \ + dprint_opt(rx+px/2, y+2, C_BLACK, C_NONE, DTEXT_CENTER, DTEXT_TOP, \ + "%d", val); \ + rx += px; \ + } + int rx = x; + block(px.passed, st.passed, FT_TEST_PASSED); + block(px.skipped, st.skipped, FT_TEST_SKIPPED); + block(px.failed, st.failed, FT_TEST_FAILED); + block(px.pending, st.pending, FT_TEST_PENDING); + block(px.empty, st.empty, FT_TEST_EMPTY); + #undef block + + /* Darken the border for a cool effect */ + for(int dx = 0; dx < w; dx++) { + int i1 = 396 * (y) + (x + dx); + int i2 = 396 * (y + h - 1) + (x + dx); + gint_vram[i1] = (gint_vram[i1] & 0xf7de) >> 1; + gint_vram[i2] = (gint_vram[i2] & 0xf7de) >> 1; + } + for(int dy = 1; dy < h-1; dy++) { + int i1 = 396 * (y + dy) + (x); + int i2 = 396 * (y + dy) + (x + w - 1); + gint_vram[i1] = (gint_vram[i1] & 0xf7de) >> 1; + gint_vram[i2] = (gint_vram[i2] & 0xf7de) >> 1; + } +} + +/* fbar type definition */ +static jwidget_poly type_fbar = { + .name = "fbar", + .csize = fbar_poly_csize, + .render = fbar_poly_render, +}; + +/* Type registration */ +__attribute__((constructor(2000))) +static void j_register_fbar(void) +{ + fbar_type_id = j_register_widget(&type_fbar, "jwidget"); +} diff --git a/src/widgets/fbrowser.c b/src/widgets/fbrowser.c new file mode 100644 index 0000000..08fd2ef --- /dev/null +++ b/src/widgets/fbrowser.c @@ -0,0 +1,273 @@ +#include +#include +#include +#include +#include + +/* Type identifier for fbrowser */ +static int fbrowser_type_id = -1; + +/* Events */ +uint16_t FBROWSER_VALIDATED; + +/* Header list and test list renderers */ +static void render_entries(flist *l, void *data, int is_headers); +/* Update whatever is displayed on the right panel */ +static void update_right_panel(fbrowser *b); + +fbrowser *fbrowser_create(void *parent) +{ + if(fbrowser_type_id < 0) return NULL; + jlabel *l; + + fbrowser *b = malloc(sizeof *b); + if(!b) return NULL; + + b->data_headers = NULL; + b->data_tests = NULL; + + jwidget_init(&b->widget, fbrowser_type_id, parent); + jlayout_set_hbox(b)->spacing = 4; + + // On the left, list of headers + + b->headers = flist_create(GINT_CALL_NULL, b); + flist_set_rows(b->headers, 1); + flist_select(b->headers, 0); + flist_set_row_height(b->headers, 13); + jwidget_set_fixed_width(b->headers, 120); + jwidget_set_stretch(b->headers, 0, 1, false); + + // On the right, either a summary of the whole application... + + b->rstack = jwidget_create(b); + jwidget_set_stretch(b->rstack, 1, 1, false); + jlayout_set_stack(b->rstack); + + jwidget *summary = jwidget_create(b->rstack); + jwidget_set_stretch(summary, 1, 1, false); + jlayout_set_vbox(summary)->spacing = 4; + + b->bar_top = fbar_create(summary); + jwidget_set_stretch(b->bar_top, 1, 0, false); + + l = jlabel_create( + "Summary here!\n\n" + "Green: Passed\n" + "Orange: Skipped\n" + "Red: Failed\n" + "Blue: Not tested\n" + "Gray: Empty\n\n" + "Run tests with F6:\n" + "- Here: runs all tests\n" + "- On a header: all tests in header\n" + "- On a test: just that test", + summary); + jlabel_set_block_alignment(l, J_ALIGN_LEFT, J_ALIGN_TOP); + jlabel_set_line_spacing(l, 3); + jwidget_set_stretch(l, 1, 1, false); + + // ... or a label for headers with no tests yet... + + l = jlabel_create("No test for this header.", b->rstack); + jlabel_set_block_alignment(l, J_ALIGN_LEFT, J_ALIGN_TOP); + jwidget_set_margin(l, 2, 0, 0, 0); + jwidget_set_stretch(l, 1, 1, false); + + // Or a header summary and a test list for the selected header + + jwidget *rvbox = jwidget_create(b->rstack); + jwidget_set_stretch(rvbox, 1, 1, false); + jlayout_set_vbox(rvbox)->spacing = 4; + + b->bar_right = fbar_create(rvbox); + jwidget_set_stretch(b->bar_right, 1, 0, false); + + b->tests = flist_create(GINT_CALL_NULL, rvbox); + flist_set_row_height(b->tests, 13); + jwidget_set_stretch(b->tests, 1, 1, false); + + return b; +} + +void fbrowser_set_headers(fbrowser *b, ft_list *headers, bool select) +{ + int rows = 0; + while(headers[rows].name) rows++; + + flist_set_renderer(b->headers, + GINT_CALL(render_entries, 0 /* Overridden */, (void *)headers, 1)); + flist_set_rows(b->headers, rows + 1); + flist_select(b->headers, select ? 1 : 0); + + jscene *scene = jscene_owning(b); + if(scene) jscene_set_focused_widget(scene, b->headers); + + b->data_headers = headers; + fbar_set_lists(b->bar_top, headers); + update_right_panel(b); + b->widget.update = 1; +} + +void fbrowser_set_tests(fbrowser *b, ft_test **tests) +{ + int rows = 0; + while(tests[rows]) rows++; + + flist_set_renderer(b->tests, + GINT_CALL(render_entries, 0 /* Overridden */, (void *)tests, 0)); + flist_set_rows(b->tests, rows); + flist_select(b->tests, -1); + + b->data_tests = tests; + fbar_set_tests(b->bar_right, tests); + b->widget.update = 1; +} + +ft_list *fbrowser_current_header(fbrowser *b) +{ + if(!b->data_headers || b->headers->selected < 1) return NULL; + return &b->data_headers[b->headers->selected - 1]; +} + +ft_test *fbrowser_current_test(fbrowser *b) +{ + if(!b->data_tests || b->tests->selected < 0) return NULL; + return b->data_tests[b->tests->selected]; +} + +//--- +// Rendering +//--- + +static void render_entries(flist *l, void *data, int is_headers) +{ + jscene *scene = jscene_owning(l); + + bool selected = (l->row == l->selected); + bool focused = (scene && jscene_focused_widget(scene) == l); + + int w = jwidget_content_width(l); + int h = l->row_height; + + if(selected && !focused) { + drect(l->x, l->y, l->x + w - 1, l->y + h - 1, C_RGB(24, 24, 24)); + } + + char const *name = "(null)"; + int color = C_BLACK; + + if(is_headers && l->row != 0) { + ft_list const *headers = data; + ft_list const *header = &headers[l->row - 1]; + color = ft_list_color(header); + name = header->name; + } + else { + ft_test const **tests = data; + ft_test const *test = tests[l->row]; + color = ft_test_color(test); + name = test->name; + } + + if(is_headers && l->row == 0) + dtext(l->x + 2, l->y + 2, C_BLACK, "Summary"); + else + dprint(l->x + 16, l->y + 2, C_BLACK, "%s", name); + + if(selected && focused) { + drect(l->x, l->y, l->x + w - 1, l->y + h - 1, C_INVERT); + } + + if(is_headers && l->row == 0) return; + for(int y = 0; y < 9; y++) + for(int x = 0; x < 9; x++) + { + if((x == 0 || x == 8) && (y == 0 || y == 8)) continue; + dpixel(l->x + 3 + x, l->y + 2 + y, color); + } +} + +//--- +// Polymorphic widget operations +//--- + +void update_right_panel(fbrowser *b) +{ + jlayout_stack *ls = jlayout_get_stack(b->rstack); + + ft_list *header = fbrowser_current_header(b); + ft_test **tests = header ? header->children : NULL; + + if(!header) { + ls->active = 0; + } + else if(!tests) { + ls->active = 1; + } + else { + fbrowser_set_tests(b, tests); + ls->active = 2; + } + + b->widget.update = 1; +} + +static bool fbrowser_poly_event(void *b0, jevent e) +{ + fbrowser *b = b0; + jscene *scene = jscene_owning(b); + + int key = (e.type==JSCENE_KEY || e.key.type==KEYEV_DOWN) ? e.key.key : 0; + void *f = scene ? jscene_focused_widget(scene) : NULL; + + /* Update right panel when moving in the header list */ + if(e.type == FLIST_SELECTED && e.source == b->headers) { + update_right_panel(b); + return true; + } + /* Move focus to the test list when validating a header */ + if(e.type == FLIST_VALIDATED && e.source == b->headers) { + ft_list *header = fbrowser_current_header(b); + + if(header && header->children) { + if(scene) jscene_set_focused_widget(scene, b->tests); + flist_select(b->tests, 0); + b->widget.update = 1; + } + return true; + } + /* Move focus back when pressing EXIT */ + if(key == KEY_EXIT && f == b->tests) { + jscene_set_focused_widget(scene, b->headers); + flist_select(b->tests, -1); + b->widget.update = 1; + return true; + } + + /* Emit validation when pressing EXE on a test */ + if(e.type == FLIST_VALIDATED && e.source == b->tests) { + ft_test *test = fbrowser_current_test(b); + if(test) { + jevent e = { .source = b, .type = FBROWSER_VALIDATED }; + jwidget_emit(b, e); + } + return true; + } + + return false; +} + +/* fbrowser type definition */ +static jwidget_poly type_fbrowser = { + .name = "fbrowser", + .event = fbrowser_poly_event, +}; + +/* Type registration */ +__attribute__((constructor(2000))) +static void j_register_fbrowser(void) +{ + fbrowser_type_id = j_register_widget(&type_fbrowser, "jwidget"); + FBROWSER_VALIDATED = j_register_event(); +} diff --git a/src/widgets/flist.c b/src/widgets/flist.c new file mode 100644 index 0000000..5d6202b --- /dev/null +++ b/src/widgets/flist.c @@ -0,0 +1,217 @@ +#include +#include +#include +#include + +/* Type identifier for flist */ +static int flist_type_id = -1; + +/* Events */ +uint16_t FLIST_SELECTED; +uint16_t FLIST_VALIDATED; + +/* Update the count of visible items after a layout change */ +static void update_visible(flist *l); + +flist *flist_create(gint_call_t renderer, void *parent) +{ + if(flist_type_id < 0) return NULL; + + flist *l = malloc(sizeof *l); + if(!l) return NULL; + + jwidget_init(&l->widget, flist_type_id, parent); + + l->renderer = renderer; + l->rows = 0; + l->visible = 0; + l->top = 0; + l->selected = -1; + l->row_height = 3; + + return l; +} + +void flist_set_renderer(flist *l, gint_call_t renderer) +{ + l->renderer = renderer; +} + +//--- +// Configuration of rows +//--- + +void flist_set_rows(flist *l, int rows) +{ + if(l->rows == rows) return; + l->rows = max(rows, 0); + + if(l->selected < 0 && l->rows > 0) + l->selected = 0; + if(l->selected >= l->rows) + l->selected = l->rows - 1; + + l->widget.dirty = 1; +} + +void flist_set_row_height(flist *l, int row_height) +{ + if(l->row_height == row_height) return; + + l->row_height = row_height; + l->widget.dirty = 1; +} + +//--- +// Movement +//--- + +/* Guarantee that the positioning is valid */ +static int bound(flist *l, int pos) +{ + pos = max(pos, 0); + pos = min(pos, flist_end(l)); + return pos; +} + +void flist_scroll_to(flist *l, int offset) +{ + l->top = bound(l, offset); +} + +void flist_select(flist *l, int entry) +{ + if(entry < -1 || entry >= l->rows) return; + l->selected = entry; + l->widget.update = 1; + + /* TODO: flist_select(): Scroll to show selected entry */ +} + +int flist_end(flist *l) +{ + update_visible(l); + return max((int)l->rows - (int)l->visible, 0); +} + +//--- +// Polymorphic widget operations +//--- + +/* Recompute (visible) based on the current size */ +static void update_visible(flist *l) +{ + if(l->visible != 0) return; + l->visible = max(0, jwidget_content_height(l) / l->row_height); +} + +static void flist_poly_csize(void *l0) +{ + flist *l = l0; + l->widget.w = 64; + l->widget.h = 32; +} + +static void flist_poly_layout(void *l0) +{ + flist *l = l0; + update_visible(l); +} + +static void flist_poly_render(void *l0, int x, int base_y) +{ + flist *l = l0; + int cw = jwidget_content_width(l); + int y = base_y; + + for(uint i = 0; l->top + i < l->rows && i < l->visible; i++) { + l->x = x; + l->y = y; + l->row = l->top + i; + + l->renderer.args[0] = (gint_call_arg_t)(void *)l; + gint_call(l->renderer); + + y += l->row_height; + } + + /* Scrollbar */ + if(l->visible < l->rows) { + /* Area where the scroll bar lives */ + int area_w = _(1,2); + int area_x = x + cw - area_w; + int area_y = base_y; + int area_h = jwidget_content_height(l); + + /* Position and size of scroll bar within the area */ + int bar_y = (l->top * area_h + l->rows/2) / l->rows; + int bar_h = (l->visible * area_h + l->rows/2) / l->rows; + + drect(area_x, area_y, area_x+area_w-1, area_y+area_h-1, C_RGB(24,24,24)); + drect(area_x, area_y + bar_y, area_x + area_w - 1, + area_y + bar_y + bar_h, C_BLACK); + } +} + +static bool flist_poly_event(void *l0, jevent e) +{ + flist *l = l0; + update_visible(l); + + if(e.type != JWIDGET_KEY) return false; + if(e.key.type == KEYEV_UP) return false; + + if(e.key.key == KEY_DOWN && l->selected < l->rows - 1) { + if(e.key.shift) l->selected = l->rows - 1; + else l->selected++; + + /* Make sure the selected item remains visible */ + if(l->selected - l->top > l->visible - 1) + l->top = bound(l, l->selected - l->visible + 1); + + l->widget.update = 1; + + jevent e = { .source = l, .type = FLIST_SELECTED }; + jwidget_emit(l, e); + return true; + } + if(e.key.key == KEY_UP && l->selected > 0) { + if(e.key.shift) l->selected = 0; + else l->selected--; + + /* Same thing as before */ + if(l->selected < l->top) + l->top = bound(l, l->selected); + + jevent e = { .source = l, .type = FLIST_SELECTED }; + jwidget_emit(l, e); + l->widget.update = 1; + return true; + } + + if(e.key.key == KEY_EXE) { + jevent e = { .source = l, .type = FLIST_VALIDATED }; + jwidget_emit(l, e); + return true; + } + + return false; +} + +/* flist type definition */ +static jwidget_poly type_flist = { + .name = "flist", + .csize = flist_poly_csize, + .layout = flist_poly_layout, + .render = flist_poly_render, + .event = flist_poly_event, +}; + +/* Type registration */ +__attribute__((constructor(2000))) +static void j_register_flist(void) +{ + flist_type_id = j_register_widget(&type_flist, "jwidget"); + FLIST_SELECTED = j_register_event(); + FLIST_VALIDATED = j_register_event(); +} diff --git a/src/widgets/gscreen.c b/src/widgets/gscreen.c new file mode 100644 index 0000000..2b6b62b --- /dev/null +++ b/src/widgets/gscreen.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include + +#include +#include +#include + +#ifdef FX9860G +gscreen *gscreen_create(char const *name, bopti_image_t const *img) +#endif +#ifdef FXCG50 +gscreen *gscreen_create(char const *name, char const *labels) +#endif +{ + gscreen *g = malloc(sizeof *g); + if(!g) return NULL; + + jscene *s = jscene_create_fullscreen(NULL); + if(!s) { free(g); return NULL; } + + g->scene = s; + g->tabs = NULL; + g->tab_count = 0; + g->fkey_level = 0; + + jlabel *title = name ? jlabel_create(name, s) : NULL; + jwidget *stack = jwidget_create(s); + jfkeys *fkeys = _(img,labels) ? jfkeys_create(_(img,labels), s) : NULL; + + if((name && !title) || !stack || (_(img,labels) && !fkeys)) { + jwidget_destroy(s); + return NULL; + } + + g->title = title; + g->fkeys = fkeys; + + jlayout_set_vbox(s)->spacing = _(1,3); + jlayout_set_stack(stack); + + if(title) { + jwidget_set_background(title, C_BLACK); + jlabel_set_text_color(title, C_WHITE); + jlabel_set_font(title, _(&font_title, dfont_default())); + jwidget_set_stretch(title, 1, 0, false); + + #ifdef FX9860G + jwidget_set_padding(title, 1, 1, 0, 1); + jwidget_set_margin(title, 0, 0, 1, 0); + #endif + + #ifdef FXCG50 + jwidget_set_padding(title, 3, 6, 3, 6); + #endif + } + + #ifdef FXCG50 + jwidget_set_padding(stack, 1, 3, 1, 3); + #endif + + jwidget_set_stretch(stack, 1, 1, false); + return g; +} + +void gscreen_destroy(gscreen *s) +{ + if(s->scene) jwidget_destroy(s->scene); + free(s->tabs); + free(s); +} + +/* tab_stack(): Stacked widget where the tabs are located */ +static jwidget *tab_stack(gscreen *s) +{ + int index = (s->title != NULL) ? 1 : 0; + return s->scene->widget.children[index]; +} + +//--- +// Function bar settings +//--- + +void gscreen_set_fkeys_level(gscreen *s, int level) +{ + s->fkey_level = level; +} + +//--- +// Tab settings +//--- + +void gscreen_add_tab(gscreen *s, void *widget, void *focus) +{ + struct gscreen_tab *t = realloc(s->tabs, (s->tab_count+1) * sizeof *t); + if(!t) return; + + s->tabs = t; + s->tabs[s->tab_count].title_visible = (s->title != NULL); + s->tabs[s->tab_count].fkeys_visible = (s->fkeys != NULL); + s->tabs[s->tab_count].focus = focus; + s->tab_count++; + + jwidget_add_child(tab_stack(s), widget); + jwidget_set_stretch(widget, 1, 1, false); +} + +#undef gscreen_add_tabs +void gscreen_add_tabs(gscreen *s, ...) +{ + va_list args; + va_start(args, s); + jwidget *w; + + while((w = va_arg(args, jwidget *))) + gscreen_add_tab(s, w, NULL); + + va_end(args); +} + +void gscreen_set_tab_title_visible(gscreen *s, int tab, bool visible) +{ + if(!s->title || tab < 0 || tab >= s->tab_count) return; + s->tabs[tab].title_visible = visible; + + if(gscreen_current_tab(s) == tab) + jwidget_set_visible(s->title, visible); +} + +void gscreen_set_tab_fkeys_visible(gscreen *s, int tab, bool visible) +{ + if(!s->fkeys || tab < 0 || tab >= s->tab_count) return; + s->tabs[tab].fkeys_visible = visible; + + if(gscreen_current_tab(s) == tab) + jwidget_set_visible(s->fkeys, visible); +} + +//--- +// Tab navigation +//--- + +bool gscreen_show_tab(gscreen *s, int tab) +{ + jwidget *stack = tab_stack(s); + jlayout_stack *l = jlayout_get_stack(stack); + + /* Find widget ID in the stack + int i = 0; + while(i < stack->child_count && stack->children[i] != widget) i++; + if(i >= stack->child_count || l->active == i) return false; */ + if(tab < 0 || tab >= stack->child_count) return false; + + /* Update keyboard focus */ + s->tabs[l->active].focus = jscene_focused_widget(s->scene); + jscene_set_focused_widget(s->scene, s->tabs[tab].focus); + + l->active = tab; + stack->update = 1; + + /* Hide or show title and function key bar as needed */ + jwidget_set_visible(s->title, s->tabs[tab].title_visible); + if(s->fkeys) jwidget_set_visible(s->fkeys, s->tabs[tab].fkeys_visible); + + return true; +} + +int gscreen_current_tab(gscreen *s) +{ + jwidget *stack = tab_stack(s); + jlayout_stack *l = jlayout_get_stack(stack); + return l->active; +} + +bool gscreen_in(gscreen *s, int tab) +{ + return gscreen_current_tab(s) == tab; +} + +//--- +// Focus management +//--- + +void gscreen_focus(gscreen *s, void *widget) +{ + return jscene_set_focused_widget(s->scene, widget); +}