first version - and it works too!
This commit is contained in:
commit
c32e91b7f4
|
@ -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
|
|
@ -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()
|
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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_ */
|
|
@ -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 */
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
|
@ -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");
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue