flog: add a widget to show a line-numbered scrollable string log

This commit is contained in:
Lephenixnoir 2021-05-18 11:39:33 +02:00
parent c32e91b7f4
commit ab754d9bdd
Signed by: Lephenixnoir
GPG Key ID: 1BBA026E13FC0495
6 changed files with 271 additions and 3 deletions

View File

@ -18,6 +18,7 @@ set(SOURCES
src/widgets/fbar.c
src/widgets/fbrowser.c
src/widgets/flist.c
src/widgets/flog.c
src/widgets/gscreen.c
# ctype
src/ctype/trivialties.c
@ -34,7 +35,8 @@ 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_compile_options(fxlibctest PRIVATE
-Wall -Wextra -Os -fmacro-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}/src/=)
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)

View File

@ -24,6 +24,7 @@ typedef struct ft_test {
jwidget *(*widget)(struct ft_test *test);
/* Test log; this can be displayed by a scrolling widget */
char *log;
uint16_t log_size;
/* Number of passed, skipped and failed assertions in the test */
uint16_t passed;
uint16_t skipped;

47
include/ft/widgets/flog.h Normal file
View File

@ -0,0 +1,47 @@
//---
// ft.widgets.flog: A scrollable log based on a string
//---
#ifndef _FT_WIDGETS_FLOG_H
#define _FT_WIDGETS_FLOG_H
#include <justui/jwidget.h>
#include <gint/display.h>
/* flog: A scrollable log with line wrapping
This widget displays long strings with newlines in a scrollable region. */
typedef struct {
jwidget widget;
/* Log to display and its size */
char const *log;
int log_size;
/* Total number of lines, and number of visible lines */
int lines;
int visible;
/* Current scroll offset */
int top;
/* Font and extra line spacing */
font_t const *font;
int line_spacing;
} flog;
/* flog_create(): Create a log browser */
flog *flog_create(void *parent);
/* flog_set_log(): Set the log string
If size is set to -1, strlen(string) is used instead. */
void flog_set_log(flog *l, char const *string, int size);
//---
// Display properties
//---
/* The default font is dfont_default() */
font_t const *flog_font(flog *l);
void flog_set_font(flog *l, font_t const *font);
/* Extra vertical spacing between lines. The default line spacing is 1. */
int flog_line_spacing(flog *l);
void flog_set_line_spacing(flog *l, int line_spacing);
#endif /* _FT_WIDGETS_FLOG_H */

View File

@ -2,8 +2,9 @@
#include <gint/keyboard.h>
#include <ft/widgets/gscreen.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/fbrowser.h>
#include <ft/widgets/flist.h>
#include <ft/widgets/flog.h>
#include <ft/test.h>
#include <ft/all-tests.h>
@ -67,6 +68,11 @@ int main(void)
gscreen_add_tab(scr, results, NULL);
gscreen_set_tab_fkeys_visible(scr, 1, false);
flog *testlog = flog_create(NULL);
flog_set_line_spacing(testlog, 2);
gscreen_add_tab(scr, testlog, testlog);
gscreen_set_tab_fkeys_visible(scr, 2, false);
// Event handling
while(1) {
jevent e = jscene_run(scr->scene);
@ -125,6 +131,10 @@ int main(void)
jscene_set_focused_widget(scr->scene, w);
}
}
else if(ft_test_done(test) && test->log) {
flog_set_log(testlog, test->log, test->log_size);
gscreen_show_tab(scr, 2);
}
}
// Close test results
@ -135,6 +145,10 @@ int main(void)
jwidget_destroy(results->children[0]);
}
}
if(tab == 2 && key == KEY_EXIT) {
flog_set_log(testlog, NULL, -1);
gscreen_show_tab(scr, 0);
}
}
gscreen_destroy(scr);

View File

@ -1,4 +1,6 @@
#include <ft/test.h>
#include <gint/std/stdio.h>
#include <gint/std/stdlib.h>
#include <stdarg.h>
#undef ft_assert
@ -11,6 +13,8 @@ void ft_test_init(ft_test *test)
test->passed = 0;
test->skipped = 0;
test->failed = 0;
test->log = NULL;
test->log_size = 0;
}
void ft_test_run(ft_test *test)
@ -33,12 +37,25 @@ void ft_assert(ft_test *test, int expression, char const *file, int line,
}
else {
test->failed++;
ft_log(test, "%s:%d: assertion '%s' failed", file, line, str);
ft_log(test, "%s:%d: assertion failed:\n %s\n", file, line, str);
}
}
void ft_log(ft_test *test, char const *format, ...)
{
va_list args1, args2;
va_start(args1, format);
va_copy(args2, args1);
int size = vsnprintf(NULL, 0, format, args1);
if(test->log_size + size > 0xffff) return;
char *newlog = realloc(test->log, test->log_size + size + 1);
if(!newlog) return;
vsnprintf(newlog + test->log_size, size + 1, format, args2);
test->log = newlog;
test->log_size += size;
}
//---

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

