flog: add a widget to show a line-numbered scrollable string log
This commit is contained in:
parent
c32e91b7f4
commit
ab754d9bdd
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
16
src/main.c
16
src/main.c
|
@ -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);
|
||||
|
|
19
src/test.c
19
src/test.c
|
@ -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;
|
||||
}
|
||||
|
||||
//---
|
||||
|
|
|
@ -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");
|
||||
}
|
Loading…
Reference in New Issue