From 5c12cb56534669a1534c8d2269ee1d074897a72b Mon Sep 17 00:00:00 2001 From: Lephenixnoir Date: Tue, 23 May 2023 01:05:19 +0200 Subject: [PATCH] let there be a text viewer --- .gitignore | 14 ++ CMakeLists.txt | 26 +++ README.md | 15 ++ assets-cg/fxconv-metadata.txt | 16 ++ assets-cg/icon-cg.xcf | Bin 0 -> 43915 bytes assets-cg/icon-sel.png | Bin 0 -> 16759 bytes assets-cg/icon-uns.png | Bin 0 -> 13153 bytes assets-cg/uf5x7 | 1 + assets-cg/uf8x9 | 1 + screenshots/fileselect.png | Bin 0 -> 3736 bytes screenshots/search.png | Bin 0 -> 6116 bytes screenshots/start.png | Bin 0 -> 1728 bytes screenshots/textview.png | Bin 0 -> 6223 bytes screenshots/textviewer.gif | Bin 0 -> 10802 bytes src/main.c | 381 +++++++++++++++++++++++++++++++++ src/vtext.c | 386 ++++++++++++++++++++++++++++++++++ src/vtext.h | 67 ++++++ 17 files changed, 907 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 assets-cg/fxconv-metadata.txt create mode 100644 assets-cg/icon-cg.xcf create mode 100644 assets-cg/icon-sel.png create mode 100644 assets-cg/icon-uns.png create mode 120000 assets-cg/uf5x7 create mode 120000 assets-cg/uf8x9 create mode 100644 screenshots/fileselect.png create mode 100644 screenshots/search.png create mode 100644 screenshots/start.png create mode 100644 screenshots/textview.png create mode 100644 screenshots/textviewer.gif create mode 100644 src/main.c create mode 100644 src/vtext.c create mode 100644 src/vtext.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..baadade --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Build files +/build-fx +/build-cg +/build-cg-push +/*.g1a +/*.g3a + +# Python bytecode +__pycache__/ + +# Common IDE files +*.sublime-project +*.sublime-workspace +.vscode diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3144935 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,26 @@ +# 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(CGTextViewer) + +include(GenerateG3A) +include(Fxconv) +find_package(Gint 2.9 REQUIRED) +find_package(JustUI 1.3 REQUIRED) + +set(SOURCES + src/main.c + src/vtext.c) +set(ASSETS + assets-cg/uf5x7 + assets-cg/uf8x9) + +fxconv_declare_assets(${ASSETS} WITH_METADATA) + +add_executable(myaddin ${SOURCES} ${ASSETS}) +target_compile_options(myaddin PRIVATE -Wall -Wextra -Os) +target_link_libraries(myaddin JustUI::JustUI Gint::Gint) + +generate_g3a(TARGET myaddin OUTPUT "TextView.g3a" + NAME "TextViewer" ICONS assets-cg/icon-uns.png assets-cg/icon-sel.png) diff --git a/README.md b/README.md new file mode 100644 index 0000000..f1a1fa2 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# UTF-8 Text Viewer for fx-CG + +A basic text viewer programmed in an evening — expect a correspondingly polished experience! + +![](screenshots/textviewer.gif) + +Features: +* View UTF-8 files +* Word wrapping: no wrapping, by character, by word +* Two fonts: proportional 8x9 and fixed-width 5x7 +* Basic search function to get around the document + +**How search works** + +It searches from the second line visible on screen to the end of the document, and if the requested search key is found, it brings the line where the key occurs to the top of the screen. Otherwise, it searches again from the start. diff --git a/assets-cg/fxconv-metadata.txt b/assets-cg/fxconv-metadata.txt new file mode 100644 index 0000000..5002737 --- /dev/null +++ b/assets-cg/fxconv-metadata.txt @@ -0,0 +1,16 @@ +uf5x7: + type: font + name: uf5x7 + charset: unicode + grid.size: 5x7 + grid.padding: 1 + +uf8x9: + type: font + name: uf8x9 + charset: unicode + grid.size: 8x11 + grid.padding: 1 + proportional: true + height: 9 + diff --git a/assets-cg/icon-cg.xcf b/assets-cg/icon-cg.xcf new file mode 100644 index 0000000000000000000000000000000000000000..344c4cb9a07193d805c3082d4f64497b50006686 GIT binary patch literal 43915 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 H!SVkA?}8&f literal 0 HcmV?d00001 diff --git a/assets-cg/icon-sel.png b/assets-cg/icon-sel.png new file mode 100644 index 0000000000000000000000000000000000000000..378de274e0d4429d90450a7b8c13fc4cbc90ad09 GIT binary patch literal 16759 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 E7wD%Z(*OVf literal 0 HcmV?d00001 diff --git a/assets-cg/icon-uns.png b/assets-cg/icon-uns.png new file mode 100644 index 0000000000000000000000000000000000000000..7725ceee5dbcbe65fe6f19e8ffa0082a4b3dae92 GIT binary patch literal 13153 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 N6$K6XH?kI?{|9hz=BWSx literal 0 HcmV?d00001 diff --git a/assets-cg/uf5x7 b/assets-cg/uf5x7 new file mode 120000 index 0000000..988510a --- /dev/null +++ b/assets-cg/uf5x7 @@ -0,0 +1 @@ +../../unicode-fonts/uf5x7 \ No newline at end of file diff --git a/assets-cg/uf8x9 b/assets-cg/uf8x9 new file mode 120000 index 0000000..6cbf6ec --- /dev/null +++ b/assets-cg/uf8x9 @@ -0,0 +1 @@ +../../unicode-fonts/uf8x9 \ No newline at end of file diff --git a/screenshots/fileselect.png b/screenshots/fileselect.png new file mode 100644 index 0000000000000000000000000000000000000000..2756a34f077e887fb5a81a976c911883a2036d17 GIT binary patch literal 3736 zcmeHKS5p%TunxVc9I6y4ha%!3G^KZ?MWi=F4X7xg1S!%JK#{IAsR1lO2!swtq=bYj zAc#s4C8363C?Vk>C6tRZ_x=8X`?9mMJNvL@zVF*KI~$Ah++y4S0N^~x($oO}V9Gr6 z7dV;EY=zihDgbc78)RzY6ouR*;0u0UJ(u9KSFTJyu`L}?+LEa<%*=j@gN%-!mMQB# zKaV;uhWj|Hc~%O*u^gj$yAi~tkL%R+{K1c!nA*sQ1AyN_KzC#0!ro`b@s8mKBLPD| zT#-r5zb_0&Ub~1|PcH8kRqD%_2Q5Jk?t&Yxbj7tDS?y$*&g&$sAI-$#iG60qP0u+p z_-U2~V5EzeB>DnQqF7cjpxx=@;rT*C`{&XH6A;2`!fgy45oRr@U_uUi zhx^L0Nw*EYb;FbuSXsD7YYMOUbNz+-ZAVtSg{e-KAiscU5wO%IH8AiJBbYW&I;#>zlt~B)teD9R+l>3P(i;d1b7c_j863 zJ(*8fz47pf@|jGbbIhP9785~_jk7LE#X5PRgsbqo$=zuqnT^>alqj008WNc_P1|3{ zJ%srf*oQF;WLhLkBv1Dwq9|UC+qyPr*~`ytEUOQiGL^2l`=3%|Z%@8#_~n?5m!85H z`fj|ARN&y;T|}HK3(>>7do5iLuvGInF&_Swu$G$ zXXM_|rucY|*$koEa`d>Mnkv|yK2`W+sBYMVN*i0dFz$b%!Z6Q{Ngzt!m|(J5e&{q>f4?zK~99lvW`Z2H6E{P5w`xXV}47G!EHh)dmPvL&U~zgpoo#T!I+L? zkx{P0Ue)vBW(Ia{3-?AFo3(pDHBjabEP#owrDY|D?Fx5_S(JW}3+?+lf3xw}3ZIcL zFqclERx(SPZ^(z+*m=@{rew7WFB3+*9AxX{Sbw;3=s8Mob~!4 z>%z`GTfgr~gs*uTJpJVixlA;F2}aMYfn`ku&J!?cZf+`yEs0u|8P`-jlyOGz=rllxltt%YXrYczsb zeXJC%lvUq+Wk*Vf=#IS)v4-jcFCRdi=&MYey8rDj1pY1Ez9WRo%7&bi*5J=%LQPw$ z64RJwxW08Seh|+ygghYB2q@g)l4Mv#=$x#6wzOIeT%G%=6?xOuu}o4V5VxI-dxCcV zv@TVuV&+>szs!E<_i(X`=2yYhl4rPwpR|%y2$Fef3D}2G&4JUE2jg80!UobB$Cy*bV0bU z@s!YOiu13SW??QtmX_k*JE)Fy@r9==i4@zUJZ122;cxd^$)DM4u9KyF+7F%no`|AD0@G``Wi=A+5s5bb~ zi(s3t>I}`qZ%S@%Y8DREah1gV?LE|V@?@-GX`!i@bZ)_NAq(%cxar8w5Ob2VpDPzJ z-1hdXj_LBH(jrV@5t>OrBF&ESLPi>6rHX}ja6sCg@KN0SvYQLik(h-knF#c{BTlO` z;^~jUX;i-zOMr8RGP-oAL~#4>!_crS^xjdEpQ-ZSKz3s&2E=5UCCw(SktCQf$u5vt zL9Qh#l`Dj*5OwZgv>#CrEp)hS^| zV}PQZs2j*o>|f$f!h`0(1jWk@pzgz-ci*Ema&IUp zr_Bf*!M1r=`5h2wDx!p}SE_RyDqM_&1ZH_qvR;vj@oSQSpvLP8mC>(2#(p$V!P)JQ zYC4b~U3S+6GG&2b&=08A!yoi9_3++qkC&`1%J_X0ETdRk(mv%~1`>QRJLS#||KIbK zzu*L}!6oEsP9@hcEkeEreHSJiRUV0jscw1Y(Z5Abb;h!A4L_GX=$mdkOMQg7_=dC~ zAwn#`;sQvQZb^N}kS5Fg>fk&?|DEKa=M+DHPrrukf1xa=(^XHM?l6;p$-kJhq3+?| z<)dd_*sfxf638I5P?l}%ZM4#qQY@fO(UE(T&&oHR#;l*F$^LXn<}VO~UTYa+d$ zi#YES$dhA8=tgU9LjOAyP&j_llP4#p&aJ=vN!Et1eyT1*lriypYeTeJCOE|U)_>cp z?)Y9}_hk5lDB{8jCVyryGVy{d2eLuStE=_tVxW7Nxw*rVLB38OX}+q*xyoz( z63^^FRCzrx-=%juCs_Q$V+d6JL5C}UXKTtRwQf(kIfDinD)d;FAm50KoA{YalO{DR z*7jYhs*WON9@bp(xt01KEC|4R*$=V1FzW465peH}N6bQ9@-f&=3lcGAG;H!wpl7VB zRjOUP;`(xs1VWr^5H6JS?O9tS(jBu9VqtTEbpzEt?;1XJ*0Cx2#Xk1pWePPeQg;JtB@TEgVzz*S`EI|L{s zGARZ`O~zRR-8iWog_YB_<*P_VOxe~Lw?ns?227oqSD>fKYkpT55k6vnDoG9K>;5VZ zLTfG5y|0msbaPBuS(T@m+sqg7%3VDDq*CK6LH+5g36z*g&!NtP+hKZd+YJ>$L9Vh- z3Ip>_b`m~q8Rl`;fi!O7a<~W>L}y11G_aeB5SqAWZMO>FF!LR*CI5WLzN%(RRB#@L zd#{{Z=BchYAo3rXsv-Q*V5%`s#U&KKxF?;x9!M}wfy@Plfc6r zpcvOHtA`bn7UScH40C1?^FKqfRAtY0V~A&Vh6as;g~Ub!e0)=>C^-E|?SlJ0d}f+* z(#Jz4YKf3Hrj~@gLX7TE3$87Q9@%22*h)luBY3&Deu3MIuNApsyef&knJWslB&~TN z#UWhq5?QC&(?5Bw#7VW`PFIIB-z|grt3xW@W+Q5U?R~}Kzv~1GHP;<&!mj||iGoK3 zWPa(>=u=lIqf^$$Z$h5gM0FkJc~f6053%mjI$E1pR};abxpM|>gfNRMck!k9cjKzk zYMVHQtXY8IW;TE{FL=7b1r@U9xhT!r^_K#Dzj}6EH}gW-72&}lJM9{`cQUd* z)r&eTMq_N@f3!#pHJa;_aM;j?BHbFJ>5QdmS~D*4W&K=4q%9%n3#J;|sN5KAv<(=u zJ^4076M%ZXMA9AWPuP?@HPBYxR$27Hdh zQ>?XWF0az1IU}&kSSpttx~$h$uDobnq57KY{C0|b>=q6rxd6TURY0Lna@Ia}fM?BP6o` literal 0 HcmV?d00001 diff --git a/screenshots/search.png b/screenshots/search.png new file mode 100644 index 0000000000000000000000000000000000000000..c1f0eece5642672d5a448caa86106fb9bb38e8fb GIT binary patch literal 6116 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 literal 0 HcmV?d00001 diff --git a/screenshots/start.png b/screenshots/start.png new file mode 100644 index 0000000000000000000000000000000000000000..1c1b37721c64a02ebe302d0f191cf1bf26cbefe3 GIT binary patch literal 1728 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "vtext.h" + +#define ENABLE_USB 1 +#ifdef ENABLE_USB +#include +#include +#endif + +extern font_t uf5x7; +extern font_t uf8x9; + +// Global application data // + +struct source { + /* Status (0 if everything is fine, error number otherwise) */ + int status; + /* File descriptor (usually file is closed after loading and this is -1) */ + int fd; + /* File path (owned by this structure) */ + char *path; + /* Full data loaded to memory */ + // TODO: Load partial files? + char *data; + /* General file information */ + int size; + int lines; +}; + +struct search_results { + /* Source buffer that this indexes in. */ + struct source *source; + /* Offset of occurrences, depending on the size of the file */ + union { + uint16_t *occs16; + uint32_t *occs32; + }; +}; + +struct { + /* GUI elements */ + jscene *scene; + jlabel *title; + jlabel *status; + jfkeys *fkeys; + jfileselect *fileselect; + jinput *search_input; + vtext *viewer; + + /* Current open file. */ + struct source source; + /* Search results. */ + struct search_results *search_results; + /* Current view. */ + int view; + /* Current position in the menus. */ +// int fkmenu; +} app; + +enum { + VIEW_FILESELECT, + VIEW_TEXT, + VIEW_SEARCH, +}; + +// General UI update functions // + +static void fkeys_sprintf(jfkeys *fk, char const *fmt, ...) +{ + static char str[128]; + va_list args; + va_start(args, fmt); + vsnprintf(str, sizeof str, fmt, args); + va_end(args); + jfkeys_set(fk, str); +} + +static void update_fkeys(void) +{ + char wrap[16]; + if(!app.viewer) + strcpy(wrap, ""); + else if(app.viewer->wrap == VTEXT_WRAP_NONE) + strcpy(wrap, "@NOWRAP"); + else if(app.viewer->wrap == VTEXT_WRAP_CHAR) + strcpy(wrap, "@WRAP(C)"); + else if(app.viewer->wrap == VTEXT_WRAP_WORD) + strcpy(wrap, "@WRAP(W)"); + + if(app.view == VIEW_FILESELECT) { + jfkeys_set(app.fkeys, "/OPEN;;;;;"); + } + else if(app.view == VIEW_TEXT) { + fkeys_sprintf(app.fkeys, "/OPEN;/SEARCH;%s;@FONT;;", wrap); + } + else if(app.view == VIEW_SEARCH) { + fkeys_sprintf(app.fkeys, "/OPEN;#SEARCH;%s;@FONT;;", wrap); + } +} + +static void update_status(void) +{ + struct source *s = &app.source; + + if(!s->path) { + jwidget_set_visible(app.status, false); + jlabel_set_text(app.title, "Text Viewer"); + return; + } + + jwidget_set_visible(app.status, true); + jlabel_asprintf(app.title, "%s - Text Viewer", s->path); + + if(s->status) { + jlabel_asprintf(app.status, "Error %d!", s->status); + } + else { + jlabel_asprintf(app.status, "vlines %d--%d/%d [%d] (%d lines, %d B)", + app.viewer->scroll + 1, + app.viewer->scroll + app.viewer->visible_lines, + app.viewer->virtual_lines, + app.viewer->max_scroll, + s->lines, s->size); + } +} + +static void switch_to_view(int view) +{ + if(view == VIEW_FILESELECT) { + jscene_show_and_focus(app.scene, app.fileselect); + } + if(view == VIEW_TEXT) { + jscene_show_and_focus(app.scene, app.viewer); + } + + if(view == VIEW_SEARCH) { + jscene_show_and_focus(app.scene, app.search_input); + } + else { + jwidget_set_visible(app.search_input, false); + } + + app.view = view; + update_fkeys(); + update_status(); +} + +static void source_analyze(struct source *source) +{ + source->lines = 1; + for(int i = 0; i < source->size; i++) + source->lines += (source->data[i] == '\n'); + + // TODO: Check if source is valid UTF-8 +} + +static int source_open(struct source *source, char const *path) +{ + memset(source, 0, sizeof *source); + + int fd = open(path, O_RDONLY); + if(fd < 0) + return fd; + + int size = lseek(fd, 0, SEEK_END); + lseek(fd, 0, SEEK_SET); + + char *data = malloc(size + 1); + if(!data) { + close(fd); + return -ENOMEM; + } + + int rc = read(fd, data, size); + if(rc < 0) { + close(fd); + return rc; + } + if(rc < size) { + close(fd); + return -999; + } + + data[size] = 0; + close(fd); + + source->fd = -1; + source->path = strdup(path); + source->data = data; + source->size = size; + source_analyze(source); + return 0; +} + +static void source_close(struct source *source) +{ + free(source->path); + free(source->data); + memset(source, 0, sizeof *source); +} + +void txt_view(void) +{ + memset(&app, 0, sizeof app); + + // + + jscene *scene = jscene_create_fullscreen(NULL); + jlabel *title = jlabel_create("Text Viewer", scene); + jwidget *stack = jwidget_create(scene); + jfkeys *fkeys = jfkeys_create(";;;;;",scene); + + if(!scene || !title || !stack || !fkeys) { + jwidget_destroy(scene); + return; + } + app.scene = scene; + app.title = title; + app.fkeys = fkeys; + + jwidget_set_background(title, C_BLACK); + jlabel_set_text_color(title, C_WHITE); + jwidget_set_stretch(title, 1, 0, false); + jwidget_set_padding(title, 3, 6, 3, 6); + + jlayout_set_vbox(scene)->spacing = 3; + jlayout_set_stack(stack); + jwidget_set_padding(stack, 0, 6, 0, 6); + jwidget_set_stretch(stack, 1, 1, false); + + // Main tab // + + jwidget *tab_viewer = jwidget_create(NULL); + vtext *viewer = vtext_create(tab_viewer); + jinput *search_input = jinput_create("Search: ", 40, tab_viewer); + jlabel *status = jlabel_create("", tab_viewer); + app.viewer = viewer; + app.search_input = search_input; + app.status = status; + + jwidget_set_margin(viewer, 2, 0, 0, 0); + jwidget_set_stretch(viewer, 1, 1, false); + vtext_set_line_spacing(viewer, 3); + vtext_set_font(viewer, &uf8x9); + + jwidget_set_stretch(status, 1, 0, false); + jwidget_set_padding(status, 2, 0, 0, 0); + jwidget_set_borders(status, J_BORDER_SOLID, C_BLACK, 1, 0, 0, 0); + + jwidget_set_stretch(search_input, 1, 0, false); + jwidget_set_visible(search_input, false); + + jlayout_set_vbox(tab_viewer)->spacing = 3; + jwidget_add_child(stack, tab_viewer); + jwidget_set_stretch(tab_viewer, 1, 1, false); + + // File selection tab // + + jfileselect *fileselect = jfileselect_create(stack); + jfileselect_set_show_file_size(fileselect, true); + jwidget_set_stretch(fileselect, 1, 1, false); + app.fileselect = fileselect; + + // Initial state // + + switch_to_view(VIEW_TEXT); + + // Event handling // + + int key = 0; + while(1) { + jevent e = jscene_run(scene); + + if(e.type == JSCENE_PAINT) { + dclear(C_WHITE); + jscene_render(scene); + dupdate(); + } + if(e.type == JINPUT_VALIDATED) { + char const *key = jinput_value(search_input); + if(*key) { + /* Don't switch to text view so that we can keep searching */ + vtext_scroll_to_substring(viewer, key); + } + else { + switch_to_view(VIEW_TEXT); + } + } + if(e.type == JINPUT_CANCELED) { + switch_to_view(VIEW_TEXT); + } + if(e.type == JFILESELECT_LOADED) { + char const *path = jfileselect_current_folder(fileselect); + jlabel_asprintf(title, "Browsing: %s", path ? path : "(nil)"); + } + if(e.type == JFILESELECT_CANCELED) { + switch_to_view(VIEW_TEXT); + } + if(e.type == JFILESELECT_VALIDATED) { + char const *file = jfileselect_selected_file(fileselect); + if(file) { + source_close(&app.source); + app.source.status = source_open(&app.source, file); + if(!app.source.status) { + vtext_set_source(viewer, app.source.data, app.source.size); + } + switch_to_view(VIEW_TEXT); + } + } + if(e.type == VTEXT_CHANGED) { + update_status(); + } + + // Fkey menu navigation // + + if(e.type != JSCENE_KEY || e.key.type == KEYEV_UP) continue; + key = e.key.key; + +#if ENABLE_USB + if(key == KEY_OPTN && e.key.shift && e.key.alpha) { + if(!usb_is_open()) { + usb_interface_t const *intf[] = { &usb_ff_bulk, NULL }; + usb_open(intf, GINT_CALL_NULL); + usb_open_wait(); + } + usb_fxlink_screenshot(true); + } +#endif + + if(jscene_focused_widget(scene) == search_input) + continue; + + if(key == KEY_F1) { + jfileselect_set_saveas(fileselect, false); + jfileselect_browse(fileselect, "/"); + switch_to_view(VIEW_FILESELECT); + } + else if(key == KEY_F2 && app.source.data) { + jinput_clear(search_input); + switch_to_view(VIEW_SEARCH); + } + else if(key == KEY_F3 && app.source.data) { + if(viewer->wrap == VTEXT_WRAP_NONE) + vtext_set_word_wrapping(viewer, VTEXT_WRAP_CHAR); + else if(viewer->wrap == VTEXT_WRAP_CHAR) + vtext_set_word_wrapping(viewer, VTEXT_WRAP_WORD); + else if(viewer->wrap == VTEXT_WRAP_WORD) + vtext_set_word_wrapping(viewer, VTEXT_WRAP_NONE); + update_fkeys(); + } + else if(key == KEY_F4) { + if(viewer->font == &uf8x9) + vtext_set_font(viewer, &uf5x7); + else + vtext_set_font(viewer, &uf8x9); + } + } + jwidget_destroy(scene); +} + +int main(void) +{ + txt_view(); + return 1; +} diff --git a/src/vtext.c b/src/vtext.c new file mode 100644 index 0000000..e83bcf7 --- /dev/null +++ b/src/vtext.c @@ -0,0 +1,386 @@ +#include "vtext.h" + +#include +#include +#include + +#include +#include +#include + +/* Type identifier for vtext */ +static int vtext_type_id = -1; + +/* Events */ +uint16_t VTEXT_CHANGED; + +vtext *vtext_create(jwidget *parent) +{ + if(vtext_type_id < 0) + return NULL; + + vtext *t = malloc(sizeof *t); + if(!t) + return NULL; + + jwidget_init(&t->widget, vtext_type_id, parent); + + t->data = NULL; + t->size = 0; + t->wrap = VTEXT_WRAP_WORD; + t->scroll = 0; + t->horiz_scroll = 0; + t->virtual_lines = 0; + t->vline_offsets = NULL; + t->visible_lines = 0; + t->line_spacing = 0; + t->font = dfont_default(); + return t; +} + +static char const *word_boundary(char const *start, char const *cursor, bool + look_ahead) +{ + char const *str = cursor; + + /* Look for a word boundary behind the cursor */ + while(1) { + /* Current position is end-of-string: suitable */ + if(*str == 0) return str; + /* Current position is start of string: bad */ + if(str <= start) break; + + /* Look for heteregoneous neighboring characters */ + int space_l = (str[-1] == ' ' || str[-1] == '\n'); + int space_r = (str[0] == ' ' || str[0] == '\n'); + + if(!space_l && space_r) return str; + str--; + } + + /* If we can't look ahead, return the starting position to force a cut */ + if(!look_ahead) return cursor; + str++; + + /* Otherwise, look ahead */ + while(*str) { + int space_l = (str[-1] == ' ' || str[-1] == '\n'); + int space_r = (str[0] == ' ' || str[0] == '\n'); + + if(!space_l && space_r) return str; + str++; + } + + /* If there's really nothing, return end-of-string */ + return str; +} + +static char const *next_line(char const *text, int width, font_t const *font, + vtext_wrap wrap, int *chars_on_this_line) +{ + char const *endline = text; + while(*endline && *endline != '\n') + endline++; + + char const *endscreen = NULL; + if(wrap == VTEXT_WRAP_CHAR || wrap == VTEXT_WRAP_WORD) { + endscreen = drsize(text, font, width, NULL); + if(wrap == VTEXT_WRAP_WORD) { + endscreen = word_boundary(text, endscreen, false); + while(*endscreen == ' ') + endscreen++; + } + } + + if(!endscreen || endline <= endscreen) { + if(chars_on_this_line) + *chars_on_this_line = endline - text; + return endline + (*endline != 0); + } + else { + if(chars_on_this_line) + *chars_on_this_line = endscreen - text; + return endscreen + (*endscreen && endscreen == text); + } +} + +static void update_after_layout(vtext *t) +{ + int ch = jwidget_content_height(t); + int line_height = t->font->line_height + t->line_spacing; + int visible_lines = (ch + t->line_spacing) / line_height; + t->visible_lines = visible_lines; +} + +static void update_after_content_change(vtext *t) +{ + if(!t || !t->data) + return; + + char const *data = t->data; + t->virtual_lines = 0; + int cw = jwidget_content_width(t) - 4; + + while(*data) { + t->virtual_lines++; + data = next_line(data, cw, t->font, t->wrap, NULL); + } + + t->vline_offsets = malloc(t->virtual_lines * sizeof *t->vline_offsets); + if(!t->vline_offsets) { + t->data = NULL; + t->size = 0; + return; + } + + data = t->data; + int i = 0; + + while(*data) { + t->vline_offsets[i] = data - t->data; + i++; + data = next_line(data, cw, t->font, t->wrap, NULL); + } + + t->max_scroll = max(t->virtual_lines - t->visible_lines, 0); +} + +/* Adjust positioning to make sure we stay on legal values */ +static void shake(vtext *t) +{ + vtext_scroll_to(t, t->scroll); + + if(t->wrap != VTEXT_WRAP_NONE) + t->horiz_scroll = 0; +} + +static void emit_changed(vtext *t) +{ + jwidget_emit(t, (jevent){ .type = VTEXT_CHANGED }); +} + +bool vtext_scroll_to(vtext *t, int target_scroll) +{ + target_scroll = max(0, min(target_scroll, t->max_scroll)); + if(t->scroll == target_scroll) + return false; + + t->scroll = target_scroll; + t->widget.update = true; + emit_changed(t); + return true; +} + +static void const *memmem(void const *haystack, size_t haystacklen, + void const *needle, size_t needlelen) +{ + if(haystacklen < needlelen) + return NULL; + + for(size_t i = 0; i < haystacklen - needlelen; i++) { + if(!memcmp(haystack + i, needle, needlelen)) + return haystack + i; + } + + return NULL; +} + +static bool scroll_to_substring_within(vtext *t, char const *substring, + int start, int length) +{ + length = min(length, t->size - start); + + char const *occ = memmem(t->data + start, length, substring, + strlen(substring)); + + if(!occ) + return false; + + /* Find nearest line with lazy linear search */ + int s = 0; + s = 0; + while(s + 1 < t->virtual_lines && + (int)t->vline_offsets[s + 1] <= occ - t->data) { + s++; + } + vtext_scroll_to(t, s); + return true; +} + +bool vtext_scroll_to_substring(vtext *t, char const *key) +{ + int offset = (t->scroll + 1 < t->virtual_lines) + ? (int)t->vline_offsets[t->scroll + 1] + : t->size; + if(scroll_to_substring_within(t, key, offset, t->size - offset)) + return true; + if(scroll_to_substring_within(t, key, 0, offset + strlen(key)-1)) + return true; + return false; +} + +void vtext_set_source(vtext *t, char const *data, int size) +{ + t->data = data; + t->size = size; + t->scroll = 0; + t->horiz_scroll = 0; + update_after_content_change(t); + t->widget.update = true; +} + +// Trivial properties // + +void vtext_set_font(vtext *t, font_t const *font) +{ + t->font = font ? font : dfont_default(); + update_after_layout(t); + update_after_content_change(t); + shake(t); + t->widget.update = true; +} + +void vtext_set_line_spacing(vtext *t, int line_spacing) +{ + t->line_spacing = line_spacing; + t->widget.update = true; + update_after_layout(t); + shake(t); +} + +void vtext_set_word_wrapping(vtext *t, vtext_wrap word_wrapping) +{ + t->wrap = word_wrapping; + update_after_content_change(t); + shake(t); + t->widget.update = true; +} + +// Polymorphic widget operations // + +static void vtext_poly_csize(void *t0) +{ + vtext *t = t0; + jwidget *w = &t->widget; + + w->w = 128; + w->h = 64; +} + +static void vtext_poly_layout(void *t0) +{ + vtext *t = t0; + update_after_layout(t); + shake(t); +} + +static void vtext_poly_render(void *t0, int x, int y) +{ + vtext *t = t0; + font_t const *old_font = dfont(t->font); + + if(!t->data) { + dtext(x, y, C_BLACK, "(No file opened)"); + dfont(old_font); + return; + } + + int line_height = t->font->line_height + t->line_spacing; + + struct dwindow w = { + .left = x, + .top = y, + .right = x + jwidget_content_width(t) - 4, + .bottom = y + jwidget_content_height(t), + }; + struct dwindow oldw = dwindow_set(w); + + for(int i = 0; i < t->visible_lines; i++) { + int line = t->scroll + i; + int line_y = y + line_height * i; + + if(line >= t->virtual_lines) + continue; + + char const *curr_line = t->data + t->vline_offsets[line]; + char const *end_line = (line + 1 >= t->virtual_lines) + ? t->data + t->size + : t->data + t->vline_offsets[line + 1]; + + dtext_opt(x - t->horiz_scroll, line_y, + C_BLACK, C_NONE, DTEXT_LEFT, DTEXT_TOP, + curr_line, end_line - curr_line); + } + + dwindow_set(oldw); + + if(t->visible_lines < t->virtual_lines) { + int h_total = jwidget_content_height(t); + int h_bar = (t->visible_lines * h_total) / t->virtual_lines; + int y_bar = (t->scroll * h_total) / t->virtual_lines; + int x_bar = jwidget_content_width(t) - 2; + + drect(x+x_bar, y+y_bar, x+x_bar+1, y+y_bar+h_bar-1, C_BLACK); + } + + dfont(old_font); +} + +static bool vtext_poly_event(void *t0, jevent ev) +{ + vtext *t = t0; + if(ev.type != JSCENE_KEY || ev.key.type == KEYEV_UP) + return false; + + // Movement // + + int key = ev.key.key; + int move_speed = (ev.key.alpha ? t->visible_lines : 1); + + if(key == KEY_UP && ev.key.shift) { + vtext_scroll_to(t, 0); + return true; + } + else if(key == KEY_UP) { + vtext_scroll_to(t, t->scroll - move_speed); + return true; + } + + if(key == KEY_DOWN && ev.key.shift) { + vtext_scroll_to(t, t->virtual_lines /* auto adjust */); + return true; + } + else if(key == KEY_DOWN) { + vtext_scroll_to(t, t->scroll + move_speed); + return true; + } + + if(key == KEY_RIGHT && t->wrap == VTEXT_WRAP_NONE) { + t->horiz_scroll += 32; + t->widget.update = true; + return true; + } + else if(key == KEY_LEFT && t->wrap == VTEXT_WRAP_NONE) { + t->horiz_scroll = max(0, t->horiz_scroll - 32); + t->widget.update = true; + return true; + } + + return false; +} + +/* vtext type definiton */ +static jwidget_poly type_vtext = { + .name = "vtext", + .csize = vtext_poly_csize, + .layout = vtext_poly_layout, + .render = vtext_poly_render, + .event = vtext_poly_event, +}; + +__attribute__((constructor(1004))) +static void j_register_vtext(void) +{ + vtext_type_id = j_register_widget(&type_vtext, "jwidget"); + VTEXT_CHANGED = j_register_event(); +} diff --git a/src/vtext.h b/src/vtext.h new file mode 100644 index 0000000..266a9ca --- /dev/null +++ b/src/vtext.h @@ -0,0 +1,67 @@ +// vtext: A text visualization widget +// +// This is a trivial widget, just displays line-by-line with or without line +// wrapping. + +#pragma once +#include +#include +#include + +/* Wrapping modes */ +typedef enum { + VTEXT_WRAP_NONE, + VTEXT_WRAP_CHAR, + VTEXT_WRAP_WORD, + +} vtext_wrap; + +/* vtext: Text visualization widget */ +typedef struct { + jwidget widget; + + /* Current data block and size */ + char const *data; + int size; + /* Word wrapping mode */ + vtext_wrap wrap; + /* Current scroll offset, in bytes */ + int scroll; + /* Current horizontal offset, in pixels */ + int horiz_scroll; + + /* Number of virtual lines (depends on wrapping mode) */ + int virtual_lines; + /* Maximum scroll */ + int max_scroll; + /* Virtual line offsets */ + uint32_t *vline_offsets; + + /* Number of visible lines */ + int8_t visible_lines; + /* Additional spacing between lines, in pixels */ + int8_t line_spacing; + /* Rendering font */ + font_t const *font; + +} vtext; + +/* Type IDs */ +extern uint16_t VTEXT_CHANGED; + +/* vtext_create: Create a new text visualizer */ +vtext *vtext_create(jwidget *parent); + +/* Set a new data source. The viewer does not own the data. */ +void vtext_set_source(vtext *t, char const *data, int size); + +/* Scroll to a given (top) virtual line position. */ +bool vtext_scroll_to(vtext *t, int top_vline); + +/* Search a string and go to the next occurrence. */ +bool vtext_scroll_to_substring(vtext *t, char const *substring); + +/* Trivial properties */ +void vtext_set_font(vtext *e, font_t const *font); +void vtext_set_line_spacing(vtext *e, int line_spacing); +void vtext_set_word_wrapping(vtext *e, vtext_wrap word_wrapping);