@ -0,0 +1,187 @@
#include <ft/widgets/flog.h>
#include <justui/jwidget-api.h>
#include <gint/std/stdlib.h>
#include <gint/std/string.h>
#include <ft/util.h>
/* Type identifier for flog */
static int flog_type_id = -1;
flog *flog_create(void *parent)
{
if(flog_type_id < 0) return NULL;
flog *l = malloc(sizeof *l);
if(!l) return NULL;
jwidget_init(&l->widget, flog_type_id, parent);
l->log = NULL;
l->log_size = -1;
l->lines = 0;
l->visible = 0;
l->top = 0;
l->font = dfont_default();
l->line_spacing = 1;
return l;
}
void flog_set_log(flog *l, char const *log, int log_size)
{
if(log_size == -1 && log != NULL)
log_size = strlen(log);
l->log = log;
l->log_size = log_size;
l->widget.dirty = 1;
}
font_t const *flog_font(flog *l)
{
return l->font;
}
void flog_set_font(flog *l, font_t const *font)
{
if(l->font == font) return;
l->font = font;
l->widget.dirty = 1;
}
int flog_line_spacing(flog *l)
{
return l->line_spacing;
}
void flog_set_line_spacing(flog *l, int line_spacing)
{
if(l->line_spacing == line_spacing) return;
l->line_spacing = line_spacing;
l->widget.dirty = 1;
}
//---
// Polymorphic widget operations
//---
static void flog_poly_layout(void *l0)
{
flog *l = l0;
int total_height = jwidget_content_height(l) + l->line_spacing;
l->visible = total_height / (l->font->line_height + l->line_spacing);
/* Compute number of lines. Keep 3 pixels of room for the scrollbar */
int const w = max(0, jwidget_content_width(l) - 33);
char const *log = l->log;
l->lines = 0;
if(l->log) while(log < l->log + l->log_size) {
if(log[0] == '\n') {
log++;
l->lines++;
}
else {
char const *endscreen = drsize(log, NULL, w, NULL);
char const *endline = strchrnul(log, '\n');
int len = max(1, min(endscreen-log, endline-log));
log += len + (log[len] == '\n');
l->lines++;
}
}
}
static void flog_poly_render(void *l0, int x, int y)
{
flog *l = l0;
if(!l->log || l->log_size <= 0) return;
int const w = jwidget_content_width(l);
int const h = jwidget_content_height(l);
char const *log = l->log;
/* current_y is ignored until current_line >= top, at which point it
starts at y and increases each line */
int current_line = 0;
int current_y = y;
while(log < l->log + l->log_size)
{
if(log[0] == '\n') {
log++;
}
else {
char const *endscreen = drsize(log, NULL, w - 33, NULL);
char const *endline = strchrnul(log, '\n');
int len = max(1, min(endscreen-log, endline-log));
if(current_line >= l->top && current_line < l->top + l->visible) {
dprint_opt(x+22, current_y, C_RGB(21,21,21), C_NONE,
DTEXT_RIGHT, DTEXT_TOP, "%d", current_line+1);
dtext_opt(x+28, current_y, C_BLACK, C_NONE, DTEXT_LEFT,
DTEXT_TOP, log, len);
}
log += len + (log[len] == '\n');
}
current_line++;
if(current_line > l->top)
current_y += l->font->line_height + l->line_spacing;
}
if(current_line > l->visible) {
/* Area where the scroll bar lives */
int area_w = _(1,2);
int area_x = x + w - area_w;
int area_y = y;
int area_h = h;
/* Position and size of scroll bar within the area */
int bar_y = (l->top * area_h) / current_line;
int bar_h = (l->visible * area_h) / current_line;
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 flog_poly_event(void *l0, jevent e)
{
flog *l = l0;
int key = (e.type==JWIDGET_KEY && e.key.type!=KEYEV_UP) ? e.key.key : 0;
int scroll_speed = 1;
if(e.key.shift) scroll_speed = 4;
if(e.key.alpha) scroll_speed = 16;
if(key == KEY_UP && l->top > 0) {
l->top = max(0, l->top - scroll_speed);
l->widget.update = 1;
return true;
}
if(key == KEY_DOWN) {
l->top = min(l->lines - l->visible, l->top + scroll_speed);
l->widget.update = 1;
return true;
}
return false;
}
/* flog typed definition */
static jwidget_poly type_flog = {
.name = "flog",
.layout = flog_poly_layout,
.render = flog_poly_render,
.event = flog_poly_event,
};
/* Type registration */
__attribute__((constructor(2000)))
static void j_register_flog(void)
{
flog_type_id = j_register_widget(&type_flog, "jwidget");
}