first version - and it works too!

This commit is contained in:
Lephenixnoir 2021-05-14 11:10:03 +02:00
commit c32e91b7f4
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
21 changed files with 2095 additions and 0 deletions

16
.gitignore vendored Normal file
View File

@ -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

53
CMakeLists.txt Normal file
View File

@ -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()

BIN
assets-cg/icon-sel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets-cg/icon-uns.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

21
include/ft/all-tests.h Normal file
View File

@ -0,0 +1,21 @@
//---
// ft.all-tests: List of all test structures defined by modules
//---
#ifndef _FT_ALL_TESTS_H_
#define _FT_ALL_TESTS_H_
#include <ft/test.h>
/* 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_ */

93
include/ft/test.h Normal file
View File

@ -0,0 +1,93 @@
//---
// ft.test: Testing tools and status reports
//---
#ifndef _FT_TEST_H_
#define _FT_TEST_H_
#include <gint/display.h>
#include <justui/jwidget.h>
//---
// 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_ */

16
include/ft/util.h Normal file
View File

@ -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_ */

36
include/ft/widgets/fbar.h Normal file
View File

@ -0,0 +1,36 @@
//---
// ft.widgets.fbar: A colored bar summarizing test results
//---
#ifndef _FT_WIDGETS_FBAR_H_
#define _FT_WIDGETS_FBAR_H_
#include <justui/jwidget.h>
#include <ft/test.h>
/* 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_ */

View File

@ -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 <justui/jwidget.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/fbar.h>
#include <ft/test.h>
/* 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_ */

View File

@ -0,0 +1,79 @@
//---
// ft.widgets.flist: Scrolling list with selectable elements
//---
#ifndef _FT_WIDGETS_FLIST_H_
#define _FT_WIDGETS_FLIST_H_
#include <justui/jwidget.h>
#include <gint/display.h>
#include <gint/defs/call.h>
/* 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_ */

View File

@ -0,0 +1,110 @@
//---
// gintctl.widgets.gscreen: gintctl's extended standard scene
//---
#ifndef _GINTCTL_WIDGETS_GSCREEN
#define _GINTCTL_WIDGETS_GSCREEN
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#include <ft/util.h>
#include <gint/display.h>
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 */

28
src/ctype/trivialties.c Normal file
View File

@ -0,0 +1,28 @@
#include <ft/test.h>
#include <ft/all-tests.h>
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,
};

142
src/main.c Normal file
View File

@ -0,0 +1,142 @@
#include <gint/display.h>
#include <gint/keyboard.h>
#include <ft/widgets/gscreen.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/fbrowser.h>
#include <ft/test.h>
#include <ft/all-tests.h>
/* We don't initialize the test result fields below */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
ft_list headers_libc[] = {
{ "<ctype.h>", (ft_test*[]){
&ft_ctype_trivial_success,
&ft_ctype_trivial_failure,
&ft_ctype_trivial_empty,
NULL,
}},
{ "<locale.h>", NULL },
{ "<setjmp.h>", NULL },
{ "<signal.h>", NULL },
{ "<stdio.h>", NULL },/*(ft_test[]){
{ "Formatted printing basics", NULL, NULL },
{ "Format types and options", NULL, NULL },
{ NULL },
}}, */
{ "<stdlib.h>", NULL },
{ "<string.h>", (ft_test*[]){
&ft_string_memset,
&ft_string_memcpy,
&ft_string_memmove,
NULL,
}},
{ "<time.h>", NULL },
{ "<uchar.h>", NULL },
{ "<wchar.h>", NULL },
{ "<wctype.h>", NULL },
{ NULL }
};
ft_list headers_posix[] = {
{ "<unistd.h>", 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;
}

168
src/string/core.c Normal file
View File

@ -0,0 +1,168 @@
#include <ft/test.h>
#include <ft/all-tests.h>
#include <gint/std/string.h>
#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,
};

199
src/string/memarray.c Normal file
View File

@ -0,0 +1,199 @@
#include <gint/defs/types.h>
#include <gint/std/string.h>
#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/exc.h>
#include <gint/defs/call.h>
#include <justui/jpainted.h>
#include <ft/util.h>
#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;
}

96
src/string/memarray.h Normal file
View File

@ -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 <gint/defs/types.h>
#include <ft/test.h>
#include <justui/jwidget.h>
/* 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 */

114
src/test.c Normal file
View File

@ -0,0 +1,114 @@
#include <ft/test.h>
#include <stdarg.h>
#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;
}

187
src/widgets/fbar.c Normal file
View File

@ -0,0 +1,187 @@
#include <ft/widgets/fbar.h>
#include <justui/jwidget-api.h>
#include <gint/std/stdlib.h>
#include <gint/std/string.h>
/* 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");
}

273
src/widgets/fbrowser.c Normal file
View File

@ -0,0 +1,273 @@
#include <ft/widgets/fbrowser.h>
#include <justui/jwidget-api.h>
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <gint/std/stdlib.h>
/* 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();
}

217
src/widgets/flist.c Normal file
View File

@ -0,0 +1,217 @@
#include <ft/widgets/flist.h>
#include <ft/util.h>
#include <justui/jwidget-api.h>
#include <gint/std/stdlib.h>
/* 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();
}

188
src/widgets/gscreen.c Normal file
View File

@ -0,0 +1,188 @@
#include <ft/widgets/gscreen.h>
#include <ft/util.h>
#include <gint/std/stdlib.h>
#include <stdarg.h>
#include <justui/jscene.h>
#include <justui/jlabel.h>
#include <justui/jfkeys.h>
#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);
}