From 71080476b14117d052d20acdd2e6e8fdcf814948 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 2 Nov 2023 11:25:53 +0100 Subject: [PATCH 001/250] just a tree --- .gitignore | 3 +- RENDERER | 4 - def_style.h | 40 - font-to-atlas/.gitignore | 3 - font-to-atlas/Makefile | 2 - font-to-atlas/ff.c | 118 - font-to-atlas/ff.h | 25 - font-to-atlas/main.c | 107 - font-to-atlas/monospace.ttf | Bin 309408 -> 0 bytes font-to-atlas/stb_truetype.h | 5077 ----------------- .../generic_hash.h => generic_hash.h | 3 + .../generic_stack.h => generic_stack.h | 0 input.c | 40 - main.c | 214 + opengl-ren/.gitignore | 2 - opengl-ren/Makefile | 5 - opengl-ren/README | 2 - opengl-ren/charmap.ff | Bin 65552 -> 0 bytes opengl-ren/charmap.png | Bin 862 -> 0 bytes opengl-ren/fragment.glsl | 12 - opengl-ren/main.c | 521 -- opengl-ren/vertex.glsl | 17 - opengl/.gitignore | 2 - opengl/Makefile | 5 - opengl/README | 2 - opengl/fragment.glsl | 10 - opengl/main.c | 278 - opengl/vertex.glsl | 14 - rewrite/generic_stack.h | 126 - test/Makefile | 11 - test/main.c | 268 - test/monospace.ttf | Bin 309408 -> 0 bytes test/stb_rect_pack.h | 623 -- test/stb_truetype.h | 5077 ----------------- test/stbttf.h | 212 - text_rendering/.gitignore | 4 - text_rendering/Makefile | 18 - text_rendering/box_fragshader.glsl | 8 - text_rendering/box_vertshader.glsl | 19 - text_rendering/font.c | 195 - text_rendering/font.h | 50 - text_rendering/font_fragshader.glsl | 18 - text_rendering/font_vertshader.glsl | 26 - text_rendering/generic_cache.h | 126 - text_rendering/genmake.sh | 46 - text_rendering/main.c | 96 - text_rendering/ren.c | 826 --- text_rendering/ren.h | 54 - text_rendering/stb_image_write.h | 1724 ------ text_rendering/stb_truetype.h | 5077 ----------------- text_rendering/util.c | 126 - text_rendering/util.h | 34 - ugui.c | 1461 ----- ugui.h | 285 - 54 files changed, 219 insertions(+), 22797 deletions(-) delete mode 100644 RENDERER delete mode 100644 def_style.h delete mode 100644 font-to-atlas/.gitignore delete mode 100644 font-to-atlas/Makefile delete mode 100644 font-to-atlas/ff.c delete mode 100644 font-to-atlas/ff.h delete mode 100644 font-to-atlas/main.c delete mode 100644 font-to-atlas/monospace.ttf delete mode 100644 font-to-atlas/stb_truetype.h rename text_rendering/generic_hash.h => generic_hash.h (98%) rename text_rendering/generic_stack.h => generic_stack.h (100%) delete mode 100644 input.c create mode 100755 main.c delete mode 100644 opengl-ren/.gitignore delete mode 100644 opengl-ren/Makefile delete mode 100644 opengl-ren/README delete mode 100644 opengl-ren/charmap.ff delete mode 100644 opengl-ren/charmap.png delete mode 100644 opengl-ren/fragment.glsl delete mode 100644 opengl-ren/main.c delete mode 100644 opengl-ren/vertex.glsl delete mode 100644 opengl/.gitignore delete mode 100644 opengl/Makefile delete mode 100644 opengl/README delete mode 100644 opengl/fragment.glsl delete mode 100644 opengl/main.c delete mode 100644 opengl/vertex.glsl delete mode 100644 rewrite/generic_stack.h delete mode 100644 test/Makefile delete mode 100644 test/main.c delete mode 100644 test/monospace.ttf delete mode 100644 test/stb_rect_pack.h delete mode 100644 test/stb_truetype.h delete mode 100644 test/stbttf.h delete mode 100644 text_rendering/.gitignore delete mode 100644 text_rendering/Makefile delete mode 100644 text_rendering/box_fragshader.glsl delete mode 100644 text_rendering/box_vertshader.glsl delete mode 100644 text_rendering/font.c delete mode 100644 text_rendering/font.h delete mode 100644 text_rendering/font_fragshader.glsl delete mode 100644 text_rendering/font_vertshader.glsl delete mode 100644 text_rendering/generic_cache.h delete mode 100755 text_rendering/genmake.sh delete mode 100644 text_rendering/main.c delete mode 100644 text_rendering/ren.c delete mode 100644 text_rendering/ren.h delete mode 100644 text_rendering/stb_image_write.h delete mode 100644 text_rendering/stb_truetype.h delete mode 100644 text_rendering/util.c delete mode 100644 text_rendering/util.h delete mode 100644 ugui.c delete mode 100644 ugui.h diff --git a/.gitignore b/.gitignore index 38e4604..6320a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ microgui *.o test/test **/compile_commands.json -**/.cache \ No newline at end of file +**/.cache +test diff --git a/RENDERER b/RENDERER deleted file mode 100644 index 18def58..0000000 --- a/RENDERER +++ /dev/null @@ -1,4 +0,0 @@ -Divide up the OpenGL renderer into three parts, one part that draws simple shapes, -another that only draws text and one that is responsible for drawing icons and -mapping sprites in general, this way all the texture atlases are separate and -everything is done within 3 draw calls \ No newline at end of file diff --git a/def_style.h b/def_style.h deleted file mode 100644 index cd5f112..0000000 --- a/def_style.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef _UG_DEF_STYLE_H -#define _UG_DEF_STYLE_H - -#include "ugui.h" - -#define SZ_INT(x) x.size.i - -static const ug_style_t default_style = { - .color = { - .bg = RGB_FORMAT(0x131313), - .fg = RGB_FORMAT(0xffffff), - }, - .margin = SIZE_PX(3), - .border = { - .color = RGB_FORMAT(0xf50a00), - .size = SIZE_PX(2), - }, - .title = { - .color = { - .bg = RGB_FORMAT(0xbbbbbb), - .fg = RGB_FORMAT(0xffff00), - }, - .height = SIZE_PX(20), - .font_size = SIZE_PX(14), - }, - .btn = { - .color = { - .act = RGB_FORMAT(0x440044), - .bg = RGB_FORMAT(0x006600), - .fg = RGB_FORMAT(0xffff00), - .sel = RGB_FORMAT(0x0a0aff), - .br = RGB_FORMAT(0xff00ff), - }, - .font_size = SIZE_PX(10), - .border = SIZE_PX(5), - }, -}; - - -#endif diff --git a/font-to-atlas/.gitignore b/font-to-atlas/.gitignore deleted file mode 100644 index 4ff094b..0000000 --- a/font-to-atlas/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.ff -*.png -font-to-atlas \ No newline at end of file diff --git a/font-to-atlas/Makefile b/font-to-atlas/Makefile deleted file mode 100644 index 17fed17..0000000 --- a/font-to-atlas/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -font-to-atlas: main.c ff.c ff.h - cc -lm -g main.c ff.c -o font-to-atlas diff --git a/font-to-atlas/ff.c b/font-to-atlas/ff.c deleted file mode 100644 index c05c2cc..0000000 --- a/font-to-atlas/ff.c +++ /dev/null @@ -1,118 +0,0 @@ -#include -#include -#include -#include - -#include - -#include "ff.h" - - -#define MAX(a,b) a>b?a:b - - -struct ff * ff_new(uint32_t width, uint32_t height) -{ - uint64_t size = (uint64_t)width*height*sizeof(struct ff); - struct ff *image = malloc(sizeof(struct ff) + size); - if (!image) - return NULL; - memcpy(image->magic, "farbfeld", 8); - image->width = htonl(width); - image->height = htonl(height); - - // create a transparent image - memset(image->pixels, 0, size); - - return image; -} - - -uint64_t ff_bytes(const struct ff *image) -{ - if (!image) - return 0; - return sizeof(struct ff)+(uint64_t)ntohl(image->width)*ntohl(image->height)*sizeof(struct ff_px); -} - - -struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height) -{ - struct ff *new = NULL; - int64_t size = sizeof(struct ff)+(int64_t)width*height*sizeof(uint64_t); - if (image) { - int64_t old_size = ff_bytes(image); - if (old_size == size) - return image; - - new = realloc(image, size); - if (!new) - return NULL; - - uint32_t old_width = ntohl(new->width); - uint32_t old_height = ntohl(new->height); - if (size-old_size > 0) { - struct ff_px *b = new->pixels; - memset(&b[old_width*old_height], 0, size-old_size); - for (int64_t c = (int64_t)old_height-1; c >= 0; c--) { - memmove(&b[width*c], &b[old_width*c], sizeof(struct ff_px)*old_width); - memset(&b[c*old_width], 0, sizeof(struct ff_px)*(c*width-c*old_width)); - } - } else { - } - new->height = htonl(height); - new->width = htonl(width); - - } else { - new = ff_new(width, height); - } - - return new; -} - - -int ff_verify (const struct ff *image) -{ - if (!image || strncmp("farbfeld", (char*)image->magic, 8)) - return 1; - return 0; -} - - -int ff_free(struct ff *image) -{ - if (!ff_verify(image)) - free(image); - return 0; -} - - -// overlays the bitmap containing only 1 8bpp channel to the image starting at (x,y) -// be stands for the data is already big endian -int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h) -{ - if (!image || !*image) - return 1; - - uint32_t iw = ntohl((*image)->width), ih = ntohl((*image)->height); - *image = ff_resize(*image, MAX(iw, x+w), MAX(ih, y+h)); - if (!image) - return -1; - iw = ntohl((*image)->width); - ih = ntohl((*image)->height); - - for (uint32_t r = 0; r < h; r++) { - for (uint32_t c = 0; c < w; c++) { - uint8_t col = bitmap[r*w+c]; - struct ff_px p = { - .r = 0xffff, - .g = 0xffff, - .b = 0xffff, - .a = htons(257*col) - }; - (*image)->pixels[(r+y)*iw + (c+x)] = p; - } - } - - return 0; -} diff --git a/font-to-atlas/ff.h b/font-to-atlas/ff.h deleted file mode 100644 index 5c211cc..0000000 --- a/font-to-atlas/ff.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef _FARBFELD_EZ_H -#define _FARBFELD_EZ_H - - -#include - - -struct __attribute__((packed)) ff_px { uint16_t r, g, b, a; }; - -struct __attribute__((packed)) ff { - int8_t magic[8]; - uint32_t width, height; - struct ff_px pixels[]; -}; - - -struct ff * ff_new(uint32_t width, uint32_t height); -int ff_verify (const struct ff *image); -int ff_free(struct ff *image); -uint64_t ff_bytes(const struct ff *image); -struct ff * ff_resize(struct ff *image, uint32_t width, uint32_t height); -int ff_overlay_8r(struct ff **image, const uint8_t *bitmap, uint32_t x, uint32_t y, uint32_t w, uint32_t h); - - -#endif diff --git a/font-to-atlas/main.c b/font-to-atlas/main.c deleted file mode 100644 index cbad4bd..0000000 --- a/font-to-atlas/main.c +++ /dev/null @@ -1,107 +0,0 @@ -#define _POSIX_C_SOURCE 200809l -#define STB_TRUETYPE_IMPLEMENTATION -#define STBTT_STATIC - -#include - -#include -#include -#include -#include -#include - -#include "stb_truetype.h" -#include "ff.h" - - -const int font_size = 32; - - -void map_file(const unsigned char **str, int *size, const char *path) -{ - if (!path) - err(EXIT_FAILURE, "NULL filename"); - FILE *fp = fopen(path, "r"); - if (!fp) - err(EXIT_FAILURE, "Cannot open file %s", path); - *size = lseek(fileno(fp), 0, SEEK_END); - if (*size == (off_t)-1) - err(EXIT_FAILURE, "lseek failed"); - *str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); - if (*str == (void*)-1) - err(EXIT_FAILURE, "mmap failed"); - if (fclose(fp)) - err(EXIT_FAILURE, "Error closing file"); -} - - -int main(int argc, char *argv[]) -{ - if (argc < 2) - return EXIT_FAILURE; - int len; - const unsigned char *map; - map_file(&map, &len, argv[1]); - - stbtt_fontinfo font; - stbtt_InitFont(&font, map, stbtt_GetFontOffsetForIndex(map, 0)); - - // all this is to get the font bounding box in pixels - float font_scale = 1.0; - font_scale = stbtt_ScaleForPixelHeight(&font, font_size); - int ascent, descent, linegap; - stbtt_GetFontVMetrics(&font, &ascent, &descent, &linegap); - int x0,y0,x1,y1; - int bound_w, bound_h; - stbtt_GetFontBoundingBox(&font, &x0, &y0, &x1, &y1); - - printf("font_scale: %f\n", font_scale); - printf("x0:%d y0:%d x1:%d y1:%d\n",x0,y0,x1,y1); - - int baseline = font_scale * -y0; - bound_h = (baseline+font_scale*y1) - (baseline+font_scale*y0); - bound_w = (font_scale*x1) - (font_scale*x0); - baseline = bound_h - baseline; - unsigned char *bitmap = malloc(bound_h*bound_w); - if (!bitmap) - err(EXIT_FAILURE, "Cannot allocate bitmap"); - - printf("bounding h:%d w:%d\n", bound_h, bound_w); - printf("baseline: %d\n", baseline); - - struct ff *image = ff_new(0, 0); - - // get all ascii - int x = 0, y = 0, maxwidth = 64*bound_w; - for (unsigned int i = 0; i <= 0x7F; i++) { - int x0,y0,x1,y1,w,h,l,a, ox,oy; - int g = stbtt_FindGlyphIndex(&font, i); - - stbtt_GetGlyphBitmapBoxSubpixel(&font, g, font_scale, font_scale, 0, 0, &x0, &y0, &x1, &y1); - w = x1 - x0; - h = y1 - y0; - //printf("%d\n", y0); - stbtt_GetGlyphHMetrics(&font, g, &a, &l); - stbtt_MakeGlyphBitmapSubpixel(&font, bitmap, w, h, w, font_scale, font_scale, 0, 0, g); - //printf("'%c' -> l*scale:%.0f, y0:%d\n", i, font_scale*l, bound_h+y0); - ox = font_scale*l; - oy = bound_h+y0; - ff_overlay_8r(&image, bitmap, x+ox, y+oy, w, h); - - x += bound_w; - if (x >= maxwidth) y += bound_h; - x %= maxwidth; - - } - - FILE *fp = fopen("out.ff", "w"); - if (fp) { - fwrite(image, 1, ff_bytes(image), fp); - fclose(fp); - } - - free(bitmap); - ff_free(image); - munmap((void *)map, len); - return 0; -} diff --git a/font-to-atlas/monospace.ttf b/font-to-atlas/monospace.ttf deleted file mode 100644 index 92a90cb06e0535afa79f6cba26ceece15a7f7959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309408 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

lipX z89%t)L1Zn7w#;-}USgg#E*&nk6g|(G1Qmus3tNsjIhevgp16@5CCac&oFQ%eMM}nZ z{mnz>s34v3aMz*q^t7z0vk!G1GW!Jt2ASxcw2bWTp8MFWsimbQCDW&tl$Dk-0g1n8B}ozJZoF0r$e^J`a*sp<@okRTsO%H2(}g3TTkA_r zY_P>jVzWUR3KUay?+f%NC1?ezWXd19?3;y;D2) z=Jd@SxaBCD7@yRdoI1@I*B<-wy!p$P-g4A5y{O1h(7URyq%^-^5%4=S<)}GLXGT&? zP*KLzTkhzZ9`LuniN0E|5dVh^Rxh7GQJxUnGxL_)=k$BaC)IDL^7E%p&b9}+_DF?A zGiGGlx&2!bzLjrRQ-8rQ!6d{9w;c=hLS+CVTojp^4u~Lz7RS90h8bLbuiryo}eM%KoVvP^fjleHU^+=q5A;2uIP{IMn8hjx?CUO%x=LY*K$e zUzje{LKq99L~=Fia#OW;19`##VP1~Mrtvh>B0Gh6=r^B#@tfaImET5%z7r=71y6sR z1)+KIKbbhmHDqK(Opkw#A(lJ1ckkf2b32FbV2>ybjO9me1|^F}-I933OjHUpo!ybB za2SP5ev0ZSLtRNH0zA}Iy~GRPK;d0)CZZjOFGZkHpcnj=aBX`konu{0C@R1fXWJ6u zt>zd5de9(qgvu{hvPyqiP=X*?9QK49o6V7vmy2IXc%isb1#ifGsWofO;PWB)4-5)1 z;7{NZNdd%*kTmtr&*fqJ!&%FTzlevj)`WLeZu_9MsiDqXpU^aM&*aJB?87X%2GSC~ z2D0Q+hRKs}Y;8)eG1oRU8MQ2k#V^zZ`IMHcL&f>K|5&>xBqaImm<(OqoH=jI_YU>b z>SAO4B%e^+h{dN=?PS~(gl+~lg! zO4x6Syg#q!XWP@GGQ%@7XD^FdKFsu40i{{Jdo!~$as#PyNTj_fx=|f«q(At`*`NbkV(;WAZRz)Jn7&p-cM>U1p@ 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 D9ti__ diff --git a/font-to-atlas/stb_truetype.h b/font-to-atlas/stb_truetype.h deleted file mode 100644 index bbf2284..0000000 --- a/font-to-atlas/stb_truetype.h +++ /dev/null @@ -1,5077 +0,0 @@ -// stb_truetype.h - v1.26 - public domain -// authored from 2009-2021 by Sean Barrett / RAD Game Tools -// -// ======================================================================= -// -// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES -// -// This library does no range checking of the offsets found in the file, -// meaning an attacker can use it to read arbitrary memory. -// -// ======================================================================= -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe -// Cass Everitt Martins Mozeiko github:aloucks -// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam -// Brian Hook Omar Cornut github:vassvik -// Walter van Niftrik Ryan Griege -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. Brian Costabile -// Ken Voskuil (kaesve) -// -// VERSION HISTORY -// -// 1.26 (2021-08-28) fix broken rasterizer -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places need to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct -{ - unsigned char *data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); -// Query the font vertical metrics without having to create a font first. - - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; - float xoff2,yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct -{ - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar *chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); -// If skip != 0, this tells stb_truetype to skip any codepoints for which -// there is no corresponding glyph. If skip=0, which is the default, then -// codepoints without a glyph recived the font's "missing character" glyph, -// typically an empty box by convention. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void *user_allocator_context; - void *pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - int skip_missing; - unsigned int h_oversample, v_oversample; - unsigned char *pixels; - void *nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publicly so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. -// Returns 0 if the character codepoint is not defined in the font. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - -typedef struct stbtt_kerningentry -{ - int glyph1; // use stbtt_FindGlyphIndex - int glyph2; - int advance; -} stbtt_kerningentry; - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); -// Retrieves a complete list of all of the kerning pairs provided by the font -// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. -// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy,cx1,cy1; - unsigned char type,padding; - } stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of contours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); -// fills svg with the character's SVG data. -// returns data size or 0 if SVG not found. - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex *vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void *userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap -// glyph/codepoint -- the character to generate the SDF for -// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) -// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - - - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf *b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf *b, int o) -{ - stbtt__buf_seek(b, b->cursor + o); -} - -static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void *p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*) p; - r.size = (int) size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) return b0 - 139; - else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) return stbtt__buf_get16(b); - else if (b0 == 29) return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf *b) { - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) op = stbtt__buf_get8(b) | 0x100; - if (op == key) return stbtt__buf_range(b, start, end-start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf *b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i*offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - return ttLONG(font_collection+8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1]+subrsoff); - return stbtt__cff_get_index(&cff); -} - -// since most people won't use this, find this table the first time it's needed -static int stbtt__get_svg(stbtt_fontinfo *info) -{ - stbtt_uint32 t; - if (info->svg < 0) { - t = stbtt__find_table(info->data, info->fontstart, "SVG "); - if (t) { - stbtt_uint32 offset = ttULONG(info->data + t + 2); - info->svg = t + offset; - } else { - info->svg = 0; - } - } - return info->svg; -} - -static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data+cff, 512*1024*1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) return 0; - if (charstrings == 0) return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - info->svg = -1; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start, last; - stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); - - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - last = ttUSHORT(data + endCount + 2*item); - if (unicode_codepoint < start || unicode_codepoint > last) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours < 0) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct -{ - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex *pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} - -static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) c->max_x = x; - if (y > c->max_y || !c->started) c->max_y = y; - if (x < c->min_x || !c->started) c->min_x = x; - if (y < c->min_y || !c->started) c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx *ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp-1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x18: // rcurveline - if (sp < 8) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { f = s[i]; i++; } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // FALLTHROUGH - case 0x1D: // callgsubr - if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); - v = (int) s[--sp]; - if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - //fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1+dx2+dx3+dx4+dx5; - dy = dy1+dy2+dy3+dy4+dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && b0 < 32) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) *x0 = r ? c.min_x : 0; - if (y0) *y0 = r ? c.min_y : 0; - if (x1) *x1 = r ? c.max_x : 0; - if (y1) *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) -{ - stbtt_uint8 *data = info->data + info->kern; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - return ttUSHORT(data+10); -} - -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) -{ - stbtt_uint8 *data = info->data + info->kern; - int k, length; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - length = ttUSHORT(data+10); - if (table_length < length) - length = table_length; - - for (k = 0; k < length; k++) - { - table[k].glyph1 = ttUSHORT(data+18+(k*6)); - table[k].glyph2 = ttUSHORT(data+20+(k*6)); - table[k].advance = ttSHORT(data+22+(k*6)); - } - - return length; -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch (coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l=0, r=glyphCount-1, m; - int straw, needle=glyph; - while (l <= r) { - stbtt_uint8 *glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - break; - } - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8 *rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l=0, r=rangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - break; - } - - default: return -1; // unsupported - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch (classDefFormat) - { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8 *classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - break; - } - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8 *classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l=0, r=classRangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - break; - } - - default: - return -1; // Unsupported definition type, return an error. - } - - // "All glyphs not assigned to a class fall into class 0". (OpenType spec) - return 0; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8 *lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8 *data; - stbtt_int32 i, sti; - - if (!info->gpos) return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data+0) != 1) return 0; // Major version 1 - if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data+8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i=0; i= pairSetCount) return 0; - - needle=glyph2; - r=pairValueCount-1; - l=0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8 *pairValue; - m = (l + r) >> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } else - return 0; - break; - } - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - stbtt_uint8 *class1Records, *class2Records; - stbtt_int16 xAdvance; - - if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed - if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed - - class1Records = table + 16; - class2Records = class1Records + 2 * (glyph1class * class2Count); - xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } else - return 0; - break; - } - - default: - return 0; // Unsupported position format - } - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - else if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); - if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); - if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) -{ - int i; - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); - - int numEntries = ttUSHORT(svg_doc_list); - stbtt_uint8 *svg_docs = svg_doc_list + 2; - - for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) - return svg_doc; - } - return 0; -} - -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) -{ - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc; - - if (info->svg == 0) - return 0; - - svg_doc = stbtt_FindSVGDoc(info, gl); - if (svg_doc != NULL) { - *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); - return ttULONG(svg_doc + 8); - } else { - return 0; - } -} - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) -{ - return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { - // e.g. space character - if (ix0) *ix0 = 0; - if (iy0) *iy0 = 0; - if (ix1) *ix1 = 0; - if (iy1) *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); - if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); - if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk -{ - struct stbtt__hheap_chunk *next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap -{ - struct stbtt__hheap_chunk *head; - void *first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) -{ - if (hh->first_free) { - void *p = hh->first_free; - hh->first_free = * (void **) p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap *hh, void *p) -{ - *(void **) p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) -{ - stbtt__hheap_chunk *c = hh->head; - while (c) { - stbtt__hheap_chunk *n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - - -typedef struct stbtt__active_edge -{ - struct stbtt__active_edge *next; - #if STBTT_RASTERIZER_VERSION==1 - int x,dx; - float ey; - int direction; - #elif STBTT_RASTERIZER_VERSION==2 - float fx,fdx,fdy; - float direction; - float sy; - float ey; - #else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" - #endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX-1) - -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - //STBTT_assert(e->y0 <= start_point); - if (!z) return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->direction; - } else { - int x1 = e->x; w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) -{ - if (y0 == y1) return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) return; - if (y1 < e->sy) return; - if (y0 < e->sy) { - x0 += (x1-x0) * (e->sy - y0) / (y1-y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1-x0) * (e->ey - y1) / (y1-y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x+1); - else if (x0 == x+1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x+1) - STBTT_assert(x1 >= x+1); - else - STBTT_assert(x1 >= x && x1 <= x+1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1-y0); - else if (x0 >= x+1 && x1 >= x+1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); - scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position - } -} - -static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) -{ - STBTT_assert(top_width >= 0); - STBTT_assert(bottom_width >= 0); - return (top_width + bottom_width) / 2.0f * height; -} - -static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) -{ - return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); -} - -static float stbtt__sized_triangle_area(float height, float width) -{ - return height * width / 2; -} - -static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) -{ - float y_bottom = y_top+1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); - stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0,sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int) x_top == (int) x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int) x_top; - height = (sy1 - sy0) * e->direction; - STBTT_assert(x >= 0 && x < len); - scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); - scanline_fill[x] += height; // everything right of this pixel is filled - } else { - int x,x1,x2; - float y_crossing, y_final, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - STBTT_assert(dy >= 0); - STBTT_assert(dx >= 0); - - x1 = (int) x_top; - x2 = (int) x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = y_top + dy * (x1+1 - x0); - - // compute intersection with y axis at x2 - y_final = y_top + dy * (x2 - x0); - - // x1 x_top x2 x_bottom - // y_top +------|-----+------------+------------+--------|---+------------+ - // | | | | | | - // | | | | | | - // sy0 | Txxxxx|............|............|............|............| - // y_crossing | *xxxxx.......|............|............|............| - // | | xxxxx..|............|............|............| - // | | /- xx*xxxx........|............|............| - // | | dy < | xxxxxx..|............|............| - // y_final | | \- | xx*xxx.........|............| - // sy1 | | | | xxxxxB...|............| - // | | | | | | - // | | | | | | - // y_bottom +------------+------------+------------+------------+------------+ - // - // goal is to measure the area covered by '.' in each pixel - - // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 - // @TODO: maybe test against sy1 rather than y_bottom? - if (y_crossing > y_bottom) - y_crossing = y_bottom; - - sign = e->direction; - - // area of the rectangle covered from sy0..y_crossing - area = sign * (y_crossing-sy0); - - // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) - scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); - - // check if final y_crossing is blown up; no test case for this - if (y_final > y_bottom) { - y_final = y_bottom; - dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom - } - - // in second pixel, area covered by line segment found in first pixel - // is always a rectangle 1 wide * the height of that line segment; this - // is exactly what the variable 'area' stores. it also gets a contribution - // from the line segment within it. the THIRD pixel will get the first - // pixel's rectangle contribution, the second pixel's rectangle contribution, - // and its own contribution. the 'own contribution' is the same in every pixel except - // the leftmost and rightmost, a trapezoid that slides down in each pixel. - // the second pixel's contribution to the third pixel will be the - // rectangle 1 wide times the height change in the second pixel, which is dy. - - step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, - // which multiplied by 1-pixel-width is how much pixel area changes for each step in x - // so the area advances by 'step' every time - - for (x = x1+1; x < x2; ++x) { - scanline[x] += area + step/2; // area of trapezoid is 1*step/2 - area += step; - } - STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down - STBTT_assert(sy1 > y_final-0.01f); - - // area covered in the last pixel is the rectangle from all the pixels to the left, - // plus the trapezoid filled by the line segment in this pixel all the way to the right edge - scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); - - // the rest of the line is filled based on the total height of the line segment in this pixel - scanline_fill[x2] += sign * (sy1-sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - // note though that this does happen some of the time because - // x_top and x_bottom can be extrapolated at the top & bottom of - // the shape and actually lie outside the bounding box - int x; - for (x=0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float) (x); - float x2 = (float) (x+1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x+1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float) (off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge **step = &active; - - STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - if (j == 0 && off_y != 0) { - if (z->ey < scan_y_top) { - // this can happen due to subpixel positioning and some kind of fp rounding error i think - z->ey = scan_y_top; - } - } - STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i=0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float) STBTT_fabs(k)*255 + 0.5f; - m = (int) k; - if (m > 255) m = 255; - result->pixels[j*result->stride + i] = (unsigned char) m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge *z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) -{ - int i,j; - for (i=1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge *b = &p[j-1]; - int c = STBTT__COMPARE(a,b); - if (!c) break; - p[j] = p[j-1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) -{ - /* threshold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01,c12,c,m,i,j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0],&p[m]); - c12 = STBTT__COMPARE(&p[m],&p[n-1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0],&p[n-1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n-1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i=1; - j=n-1; - for(;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;;++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) break; - } - for (;;--j) { - if (!STBTT__COMPARE(&p[0], &p[j])) break; - } - /* make sure we haven't crossed */ - if (i >= j) break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n-i)) { - stbtt__sort_edges_quicksort(p,j); - p = p+i; - n = n-i; - } else { - stbtt__sort_edges_quicksort(p+i, n-i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge *p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1-x0; - float dy0 = y1-y0; - float dx1 = x2-x1; - float dy1 = y2-y1; - float dx2 = x3-x2; - float dy2 = y3-y2; - float dx = x3-x0; - float dy = y3-y0; - float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); - float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); - float flatness_squared = longlen*longlen-shortlen*shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0+x1)/2; - float y01 = (y0+y1)/2; - float x12 = (x1+x2)/2; - float y12 = (y1+y2)/2; - float x23 = (x2+x3)/2; - float y23 = (y2+y3)/2; - - float xa = (x01+x12)/2; - float ya = (y01+y12)/2; - float xb = (x12+x23)/2; - float yb = (y12+y23)/2; - - float mx = (xa+xb)/2; - float my = (ya+yb)/2; - - stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x3,y3); - *num_points = *num_points+1; - } -} - -// returns number of contours -static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].cx1, vertices[i].cy1, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int *winding_lengths = NULL; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 1; - if (y+gh+1 > bottom_y) - bottom_y = y+gh+1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - int width,height; - int x,y,bottom_y; -} stbrp_context; - -typedef struct -{ - unsigned char x; -} stbrp_node; - -struct stbrp_rect -{ - stbrp_coord x,y; - int id,w,h,was_packed; -}; - -static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) -{ - int i; - for (i=0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for ( ; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) -{ - stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); - int num_nodes = pw - padding; - stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) STBTT_free(context, alloc_context); - if (nodes != NULL) STBTT_free(nodes , alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - spc->skip_missing = 0; - - stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) -{ - STBTT_free(spc->nodes , spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) -{ - spc->skip_missing = skip; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) - -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char) (total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i*stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k; - int missing_glyph_added = 0; - - k=0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char) spc->h_oversample; - ranges[i].v_oversample = (unsigned char) spc->v_oversample; - for (j=0; j < ranges[i].num_chars; ++j) { - int x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { - rects[k].w = rects[k].h = 0; - } else { - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); - if (glyph == 0) - missing_glyph_added = 1; - } - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, - output, - out_w - (prefilter_x - 1), - out_h - (prefilter_y - 1), - out_stride, - scale_x, - scale_y, - shift_x, - shift_y, - glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k, missing_glyph = -1, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h,recip_v,sub_x,sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j=0; j < ranges[i].num_chars; ++j) { - stbrp_rect *r = &rects[k]; - if (r->was_packed && r->w != 0 && r->h != 0) { - stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord) spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - &x0,&y0,&x1,&y1); - stbtt_MakeGlyphBitmapSubpixel(info, - spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w - spc->h_oversample+1, - r->h - spc->v_oversample+1, - spc->stride_in_bytes, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16) r->x; - bc->y0 = (stbtt_int16) r->y; - bc->x1 = (stbtt_int16) (r->x + r->w); - bc->y1 = (stbtt_int16) (r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float) x0 * recip_h + sub_x; - bc->yoff = (float) y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - - if (glyph == 0) - missing_glyph = j; - } else if (spc->skip_missing) { - return_value = 0; - } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { - ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i,j,n, return_value = 1; - //stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect *rects; - - // flag all characters as NOT packed - for (i=0; i < num_ranges; ++i) - for (j=0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = - ranges[i].chardata_for_range[j].y0 = - ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i=0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) -{ - int i_ascent, i_descent, i_lineGap; - float scale; - stbtt_fontinfo info; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); - scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); - stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); - *ascent = (float) i_ascent * scale; - *descent = (float) i_descent * scale; - *lineGap = (float) i_lineGap * scale; -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar *b = chardata + char_index; - - if (align_to_integer) { - float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) -{ - float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; - float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; - float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; - float roperp = orig[1]*ray[0] - orig[0]*ray[1]; - - float a = q0perp - 2*q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b*b - a*c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float) STBTT_sqrt(discr); - s0 = (b+d) * rcpna; - s1 = (b-d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0]*rayn_x + q0[1]*rayn_y; - float q1d = q1[0]*rayn_x + q1[1]*rayn_y; - float q2d = q2[0]*rayn_x + q2[1]*rayn_y; - float rod = orig[0]*rayn_x + orig[1]*rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; - hits[0][1] = a*s0+b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; - hits[1][1] = a*s1+b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float *a, float *b) -{ - return (a[0] == b[0] && a[1] == b[1]); -} - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - // make sure y never passes through a vertex of the shape - y_frac = (float) STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - - orig[0] = x; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i=0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; - int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; - int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; - int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; - int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); - int by = STBTT_max(y0,STBTT_max(y1,y2)); - if (y > ay && y < by && x > ax) { - float q0[2],q1[2],q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; - x1 = (int)verts[i ].x; - y1 = (int)verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot( float x ) -{ - if (x<0) - return -(float) STBTT_pow(-x,1.0f/3.0f); - else - return (float) STBTT_pow( x,1.0f/3.0f); -} - -// x^3 + a*x^2 + b*x + c = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a*a / 3; - float q = a * (2*a*a - 9*b) / 27 + c; - float p3 = p*p*p; - float d = q*q + 4*p3 / 27; - if (d >= 0) { - float z = (float) STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float) STBTT_sqrt(-p/3); - float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float) STBTT_cos(v); - float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? - //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0,iy0,ix1,iy1; - int w,h; - unsigned char *data; - - if (scale == 0) return NULL; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width ) *width = w; - if (height) *height = h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - int x,y,i,j; - float *precompute; - stbtt_vertex *verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); - precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i=0,j=num_verts-1; i < num_verts; j=i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; - float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; - float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; - float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float len2 = bx*bx + by*by; - if (len2 != 0.0f) - precompute[i] = 1.0f / (bx*bx + by*by); - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y=iy0; y < iy1; ++y) { - for (x=ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float) x + 0.5f; - float sy = (float) y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path - - for (i=0; i < num_verts; ++i) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - - if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { - float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; - - float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - // coarse culling against bbox - //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1-x0, dy = y1-y0; - float px = x0-sx, py = y0-sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px*dx + py*dy) / (dx*dx + dy*dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; - float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; - float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); - float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); - float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); - float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { - int num=0; - float ax = x1-x0, ay = y1-y0; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3] = {0.f,0.f,0.f}; - float px,py,t,it,dist2; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3*(ax*bx + ay*by); - float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); - float c = mx*ax+my*ay; - if (a == 0.0) { // if a is 0, it's linear - if (b != 0.0) { - res[num++] = -c/b; - } - } else { - float discriminant = b*b - 4*a*c; - if (discriminant < 0) - num = 0; - else { - float root = (float) STBTT_sqrt(discriminant); - res[0] = (-b - root)/(2*a); - res[1] = (-b + root)/(2*a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; - float d = (mx*ax+my*ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, - float pixel_height, unsigned char *pixels, int pw, int ph, - int first_char, int num_chars, stbtt_bakedchar *chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char *) data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char *) data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - - -// FULL VERSION HISTORY -// -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/text_rendering/generic_hash.h b/generic_hash.h similarity index 98% rename from text_rendering/generic_hash.h rename to generic_hash.h index 99e134f..1aa7601 100644 --- a/text_rendering/generic_hash.h +++ b/generic_hash.h @@ -6,6 +6,9 @@ #include +// FIXME: change the api to just one HASH_DECL to HASH_PROTO and HASH_DEFINE + + #define HASH_MAXSIZE 4096 // for fibonacci hashing, 2^{32,64}/ #define HASH_RATIO32 ((uint64_t)2654435769u) diff --git a/text_rendering/generic_stack.h b/generic_stack.h similarity index 100% rename from text_rendering/generic_stack.h rename to generic_stack.h diff --git a/input.c b/input.c deleted file mode 100644 index 4eff88b..0000000 --- a/input.c +++ /dev/null @@ -1,40 +0,0 @@ -#include "ugui.h" - -/*=============================================================================* - * Input Handling * - *=============================================================================*/ - - -#define TEST_CTX(ctx) { if (!ctx) return -1; } - - -int ug_input_mousemove(ug_ctx_t *ctx, int x, int y) -{ - TEST_CTX(ctx) - if (x < 0 || y < 0) - return 0; - - ctx->mouse.pos = (ug_vec2_t){.x = x, .y = y}; - - return 0; -} - - -int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask) -{ - TEST_CTX(ctx); - - ctx->mouse.update |= mask; - - return 0; -} - - -int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask) -{ - TEST_CTX(ctx); - - ctx->mouse.update |= mask; - - return 0; -} diff --git a/main.c b/main.c new file mode 100755 index 0000000..764be50 --- /dev/null +++ b/main.c @@ -0,0 +1,214 @@ +//#!/usr/bin/tcc -run + +#include +#include +#include + + +#define HASH_SALT (0xbabb0cac) +#define HASH_RATIO64 ((unsigned long int)11400714819322457583u) + + +struct ntree_node +{ + unsigned int id; + const char *name; + int children_no, children_size; + struct ntree_node *children, *parent; +}; + + +static char tmp_name_buffer[16] = {"node:"}; + + +unsigned int hash_str_to_uint(const char *s) +{ + unsigned int h = HASH_SALT; + const unsigned char *v = (const unsigned char *)(s); + for (int x = *s; x; x--) { + h += v[x-1]; + h += h << 10; + h ^= h >> 6; + } + h += h << 3; + h ^= h >> 11; + h += h << 15; + return h; +} + + +unsigned int hash_u32(unsigned int c) +{ + return (unsigned int)((((unsigned long int)c<<31)*HASH_RATIO64)>>32); +} + + +const char * generate_new_name(void) +{ + static int count = 0; + unsigned int h = hash_u32(count++); + snprintf(tmp_name_buffer+sizeof("node:"), 16-sizeof("node:"), "%x", h); + return tmp_name_buffer; +} + + +int tree_prune(struct ntree_node *node) +{ + if (node == NULL) + return 0; + + if ((node->children_no == 0 && node->children != NULL) || + (node->children_no != 0 && node->children == NULL)) { + printf("ERR: (%x %s) Inconsistent node children", node->id, node->name); + } else for (int i = 0; i < node->children_no; i++) { + tree_prune(&(node->children[i])); + } + + if (node->children != NULL) { + free(node->children); + } + free((void *)node->name); + if (node->parent != NULL) + node->parent->children_no--; + *node = (struct ntree_node){0}; + + return 0; +} + + +struct ntree_node * tree_append(struct ntree_node *parent, const char *name) +{ + if (name == NULL) + return NULL; + if (strlen(name) == 0) + name = generate_new_name(); + + // generate the children information + unsigned int id = hash_str_to_uint(name); + char *str = malloc(strlen(name)+1); + if (str == NULL) + return NULL; + strcpy(str, name); + + // grow the parent buffer if necessary + if (parent->children_no >= parent->children_size) { + struct ntree_node *temp = NULL; + temp = realloc(parent->children, (parent->children_size+1)*sizeof(struct ntree_node)); + if (temp == NULL) { + free(str); + return NULL; + } + + parent->children = temp; + parent->children[parent->children_size] = (struct ntree_node){0}; + parent->children_size++; + } + + // find an open spot for the child + struct ntree_node *child = NULL; + for (int i = 0; i < parent->children_size; i++) { + if (parent->children[i].id == 0) + child = &(parent->children[i]); + } + if (child == NULL) + return NULL; + + child->name = str; + child->id = id; + child->children = NULL; + child->children_no = 0; + child->parent = parent; + parent->children_no++; + //printf("append to %s, children: %d\n", parent->name, parent->children_no); + return child; +} + + +struct ntree_node * tree_find_id(struct ntree_node *root, unsigned int id) +{ + if (id == 0 || root == NULL) + return NULL; + + if (root->id == id) + return root; + + else for (int i = 0; i < root->children_size; i++) { + if (root->children[i].id != 0 && tree_find_id(&(root->children[i]), id) != NULL) + return &(root->children[i]); + } + return NULL; +} + + +// TODO: add a foreach_child function +struct ntree_node * tree_find(struct ntree_node *root, const char *name) +{ + if (name == NULL || strlen(name) == 0 || root == NULL) + return NULL; + unsigned int id = hash_str_to_uint(name); + return tree_find_id(root, id); +} + + +int tree_size(struct ntree_node *root) +{ + if (root == NULL) + return 0; + + int count = root->children_no; + if (count > 0) { + for (int i = 0; i < root->children_size; i++) { + if (root->children[i].id != 0) + count += tree_size(&(root->children[i])); + } + } + + return count; +} + + +static int tree_print_ind(struct ntree_node *node, int dd) +{ + for (int i = 0; i < dd; i++) printf(" "); + printf("[%s]\n", node->name); + for (int i = 0; i < node->children_size; i++) { + if (node->children[i].id == 0) continue; + tree_print_ind(&(node->children[i]), dd+1); + } + return node->children_no; +} + + +int tree_print(struct ntree_node *root) +{ + if (root == NULL) + return 1; + + tree_print_ind(root, 0); + + return 0; +} + + +int main(void) +{ + char *root_name = malloc(sizeof("root")); + strcpy(root_name, "root"); + struct ntree_node root = {.name = root_name}; + struct ntree_node *n; + + n = tree_append(&root, "node 0:0"); + tree_append(n, "node 0:0:0"); + tree_append(&root, "node 0:1"); + tree_append(&root, "node 0:2"); + printf("Number of nodes %d\n", tree_size(&root)); + tree_print(&root); + + tree_prune(tree_find(&root, "node 0:0")); + printf("Number of nodes %d\n", tree_size(&root)); + + tree_prune(&root); + printf("Number of nodes %d\n", tree_size(&root)); + + return 0; +} diff --git a/opengl-ren/.gitignore b/opengl-ren/.gitignore deleted file mode 100644 index aafba12..0000000 --- a/opengl-ren/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -a.out -gl \ No newline at end of file diff --git a/opengl-ren/Makefile b/opengl-ren/Makefile deleted file mode 100644 index f05bf6e..0000000 --- a/opengl-ren/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g -LDFLAGS = -lSDL2 -lGLEW -lGL - -gl: main.c - gcc ${CFLAGS} ${LDFLAGS} main.c -o gl \ No newline at end of file diff --git a/opengl-ren/README b/opengl-ren/README deleted file mode 100644 index e18e538..0000000 --- a/opengl-ren/README +++ /dev/null @@ -1,2 +0,0 @@ -since I've never worked with opengl I made this folder as a little test environment -for an SDL-OpenGL renderer \ No newline at end of file diff --git a/opengl-ren/charmap.ff b/opengl-ren/charmap.ff deleted file mode 100644 index f3c0c543f74565f61cafd7c727f63ade60ee14e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 65552 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 -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include - - -#define GLSL_VERT_SHADER "vertex.glsl" -#define GLSL_FRAG_SHADER "fragment.glsl" -#define PACKED __attribute__((packed)) - - -const int vertindex = 0; -const int colindex = 1; -const int textindex = 2; - -struct { - SDL_Window *w; - SDL_GLContext *gl; - GLuint gl_vertbuffer; - GLuint gl_program; - GLuint font_texture; -} ren = {0}; - -typedef struct PACKED { - union { GLfloat x, u; }; - union { GLfloat y, v; }; -} vec2; - -typedef struct PACKED { - union { GLfloat x, r; }; - union { GLfloat y, g; }; - union { GLfloat z, b; }; - union { GLfloat w, a; }; -} vec4; - -// a vertex has a position and a color -struct PACKED vertex { - vec2 pos; - vec2 texture; - vec4 color; -}; - - -int w_height(SDL_Window *); -int w_width(SDL_Window *); - - -// this just descrives a farbfeld image -// https://tools.suckless.org/farbfeld/ -struct PACKED _ff { - uint8_t magic[8]; - uint32_t w, h; - uint64_t bytes[]; -}; -const int gw = 7, gh = 9; -unsigned int fw, fh; - - -struct { - struct vertex *v; - int size, idx; - int prev_idx; - unsigned long int hash, prev_hash; -} vstack = {0}; - - -void grow_stack(int step) -{ - vstack.v = realloc(vstack.v, (vstack.size+step)*sizeof(*(vstack.v))); - if(!vstack.v) - err(-1, "Could not allocate stack #S: %s", strerror(errno)); - memset(&(vstack.v[vstack.size]), 0, step*sizeof(*(vstack.v))); - vstack.size += step; -} - -void push(struct vertex v) -{ - if (vstack.idx >= vstack.size) - grow_stack(6); - vstack.v[vstack.idx++] = v; -} - - -void update_hash() -{ - if (!vstack.idx) - return; - unsigned int hash = 0x5400F1B3; - unsigned char *v = (unsigned char *)vstack.v; - int size = vstack.idx; - - for (; size; size--) { - hash += v[size-1]; - hash += hash << 10; - hash ^= hash >> 6; - } - hash += hash << 3; - hash ^= hash >> 11; - hash += hash << 15; - - vstack.hash = hash; -} - - -void force_changed() -{ - vstack.prev_idx = 0; -} - - -int changed() -{ - return vstack.prev_idx != vstack.idx || vstack.prev_hash != vstack.hash; -} - - -int vstack_push_quad_c(int x, int y, int w, int h, vec4 color) -{ - // x4,y4 x3,y3 - // +-------------+ - // |(x,y) /| - // | / | - // | 2 / | - // | / | - // | / | - // | / 1 | - // |/ | - // +-------------+ - // x1,y1 x2,y2 - - int hw = w_width(ren.w)/2; - int hh = w_height(ren.w)/2; - - float x1, x2, x3, x4; - float y1, y2, y3, y4; - - x4 = x1 = (float)(x - hw) / hw; - x2 = x3 = (float)(x+w - hw) / hw; - y4 = y3 = (float)(hh - y) / hh; - y1 = y2 = (float)(hh - y-h) / hh; - - push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color }); - push((struct vertex){ .pos.x=x2, .pos.y=y2, .color=color }); - push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color }); - push((struct vertex){ .pos.x=x1, .pos.y=y1, .color=color }); - push((struct vertex){ .pos.x=x3, .pos.y=y3, .color=color }); - push((struct vertex){ .pos.x=x4, .pos.y=y4, .color=color }); - - return 0; -} - - -int vstack_push_quad_t(int x, int y, int w, int h, int u, int v) -{ - // x4,y4 x3,y3 - // +-------------+ - // |(x,y) /| - // | / | - // | 2 / | - // | / | - // | / | - // | / 1 | - // |/ | - // +-------------+ - // x1,y1 x2,y2 - - int hw = w_width(ren.w)/2; - int hh = w_height(ren.w)/2; - - float x1, x2, x3, x4; - float y1, y2, y3, y4; - - x1 = x4 = (float)(x - hw) / hw; - x2 = x3 = (float)(x+w - hw) / hw; - y1 = y2 = (float)(hh - y-h) / hh; - y3 = y4 = (float)(hh - y) / hh; - - float u1, u2, u3, u4; - float v1, v2, v3, v4; - - u1 = u4 = (float)(u) / fw; - u2 = u3 = (float)(u+gw) / fw; - v1 = v2 = (float)(v+gh) / fh; - v3 = v4 = (float)(v) / fh; - - push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 }); - push((struct vertex){ .pos.x=x2, .pos.y=y2, .texture.x=u2, .texture.y=v2 }); - push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 }); - push((struct vertex){ .pos.x=x1, .pos.y=y1, .texture.x=u1, .texture.y=v1 }); - push((struct vertex){ .pos.x=x3, .pos.y=y3, .texture.x=u3, .texture.y=v3 }); - push((struct vertex){ .pos.x=x4, .pos.y=y4, .texture.x=u4, .texture.y=v4 }); - - return 0; -} - - -void vstack_clear(void) -{ - vstack.prev_hash = vstack.hash; - vstack.prev_idx = vstack.idx; - vstack.idx = 0; -} - - -// copy the vertex buffer from system to video memory -void ren_initvertbuffer() -{ - // generate a buffer id - glGenBuffers(1, &ren.gl_vertbuffer); - // tell opengl that we want to work on that buffer - glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer); - // copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl - // that the data will be used for drawing (DRAW) and it will only be modified - // every frame (DYNAMIC) - glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW); - // set the format of each vertex, in this case each vertex is made of 4 - // coordinates, x y z w, where w is the clip coordinate, stride is - // sizeof(vec4) since every postition vertex there is a vector representing - // color data - // set the position data of the vertex buffer, and bind it as input to the - // vertex shader with an index of 0 - glEnableVertexAttribArray(vertindex); - glVertexAttribPointer(vertindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0); - // set the color data of the vertex buffer to index 1 - // vertex attribute is the OpenGL name given to a set of vertices which are - // given as input to a vertext shader, in a shader an array of vertices is - // always referred to by index and not by pointer or other manners, indices - // go from 0 to 15 - glEnableVertexAttribArray(colindex); - glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)(2*sizeof(vec2))); - // texture uv data - glEnableVertexAttribArray(textindex); - glVertexAttribPointer(textindex, 2, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec2)); - // reset the object bind so not to create errors - glBindBuffer(GL_ARRAY_BUFFER, 0); -} - - -void map_file(const char **str, int *size, const char *fname) -{ - FILE *fp = fopen(fname, "r"); - *size = lseek(fileno(fp), 0, SEEK_END); - *str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); - fclose(fp); -} - - -// print shader compilation errors -int debug_shader(GLuint shader) -{ - GLint status; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (status != GL_FALSE) - return 0; - - GLint log_length; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); - - GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar)); - glGetShaderInfoLog(shader, log_length, NULL, log_str); - - const char *shader_type_str = NULL; - GLint shader_type; - glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type); - switch(shader_type) { - case GL_VERTEX_SHADER: shader_type_str = "vertex"; break; - case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break; - case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break; - } - - fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str); - free(log_str); - return -1; -} - - -// print program compilation errors -int debug_program(GLuint prog) -{ - GLint status; - glGetProgramiv (prog, GL_LINK_STATUS, &status); - if (status != GL_FALSE) - return 0; - - GLint log_length; - glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length); - - GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar)); - glGetProgramInfoLog(prog, log_length, NULL, log_str); - fprintf(stderr, "Linker failure: %s\n", log_str); - free(log_str); - - return -1; -} - - -void ren_initshaders() -{ - GLuint gl_vertshader, gl_fragshader; - - // initialize the vertex shader and get the corresponding id - gl_vertshader = glCreateShader(GL_VERTEX_SHADER); - if (!gl_vertshader) - err(-1, "Could not create the vertex shader"); - - // map the shader file into memory - const char *vshader_str = NULL; - int vshader_size; - map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER); - - // get the shader into opengl - glShaderSource(gl_vertshader, 1, &vshader_str, NULL); - // compile the shader - glCompileShader(gl_vertshader); - if (debug_shader(gl_vertshader)) - exit(EXIT_FAILURE); - - // do the same for the fragment shader - // FIXME: make this a function - gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER); - if (!gl_fragshader) - err(-1, "Could not create the vertex shader"); - - // map the shader file into memory - const char *fshader_str = NULL; - int fshader_size; - map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER); - - // get the shader into opengl - glShaderSource(gl_fragshader, 1, &fshader_str, NULL); - // compile the shader - glCompileShader(gl_fragshader); - if (debug_shader(gl_fragshader)) - exit(EXIT_FAILURE); - - // create the main program object, it is an amalgamation of all shaders - ren.gl_program = glCreateProgram(); - // attach the shaders to the program (set which shaders are present) - glAttachShader(ren.gl_program, gl_vertshader); - glAttachShader(ren.gl_program, gl_fragshader); - // then link the program (basically the linking stage of the program) - glLinkProgram(ren.gl_program); - if (debug_program(ren.gl_program)) - exit(EXIT_FAILURE); - - // after linking the shaders can be detached and the source freed from - // memory since the program is ready to use - glDetachShader(ren.gl_program, gl_vertshader); - glDetachShader(ren.gl_program, gl_fragshader); - munmap((void *)vshader_str, vshader_size); - munmap((void *)fshader_str, fshader_size); - - // now tell opengl to use the program - glUseProgram(ren.gl_program); -} - - -void ren_drawvertbuffer() -{ - if (!changed()) - return; - glClear(GL_COLOR_BUFFER_BIT); - glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer); - // upload vertex data - if (vstack.idx != vstack.prev_idx) - glBufferData(GL_ARRAY_BUFFER, vstack.idx*sizeof(struct vertex), vstack.v, GL_DYNAMIC_DRAW); - else - glBufferSubData(GL_ARRAY_BUFFER, 0, vstack.idx*sizeof(struct vertex), vstack.v); - // draw vertex data - glDrawArrays(GL_TRIANGLES, 0, vstack.idx); - glBindBuffer(GL_ARRAY_BUFFER, 0); - SDL_GL_SwapWindow(ren.w); -} - - -int w_height(SDL_Window *win) -{ - int h; - SDL_GetWindowSize(win, NULL, &h); - return h; -} - - -int w_width(SDL_Window *win) -{ - int w; - SDL_GetWindowSize(win, &w, NULL); - return w; -} - - -void import_font(const char *path) -{ - const char *map; - int size; - const struct _ff *img; - - map_file(&map, &size, path); - img = (const struct _ff *)map; - fw = ntohl(img->w); - fh = ntohl(img->h); - - glGenTextures(1, &ren.font_texture); - glBindTexture(GL_TEXTURE_2D, ren.font_texture); - // farbfeld image - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_SHORT, img->bytes); - - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - munmap((void *)map, size); -} - - -void push_text(int x, int y, float scale, const char *s) -{ - for (; *s; s++) { - int u, v; - int idx = *s - ' '; - u = idx % (fw / gw); - v = (idx / (fw / gw)) % (fh / gh); - vstack_push_quad_t(x, y, gw*scale, gh*scale, u*gw, v*gh); - x += gw*scale; - if (*s == '\n') - y += gh; - } -} - - -int main (void) -{ - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); - - ren.w = SDL_CreateWindow("test", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - 500, 500, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ); - - // create the OpenGL context - ren.gl = SDL_GL_CreateContext(ren.w); - if (ren.gl == NULL) - err(-1, "Failed to create OpenGL context: %s", SDL_GetError()); - - // select some features - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glDisable(GL_CULL_FACE); - glDisable(GL_DEPTH_TEST); - glEnable(GL_SCISSOR_TEST); - glEnable(GL_TEXTURE_2D); - - // initialize glew, this library gives us the declarations for most GL - // functions, in the future it would be cool to do it manually - GLenum glew_err = glewInit(); - if (glew_err != GLEW_OK) - err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err)); - - ren_initshaders(); - import_font("./charmap.ff"); - - vec4 magenta = {.r=1.0, .g=0.0, .b=1.0, .a=1.0}; - - ren_initvertbuffer(); - - glClearColor(0.3f, 0.3f, 0.3f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - - // event loop and drawing - SDL_Event ev = {0}; - int running = 1; - do { - SDL_WaitEvent(&ev); - switch (ev.type) { - case SDL_QUIT: running = 0; break; - case SDL_WINDOWEVENT: - if(ev.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { - glViewport(0, 0, w_width(ren.w), w_height(ren.w)); - glScissor(0, 0, w_width(ren.w), w_height(ren.w)); - force_changed(); - } - break; - default: break; - } - - vstack_push_quad_c(0, 0, 100, 100, magenta); - vstack_push_quad_c(200, 0, 10, 10, magenta); - vstack_push_quad_c(10, 150, 100, 100, magenta); - push_text(250, 250, 1.0f, "Ciao Victoria <3"); - update_hash(); - - ren_drawvertbuffer(); - - vstack_clear(); - - } while(running); - - glUseProgram(0); - glDisableVertexAttribArray(vertindex); - glDisableVertexAttribArray(colindex); - glDeleteTextures(1, &ren.font_texture); - glDeleteBuffers(1, &ren.gl_vertbuffer); - SDL_GL_DeleteContext(ren.gl); - SDL_DestroyWindow(ren.w); - SDL_Quit(); - - return 0; -} diff --git a/opengl-ren/vertex.glsl b/opengl-ren/vertex.glsl deleted file mode 100644 index 9c4d840..0000000 --- a/opengl-ren/vertex.glsl +++ /dev/null @@ -1,17 +0,0 @@ -#version 330 - -// use the input ("in") vertices from index zero -layout(location = 0) in vec2 position; -layout(location = 1) in vec4 color; -layout(location = 2) in vec2 uv; - -flat out vec4 out_color; -out vec2 texture_coord; - -void main() -{ - // simply copy teh output position - gl_Position = vec4(position.x, position.y, 0.0f, 1.0f); - out_color = color; - texture_coord = uv; -} \ No newline at end of file diff --git a/opengl/.gitignore b/opengl/.gitignore deleted file mode 100644 index aafba12..0000000 --- a/opengl/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -a.out -gl \ No newline at end of file diff --git a/opengl/Makefile b/opengl/Makefile deleted file mode 100644 index f05bf6e..0000000 --- a/opengl/Makefile +++ /dev/null @@ -1,5 +0,0 @@ -CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g -LDFLAGS = -lSDL2 -lGLEW -lGL - -gl: main.c - gcc ${CFLAGS} ${LDFLAGS} main.c -o gl \ No newline at end of file diff --git a/opengl/README b/opengl/README deleted file mode 100644 index e18e538..0000000 --- a/opengl/README +++ /dev/null @@ -1,2 +0,0 @@ -since I've never worked with opengl I made this folder as a little test environment -for an SDL-OpenGL renderer \ No newline at end of file diff --git a/opengl/fragment.glsl b/opengl/fragment.glsl deleted file mode 100644 index 5d2d2c1..0000000 --- a/opengl/fragment.glsl +++ /dev/null @@ -1,10 +0,0 @@ -#version 330 - -smooth in vec4 out_color; -out vec4 color; - -void main() -{ - // set the color for each vertex to white - color = out_color; -} \ No newline at end of file diff --git a/opengl/main.c b/opengl/main.c deleted file mode 100644 index 8d106d0..0000000 --- a/opengl/main.c +++ /dev/null @@ -1,278 +0,0 @@ -#define _POSIX_C_SOURCE 200809l - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - - -#define GLSL_VERT_SHADER "vertex.glsl" -#define GLSL_FRAG_SHADER "fragment.glsl" -#define PACKED __attribute__((packed)) - - -const int vertindex = 0; -const int colindex = 1; - -struct { - SDL_Window *w; - SDL_GLContext *gl; - GLuint gl_vertbuffer; - GLuint gl_program; -} ren = {0}; - -typedef struct PACKED { - union { GLfloat x, r; }; - union { GLfloat y, g; }; - union { GLfloat z, b; }; - union { GLfloat w, a; }; -} vec4; - -// a vertex has a position and a color -struct PACKED vertex { vec4 pos, col; }; - -#define VNUM(s) (sizeof(s)/sizeof(s[0])) -struct vertex vertbuffer[] = { - { .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, - { .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, - { .pos={.x= 0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, - - { .pos={.x= 0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, - { .pos={.x=-0.75f, .y=-0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, - { .pos={.x=-0.75f, .y= 0.75f, .z=0.0f,.w=1.0f}, .col={ .r=1.0f, .g=0.0f, .b=0.0f, .a=1.0f} }, -}; - - -// copy the vertex buffer from system to video memory -void ren_initvertbuffer() -{ - // generate a buffer id - glGenBuffers(1, &ren.gl_vertbuffer); - // tell opengl that we want to work on that buffer - glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer); - // copy the vertex data into the gpu memory, GL_STATIC_DRAW tells opengl - // that the data will be used for drawing (DRAW) and it will only be modified - // once (STATIC) - glBufferData(GL_ARRAY_BUFFER, sizeof(vertbuffer), &vertbuffer, GL_STATIC_DRAW); - // set the format of each vertex, in this case each vertex is made of 4 - // coordinates, x y z w, where w is the clip coordinate, stride is - // sizeof(vec4) since every postition vertex there is a vector representing - // color data - // set the position data of the vertex buffer, and bind it as input to the - // vertex shader with an index of 0 - glEnableVertexAttribArray(vertindex); - glVertexAttribPointer(vertindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), 0); - // set the color data of the vertex buffer to index 1 - // vertex attribute is the OpenGL name given to a set of vertices which are - // given as input to a vertext shader, in a shader an array of vertices is - // always referred to by index and not by pointer or other manners, indices - // go from 0 to 15 - glEnableVertexAttribArray(colindex); - glVertexAttribPointer(colindex, 4, GL_FLOAT, GL_FALSE, sizeof(struct vertex), (void*)sizeof(vec4)); - // reset the object bind so not to create errors - glBindBuffer(GL_ARRAY_BUFFER, 0); -} - - -void map_file(const char **str, int *size, const char *fname) -{ - FILE *fp = fopen(fname, "r"); - *size = lseek(fileno(fp), 0, SEEK_END); - *str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); - fclose(fp); -} - - -// print shader compilation errors -int debug_shader(GLuint shader) -{ - GLint status; - glGetShaderiv(shader, GL_COMPILE_STATUS, &status); - if (status != GL_FALSE) - return 0; - - GLint log_length; - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length); - - GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar)); - glGetShaderInfoLog(shader, log_length, NULL, log_str); - - const char *shader_type_str = NULL; - GLint shader_type; - glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type); - switch(shader_type) { - case GL_VERTEX_SHADER: shader_type_str = "vertex"; break; - case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break; - case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break; - } - - fprintf(stderr, "Compile failure in %s shader:\n%s\n", shader_type_str, log_str); - free(log_str); - return -1; -} - - -// print program compilation errors -int debug_program(GLuint prog) -{ - GLint status; - glGetProgramiv (prog, GL_LINK_STATUS, &status); - if (status != GL_FALSE) - return 0; - - GLint log_length; - glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length); - - GLchar *log_str = malloc((log_length + 1)*sizeof(GLchar)); - glGetProgramInfoLog(prog, log_length, NULL, log_str); - fprintf(stderr, "Linker failure: %s\n", log_str); - free(log_str); - - return -1; -} - - -void ren_initshaders() -{ - GLuint gl_vertshader, gl_fragshader; - - // initialize the vertex shader and get the corresponding id - gl_vertshader = glCreateShader(GL_VERTEX_SHADER); - if (!gl_vertshader) - err(-1, "Could not create the vertex shader"); - - // map the shader file into memory - const char *vshader_str = NULL; - int vshader_size; - map_file(&vshader_str, &vshader_size, GLSL_VERT_SHADER); - - // get the shader into opengl - glShaderSource(gl_vertshader, 1, &vshader_str, NULL); - // compile the shader - glCompileShader(gl_vertshader); - if (debug_shader(gl_vertshader)) - exit(EXIT_FAILURE); - - // do the same for the fragment shader - // FIXME: make this a function - gl_fragshader = glCreateShader(GL_FRAGMENT_SHADER); - if (!gl_fragshader) - err(-1, "Could not create the vertex shader"); - - // map the shader file into memory - const char *fshader_str = NULL; - int fshader_size; - map_file(&fshader_str, &fshader_size, GLSL_FRAG_SHADER); - - // get the shader into opengl - glShaderSource(gl_fragshader, 1, &fshader_str, NULL); - // compile the shader - glCompileShader(gl_fragshader); - if (debug_shader(gl_fragshader)) - exit(EXIT_FAILURE); - - // create the main program object, it is an amalgamation of all shaders - ren.gl_program = glCreateProgram(); - // attach the shaders to the program (set which shaders are present) - glAttachShader(ren.gl_program, gl_vertshader); - glAttachShader(ren.gl_program, gl_fragshader); - // then link the program (basically the linking stage of the program) - glLinkProgram(ren.gl_program); - if (debug_program(ren.gl_program)) - exit(EXIT_FAILURE); - - // after linking the shaders can be detached and the source freed from - // memory since the program is ready to use - glDetachShader(ren.gl_program, gl_vertshader); - glDetachShader(ren.gl_program, gl_fragshader); - munmap((void *)vshader_str, vshader_size); - munmap((void *)fshader_str, fshader_size); - - // now tell opengl to use the program - glUseProgram(ren.gl_program); -} - - -void ren_drawvertbuffer() -{ - glBindBuffer(GL_ARRAY_BUFFER, ren.gl_vertbuffer); - glDrawArrays(GL_TRIANGLES, 0, VNUM(vertbuffer)); - glBindBuffer(GL_ARRAY_BUFFER, 0); -} - - -int w_height(SDL_Window *win) -{ - int h; - SDL_GetWindowSize(win, NULL, &h); - return h; -} - - -int w_width(SDL_Window *win) -{ - int w; - SDL_GetWindowSize(win, &w, NULL); - return w; -} - - -int main (void) -{ - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); - - ren.w = SDL_CreateWindow("test", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - 500, 500, SDL_WINDOW_OPENGL ); - - // create the OpenGL context - ren.gl = SDL_GL_CreateContext(ren.w); - if (ren.gl == NULL) - err(-1, "Failed to create OpenGL context: %s", SDL_GetError()); - - // initialize glew, this library gives us the declarations for most GL - // functions, in the future it would be cool to do it manually - GLenum glew_err = glewInit(); - if (glew_err != GLEW_OK) - err(-1, "Failed to initialize GLEW: %s", glewGetErrorString(glew_err)); - - ren_initvertbuffer(); - ren_initshaders(); - - // event loop and drawing - SDL_Event ev = {0}; - int running = 1; - do { - SDL_WaitEvent(&ev); - switch (ev.type) { - case SDL_QUIT: running = 0; break; - default: break; - } - - glViewport(0, 0, w_width(ren.w), w_height(ren.w)); - glClearColor(0.f, 0.f, 0.f, 0.f); - glClear(GL_COLOR_BUFFER_BIT); - - ren_drawvertbuffer(); - - SDL_GL_SwapWindow(ren.w); - - } while(running); - - glDisableVertexAttribArray(vertindex); - glDisableVertexAttribArray(colindex); - SDL_GL_DeleteContext(ren.gl); - SDL_DestroyWindow(ren.w); - SDL_Quit(); - - return 0; -} diff --git a/opengl/vertex.glsl b/opengl/vertex.glsl deleted file mode 100644 index 76bd58a..0000000 --- a/opengl/vertex.glsl +++ /dev/null @@ -1,14 +0,0 @@ -#version 330 - -// use the input ("in") vertices from index zero -layout(location = 0) in vec4 position; -layout(location = 1) in vec4 color; - -smooth out vec4 out_color; - -void main() -{ - // simply copy teh output position - gl_Position = position; - out_color = color; -} \ No newline at end of file diff --git a/rewrite/generic_stack.h b/rewrite/generic_stack.h deleted file mode 100644 index 54875b2..0000000 --- a/rewrite/generic_stack.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef _STACK_GENERIC_H -#define _STACK_GENERIC_H - - -#define STACK_STEP 8 -#define STACK_SALT 0xbabb0cac - -// FIXME: find a way to not re-hash the whole stack when removing one item - -// incremental hash for every grow -#define STACK_HASH(p, s, h) \ -{ \ - unsigned char *v = (unsigned char *)(p); \ - for (int x = (s); x; x--) { \ - (h) += v[x-1]; \ - (h) += (h) << 10; \ - (h) ^= (h) >> 6; \ - } \ - (h) += (h) << 3; \ - (h) ^= (h) >> 11; \ - (h) += (h) << 15; \ -} - - -// declare just the prototypes -#define STACK_PROTO(stackname, type) \ -struct stackname { \ - type *items; \ - int size, idx, old_idx; \ - unsigned int hash, old_hash; \ -}; \ -struct stackname stackname##_init(void); \ -int stackname##_grow(struct stackname *stack, int step); \ -int stackname##_push(struct stackname *stack, type *e); \ -type stackname##_pop(struct stackname *stack); \ -int stackname##_clear(struct stackname *stack); \ -int stackname##_changed(struct stackname *stack); \ -int stackname##_size_changed(struct stackname *stack); \ -int stackname##_free(struct stackname *stack); - - -#define STACK_DEFINE(stackname, type) \ -struct stackname; \ -\ -\ -struct stackname stackname##_init(void) \ -{ \ - return (struct stackname){0, .hash = STACK_SALT}; \ -} \ -\ -\ -int stackname##_grow(struct stackname *stack, int step) \ -{ \ - if (!stack) \ - return -1; \ - stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \ - if(!stack->items) \ - return -1; \ - memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \ - stack->size += step; \ - return 0; \ -} \ -\ -\ -int stackname##_push(struct stackname *stack, type *e) \ -{ \ - if (!stack || !e) \ - return -1; \ - if (stack->idx >= stack->size) \ - if (stackname##_grow(stack, STACK_STEP)) \ - return -1; \ - stack->items[stack->idx++] = *e; \ - STACK_HASH(e, sizeof(type), stack->hash); \ - return 0; \ -} \ -\ -\ -type stackname##_pop(struct stackname *stack) \ -{ \ - if (!stack || stack->idx == 0 || stack->size == 0) \ - return (type){0}; \ - stack->hash = STACK_SALT; \ - STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \ - return stack->items[stack->idx--]; \ -} \ -\ -\ -int stackname##_clear(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - stack->old_idx = stack->idx; \ - stack->old_hash = stack->hash; \ - stack->hash = STACK_SALT; \ - stack->idx = 0; \ - return 0; \ -} \ -\ -\ -int stackname##_changed(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - return stack->hash != stack->old_hash; \ -} \ -\ -\ -int stackname##_size_changed(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - return stack->size != stack->old_idx; \ -} \ -\ -\ -int stackname##_free(struct stackname *stack) \ -{ \ - if (stack) { \ - stackname##_clear(stack); \ - if (stack->items) \ - free(stack->items); \ - } \ - return 0; \ -} \ - -#endif diff --git a/test/Makefile b/test/Makefile deleted file mode 100644 index aa3facf..0000000 --- a/test/Makefile +++ /dev/null @@ -1,11 +0,0 @@ -CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g -LDFLAGS = -lSDL2 -lm - -test: main.c ../ugui.c ../ugui.h ../def_style.h ../input.c - gcc ${CFLAGS} -c ../ugui.c -o ugui.o - gcc ${CFLAGS} -c ../input.c -o input.o - gcc ${CFLAGS} -c main.c -o main.o - gcc ${LDFLAGS} main.o ugui.o input.o -o test - -clean: - rm -f main.o ugui.o diff --git a/test/main.c b/test/main.c deleted file mode 100644 index 5f2d8f1..0000000 --- a/test/main.c +++ /dev/null @@ -1,268 +0,0 @@ -#define _POSIX_C_SOURCE 200809l - -#include -#include - -#include "../ugui.h" - -#define STB_RECT_PACK_IMPLEMENTATION -#define STB_TRUETYPE_IMPLEMENTATION -#define STBTTF_IMPLEMENTATION -#include "stbttf.h" - - -SDL_Window *w; -SDL_Renderer *r; -ug_ctx_t *ctx; -STBTTF_Font *font; - -void cleanup(void); - -int main(void) -{ - SDL_DisplayMode dm; - - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - SDL_EnableScreenSaver(); - SDL_EventState(SDL_DROPFILE, SDL_ENABLE); - SDL_EventState(SDL_DROPTEXT, SDL_ENABLE); - - SDL_GetDesktopDisplayMode(0, &dm); - -#ifdef SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR /* Available since 2.0.8 */ - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); -#endif - -#if SDL_VERSION_ATLEAST(2, 0, 5) - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); -#endif - - w = SDL_CreateWindow("test", - SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, - dm.w*0.8, dm.h*0.8, - SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | - SDL_WINDOW_OPENGL ); - - //SDL_Surface *s; - //s = SDL_GetWindowSurface(w); - r = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED); - SDL_SetRenderDrawBlendMode(r, SDL_BLENDMODE_BLEND); - - ug_vec2_t size, dsize; - SDL_GetWindowSize(w, &size.w, &size.h); - SDL_GL_GetDrawableSize(w, &dsize.w, &dsize.h); - float scale = 1.0; - scale = ((float)(size.w+size.h)/2)/((float)(dsize.w+dsize.h)/2); - - float dpi; - int idx; - idx = SDL_GetWindowDisplayIndex(w); - SDL_GetDisplayDPI(idx, &dpi, NULL, NULL); - - - ctx = ug_ctx_new(); - ug_ctx_set_displayinfo(ctx, scale, dpi); - ug_ctx_set_drawableregion(ctx, dsize); - - // open font - font = STBTTF_OpenFont(r, "monospace.ttf", ctx->style_px->title.font_size.size.i); - -// atexit(cleanup); - - SDL_Event event; - - char button_map[] = { - [SDL_BUTTON_LEFT & 0xff] = UG_BTN_LEFT, - [SDL_BUTTON_MIDDLE & 0xff] = UG_BTN_MIDDLE, - [SDL_BUTTON_RIGHT & 0xff] = UG_BTN_RIGHT, - [SDL_BUTTON_X1 & 0xff] = UG_BTN_4, - [SDL_BUTTON_X2 & 0xff] = UG_BTN_5, - }; - - do { - SDL_WaitEvent(&event); - - switch (event.type) { - case SDL_WINDOWEVENT: - switch (event.window.event) { - case SDL_WINDOWEVENT_SHOWN: - (SDL_Log("Window %d shown", event.window.windowID)); - break; - case SDL_WINDOWEVENT_HIDDEN: - (SDL_Log("Window %d hidden", event.window.windowID)); - break; - case SDL_WINDOWEVENT_EXPOSED: - (SDL_Log("Window %d exposed", event.window.windowID)); - break; - case SDL_WINDOWEVENT_MOVED: - (SDL_Log("Window %d moved to %d,%d", - event.window.windowID, event.window.data1, - event.window.data2)); - break; - case SDL_WINDOWEVENT_RESIZED: - (SDL_Log("Window %d resized to %dx%d", - event.window.windowID, event.window.data1, - event.window.data2)); - break; - case SDL_WINDOWEVENT_SIZE_CHANGED: - (SDL_Log("Window %d size changed to %dx%d", - event.window.windowID, event.window.data1, - event.window.data2)); - - size.w = event.window.data1; - size.h = event.window.data2; - // surface is invalidated every time the window - // is resized - //s = SDL_GetWindowSurface(w); - ug_ctx_set_drawableregion(ctx, size); - - break; - case SDL_WINDOWEVENT_MINIMIZED: - (SDL_Log("Window %d minimized", event.window.windowID)); - break; - case SDL_WINDOWEVENT_MAXIMIZED: - (SDL_Log("Window %d maximized", event.window.windowID)); - break; - case SDL_WINDOWEVENT_RESTORED: - (SDL_Log("Window %d restored", event.window.windowID)); - break; - case SDL_WINDOWEVENT_ENTER: - (SDL_Log("Mouse entered window %d", - event.window.windowID)); - break; - case SDL_WINDOWEVENT_LEAVE: - (SDL_Log("Mouse left window %d", event.window.windowID)); - break; - case SDL_WINDOWEVENT_FOCUS_GAINED: - (SDL_Log("Window %d gained keyboard focus", - event.window.windowID)); - break; - case SDL_WINDOWEVENT_FOCUS_LOST: - (SDL_Log("Window %d lost keyboard focus", - event.window.windowID)); - break; - case SDL_WINDOWEVENT_CLOSE: - (SDL_Log("Window %d closed", event.window.windowID)); - break; -#if SDL_VERSION_ATLEAST(2, 0, 5) - case SDL_WINDOWEVENT_TAKE_FOCUS: - (SDL_Log("Window %d is offered a focus", event.window.windowID)); - break; - case SDL_WINDOWEVENT_HIT_TEST: - (SDL_Log("Window %d has a special hit test", event.window.windowID)); - break; -#endif - default: - (SDL_Log("Window %d got unknown event %d", - event.window.windowID, event.window.event)); - break; - } - break; - case SDL_QUIT: - (printf("Quitting\n")); - break; - case SDL_MOUSEMOTION: - - ug_input_mousemove(ctx, event.motion.x, event.motion.y); - - break; - case SDL_MOUSEBUTTONDOWN: - ug_input_mousemove(ctx, event.button.x, event.button.y); - ug_input_mousedown(ctx, button_map[event.button.button & 0xff]); - - break; - case SDL_MOUSEBUTTONUP: - - ug_input_mousemove(ctx, event.button.x, event.button.y); - ug_input_mouseup(ctx, button_map[event.button.button & 0xff]); - - break; - default: - (printf("Unknown event: %d\n", event.type)); - break; - } - - - ug_frame_begin(ctx); - - //if (ctx->frame < 5000) { - // ug_container_menu_bar(ctx, "Menu fichissimo", (ug_size_t)SIZE_PX(24)); - //} else if (ctx->frame == 5000) { - // ug_container_remove(ctx, "Menu fichissimo"); - //} - - ug_container_floating(ctx, "stupid name", (ug_div_t){.x=SIZE_PX(0), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)}); - ug_element_button(ctx, "float", "X", (ug_div_t){SQUARE(SIZE_MM(5))}); - //ug_container_floating(ctx, "floating windoooooooow", (ug_div_t){.x=SIZE_PX(100), .y=SIZE_PX(0), .w=SIZE_PX(100), .h=SIZE_MM(75.0)}); - - //ug_container_sidebar(ctx, "Right Sidebar", (ug_size_t)SIZE_PX(300), UG_SIDE_RIGHT); - //ug_container_sidebar(ctx, "Left Sidebar", (ug_size_t)SIZE_PX(200), UG_SIDE_LEFT); - //ug_container_sidebar(ctx, "Bottom Sidebar", (ug_size_t)SIZE_MM(10), UG_SIDE_BOTTOM); - ug_container_sidebar(ctx, "Top Sidebar", (ug_size_t)SIZE_MM(40), UG_SIDE_TOP); - - // ug_container_popup(ctx, "Annoying popup", (ug_div_t){.x=SIZE_MM(150), .y=SIZE_MM(150), .w=SIZE_PX(100), .h=SIZE_MM(75.0)}); - - //ug_container_body(ctx, "Main Body"); - //if (ug_container_body(ctx, "Other Body")) - // printf("No space!\n"); - - ug_layout_row(ctx); - - ug_layout_column(ctx); - ug_element_button(ctx, "button 1", "hey", (ug_div_t){SQUARE(SIZE_MM(10))}); - //ug_element_button(ctx, "button 2", "lol", (ug_div_t){SQUARE(SIZE_MM(10))}); - //ug_layout_next_row(ctx); - //ug_element_button(ctx, "button 3", "L", (ug_div_t){SQUARE(SIZE_MM(10))}); - //ug_element_button(ctx, "button 4", "69", (ug_div_t){SQUARE(SIZE_MM(10))}); - ug_layout_next_column(ctx); - ug_element_button(ctx, "button 5", "lmao", (ug_div_t){SQUARE(SIZE_MM(10))}); - //ug_element_textbtn(ctx, "text button 1", "foo", (ug_div_t){.w = 0, .h = SIZE_PX(30)}); - //ug_element_button(ctx, "button 6", "", (ug_div_t){SQUARE(SIZE_MM(10)),.x=SIZE_PX(-10)}); - - ug_container_body(ctx, "fill body"); - - ug_frame_end(ctx); - - // fill background - // solid purple makes it easy to identify, same color as hl missing texture - SDL_SetRenderDrawColor(r, 0xff, 0, 0xdc, 0xff); - SDL_RenderClear(r); - for (ug_cmd_t *cmd = NULL; (cmd = ug_cmd_next(ctx));) { - switch (cmd->type) { - case UG_CMD_RECT: - { - ug_color_t col = cmd->rect.color; - SDL_Rect sr = { - .x = cmd->rect.x, - .y = cmd->rect.y, - .w = cmd->rect.w, - .h = cmd->rect.h, - }; - SDL_SetRenderDrawColor(r, col.r, col.g, col.b, col.a); - SDL_RenderFillRect(r, &sr); - } - break; - case UG_CMD_TEXT: - SDL_SetRenderDrawColor(r, cmd->text.color.r, cmd->text.color.g, cmd->text.color.b, cmd->text.color.a); - STBTTF_RenderText(r, font, cmd->text.x, cmd->text.y, cmd->text.str); - break; - default: break; - } - } - SDL_RenderPresent(r); - - } while (event.type != SDL_QUIT); - - cleanup(); - - return 0; -} - -void cleanup(void) -{ - ug_ctx_free(ctx); - STBTTF_CloseFont(font); - SDL_DestroyRenderer(r); - SDL_DestroyWindow(w); - SDL_Quit(); -} diff --git a/test/monospace.ttf b/test/monospace.ttf deleted file mode 100644 index 92a90cb06e0535afa79f6cba26ceece15a7f7959..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 309408 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

lipX z89%t)L1Zn7w#;-}USgg#E*&nk6g|(G1Qmus3tNsjIhevgp16@5CCac&oFQ%eMM}nZ z{mnz>s34v3aMz*q^t7z0vk!G1GW!Jt2ASxcw2bWTp8MFWsimbQCDW&tl$Dk-0g1n8B}ozJZoF0r$e^J`a*sp<@okRTsO%H2(}g3TTkA_r zY_P>jVzWUR3KUay?+f%NC1?ezWXd19?3;y;D2) z=Jd@SxaBCD7@yRdoI1@I*B<-wy!p$P-g4A5y{O1h(7URyq%^-^5%4=S<)}GLXGT&? zP*KLzTkhzZ9`LuniN0E|5dVh^Rxh7GQJxUnGxL_)=k$BaC)IDL^7E%p&b9}+_DF?A zGiGGlx&2!bzLjrRQ-8rQ!6d{9w;c=hLS+CVTojp^4u~Lz7RS90h8bLbuiryo}eM%KoVvP^fjleHU^+=q5A;2uIP{IMn8hjx?CUO%x=LY*K$e zUzje{LKq99L~=Fia#OW;19`##VP1~Mrtvh>B0Gh6=r^B#@tfaImET5%z7r=71y6sR z1)+KIKbbhmHDqK(Opkw#A(lJ1ckkf2b32FbV2>ybjO9me1|^F}-I933OjHUpo!ybB za2SP5ev0ZSLtRNH0zA}Iy~GRPK;d0)CZZjOFGZkHpcnj=aBX`konu{0C@R1fXWJ6u zt>zd5de9(qgvu{hvPyqiP=X*?9QK49o6V7vmy2IXc%isb1#ifGsWofO;PWB)4-5)1 z;7{NZNdd%*kTmtr&*fqJ!&%FTzlevj)`WLeZu_9MsiDqXpU^aM&*aJB?87X%2GSC~ z2D0Q+hRKs}Y;8)eG1oRU8MQ2k#V^zZ`IMHcL&f>K|5&>xBqaImm<(OqoH=jI_YU>b z>SAO4B%e^+h{dN=?PS~(gl+~lg! zO4x6Syg#q!XWP@GGQ%@7XD^FdKFsu40i{{Jdo!~$as#PyNTj_fx=|f«q(At`*`NbkV(;WAZRz)Jn7&p-cM>U1p@ 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 D9ti__ diff --git a/test/stb_rect_pack.h b/test/stb_rect_pack.h deleted file mode 100644 index 6a633ce..0000000 --- a/test/stb_rect_pack.h +++ /dev/null @@ -1,623 +0,0 @@ -// stb_rect_pack.h - v1.01 - public domain - rectangle packing -// Sean Barrett 2014 -// -// Useful for e.g. packing rectangular textures into an atlas. -// Does not do rotation. -// -// Before #including, -// -// #define STB_RECT_PACK_IMPLEMENTATION -// -// in the file that you want to have the implementation. -// -// Not necessarily the awesomest packing method, but better than -// the totally naive one in stb_truetype (which is primarily what -// this is meant to replace). -// -// Has only had a few tests run, may have issues. -// -// More docs to come. -// -// No memory allocations; uses qsort() and assert() from stdlib. -// Can override those by defining STBRP_SORT and STBRP_ASSERT. -// -// This library currently uses the Skyline Bottom-Left algorithm. -// -// Please note: better rectangle packers are welcome! Please -// implement them to the same API, but with a different init -// function. -// -// Credits -// -// Library -// Sean Barrett -// Minor features -// Martins Mozeiko -// github:IntellectualKitty -// -// Bugfixes / warning fixes -// Jeremy Jaussaud -// Fabian Giesen -// -// Version history: -// -// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section -// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles -// 0.99 (2019-02-07) warning fixes -// 0.11 (2017-03-03) return packing success/fail result -// 0.10 (2016-10-25) remove cast-away-const to avoid warnings -// 0.09 (2016-08-27) fix compiler warnings -// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) -// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) -// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort -// 0.05: added STBRP_ASSERT to allow replacing assert -// 0.04: fixed minor bug in STBRP_LARGE_RECTS support -// 0.01: initial release -// -// LICENSE -// -// See end of file for license information. - -////////////////////////////////////////////////////////////////////////////// -// -// INCLUDE SECTION -// - -#ifndef STB_INCLUDE_STB_RECT_PACK_H -#define STB_INCLUDE_STB_RECT_PACK_H - -#define STB_RECT_PACK_VERSION 1 - -#ifdef STBRP_STATIC -#define STBRP_DEF static -#else -#define STBRP_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct stbrp_context stbrp_context; -typedef struct stbrp_node stbrp_node; -typedef struct stbrp_rect stbrp_rect; - -typedef int stbrp_coord; - -#define STBRP__MAXVAL 0x7fffffff -// Mostly for internal use, but this is the maximum supported coordinate value. - -STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); -// Assign packed locations to rectangles. The rectangles are of type -// 'stbrp_rect' defined below, stored in the array 'rects', and there -// are 'num_rects' many of them. -// -// Rectangles which are successfully packed have the 'was_packed' flag -// set to a non-zero value and 'x' and 'y' store the minimum location -// on each axis (i.e. bottom-left in cartesian coordinates, top-left -// if you imagine y increasing downwards). Rectangles which do not fit -// have the 'was_packed' flag set to 0. -// -// You should not try to access the 'rects' array from another thread -// while this function is running, as the function temporarily reorders -// the array while it executes. -// -// To pack into another rectangle, you need to call stbrp_init_target -// again. To continue packing into the same rectangle, you can call -// this function again. Calling this multiple times with multiple rect -// arrays will probably produce worse packing results than calling it -// a single time with the full rectangle array, but the option is -// available. -// -// The function returns 1 if all of the rectangles were successfully -// packed and 0 otherwise. - -struct stbrp_rect -{ - // reserved for your use: - int id; - - // input: - stbrp_coord w, h; - - // output: - stbrp_coord x, y; - int was_packed; // non-zero if valid packing - -}; // 16 bytes, nominally - - -STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); -// Initialize a rectangle packer to: -// pack a rectangle that is 'width' by 'height' in dimensions -// using temporary storage provided by the array 'nodes', which is 'num_nodes' long -// -// You must call this function every time you start packing into a new target. -// -// There is no "shutdown" function. The 'nodes' memory must stay valid for -// the following stbrp_pack_rects() call (or calls), but can be freed after -// the call (or calls) finish. -// -// Note: to guarantee best results, either: -// 1. make sure 'num_nodes' >= 'width' -// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' -// -// If you don't do either of the above things, widths will be quantized to multiples -// of small integers to guarantee the algorithm doesn't run out of temporary storage. -// -// If you do #2, then the non-quantized algorithm will be used, but the algorithm -// may run out of temporary storage and be unable to pack some rectangles. - -STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); -// Optionally call this function after init but before doing any packing to -// change the handling of the out-of-temp-memory scenario, described above. -// If you call init again, this will be reset to the default (false). - - -STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); -// Optionally select which packing heuristic the library should use. Different -// heuristics will produce better/worse results for different data sets. -// If you call init again, this will be reset to the default. - -enum -{ - STBRP_HEURISTIC_Skyline_default=0, - STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, - STBRP_HEURISTIC_Skyline_BF_sortHeight -}; - - -////////////////////////////////////////////////////////////////////////////// -// -// the details of the following structures don't matter to you, but they must -// be visible so you can handle the memory allocations for them - -struct stbrp_node -{ - stbrp_coord x,y; - stbrp_node *next; -}; - -struct stbrp_context -{ - int width; - int height; - int align; - int init_mode; - int heuristic; - int num_nodes; - stbrp_node *active_head; - stbrp_node *free_head; - stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' -}; - -#ifdef __cplusplus -} -#endif - -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// IMPLEMENTATION SECTION -// - -#ifdef STB_RECT_PACK_IMPLEMENTATION -#ifndef STBRP_SORT -#include -#define STBRP_SORT qsort -#endif - -#ifndef STBRP_ASSERT -#include -#define STBRP_ASSERT assert -#endif - -#ifdef _MSC_VER -#define STBRP__NOTUSED(v) (void)(v) -#define STBRP__CDECL __cdecl -#else -#define STBRP__NOTUSED(v) (void)sizeof(v) -#define STBRP__CDECL -#endif - -enum -{ - STBRP__INIT_skyline = 1 -}; - -STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) -{ - switch (context->init_mode) { - case STBRP__INIT_skyline: - STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); - context->heuristic = heuristic; - break; - default: - STBRP_ASSERT(0); - } -} - -STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) -{ - if (allow_out_of_mem) - // if it's ok to run out of memory, then don't bother aligning them; - // this gives better packing, but may fail due to OOM (even though - // the rectangles easily fit). @TODO a smarter approach would be to only - // quantize once we've hit OOM, then we could get rid of this parameter. - context->align = 1; - else { - // if it's not ok to run out of memory, then quantize the widths - // so that num_nodes is always enough nodes. - // - // I.e. num_nodes * align >= width - // align >= width / num_nodes - // align = ceil(width/num_nodes) - - context->align = (context->width + context->num_nodes-1) / context->num_nodes; - } -} - -STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) -{ - int i; - - for (i=0; i < num_nodes-1; ++i) - nodes[i].next = &nodes[i+1]; - nodes[i].next = NULL; - context->init_mode = STBRP__INIT_skyline; - context->heuristic = STBRP_HEURISTIC_Skyline_default; - context->free_head = &nodes[0]; - context->active_head = &context->extra[0]; - context->width = width; - context->height = height; - context->num_nodes = num_nodes; - stbrp_setup_allow_out_of_mem(context, 0); - - // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) - context->extra[0].x = 0; - context->extra[0].y = 0; - context->extra[0].next = &context->extra[1]; - context->extra[1].x = (stbrp_coord) width; - context->extra[1].y = (1<<30); - context->extra[1].next = NULL; -} - -// find minimum y position if it starts at x1 -static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) -{ - stbrp_node *node = first; - int x1 = x0 + width; - int min_y, visited_width, waste_area; - - STBRP__NOTUSED(c); - - STBRP_ASSERT(first->x <= x0); - - #if 0 - // skip in case we're past the node - while (node->next->x <= x0) - ++node; - #else - STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency - #endif - - STBRP_ASSERT(node->x <= x0); - - min_y = 0; - waste_area = 0; - visited_width = 0; - while (node->x < x1) { - if (node->y > min_y) { - // raise min_y higher. - // we've accounted for all waste up to min_y, - // but we'll now add more waste for everything we've visted - waste_area += visited_width * (node->y - min_y); - min_y = node->y; - // the first time through, visited_width might be reduced - if (node->x < x0) - visited_width += node->next->x - x0; - else - visited_width += node->next->x - node->x; - } else { - // add waste area - int under_width = node->next->x - node->x; - if (under_width + visited_width > width) - under_width = width - visited_width; - waste_area += under_width * (min_y - node->y); - visited_width += under_width; - } - node = node->next; - } - - *pwaste = waste_area; - return min_y; -} - -typedef struct -{ - int x,y; - stbrp_node **prev_link; -} stbrp__findresult; - -static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) -{ - int best_waste = (1<<30), best_x, best_y = (1 << 30); - stbrp__findresult fr; - stbrp_node **prev, *node, *tail, **best = NULL; - - // align to multiple of c->align - width = (width + c->align - 1); - width -= width % c->align; - STBRP_ASSERT(width % c->align == 0); - - // if it can't possibly fit, bail immediately - if (width > c->width || height > c->height) { - fr.prev_link = NULL; - fr.x = fr.y = 0; - return fr; - } - - node = c->active_head; - prev = &c->active_head; - while (node->x + width <= c->width) { - int y,waste; - y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); - if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL - // bottom left - if (y < best_y) { - best_y = y; - best = prev; - } - } else { - // best-fit - if (y + height <= c->height) { - // can only use it if it first vertically - if (y < best_y || (y == best_y && waste < best_waste)) { - best_y = y; - best_waste = waste; - best = prev; - } - } - } - prev = &node->next; - node = node->next; - } - - best_x = (best == NULL) ? 0 : (*best)->x; - - // if doing best-fit (BF), we also have to try aligning right edge to each node position - // - // e.g, if fitting - // - // ____________________ - // |____________________| - // - // into - // - // | | - // | ____________| - // |____________| - // - // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned - // - // This makes BF take about 2x the time - - if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { - tail = c->active_head; - node = c->active_head; - prev = &c->active_head; - // find first node that's admissible - while (tail->x < width) - tail = tail->next; - while (tail) { - int xpos = tail->x - width; - int y,waste; - STBRP_ASSERT(xpos >= 0); - // find the left position that matches this - while (node->next->x <= xpos) { - prev = &node->next; - node = node->next; - } - STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); - y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); - if (y + height <= c->height) { - if (y <= best_y) { - if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { - best_x = xpos; - STBRP_ASSERT(y <= best_y); - best_y = y; - best_waste = waste; - best = prev; - } - } - } - tail = tail->next; - } - } - - fr.prev_link = best; - fr.x = best_x; - fr.y = best_y; - return fr; -} - -static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) -{ - // find best position according to heuristic - stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); - stbrp_node *node, *cur; - - // bail if: - // 1. it failed - // 2. the best node doesn't fit (we don't always check this) - // 3. we're out of memory - if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { - res.prev_link = NULL; - return res; - } - - // on success, create new node - node = context->free_head; - node->x = (stbrp_coord) res.x; - node->y = (stbrp_coord) (res.y + height); - - context->free_head = node->next; - - // insert the new node into the right starting point, and - // let 'cur' point to the remaining nodes needing to be - // stiched back in - - cur = *res.prev_link; - if (cur->x < res.x) { - // preserve the existing one, so start testing with the next one - stbrp_node *next = cur->next; - cur->next = node; - cur = next; - } else { - *res.prev_link = node; - } - - // from here, traverse cur and free the nodes, until we get to one - // that shouldn't be freed - while (cur->next && cur->next->x <= res.x + width) { - stbrp_node *next = cur->next; - // move the current node to the free list - cur->next = context->free_head; - context->free_head = cur; - cur = next; - } - - // stitch the list back in - node->next = cur; - - if (cur->x < res.x + width) - cur->x = (stbrp_coord) (res.x + width); - -#ifdef _DEBUG - cur = context->active_head; - while (cur->x < context->width) { - STBRP_ASSERT(cur->x < cur->next->x); - cur = cur->next; - } - STBRP_ASSERT(cur->next == NULL); - - { - int count=0; - cur = context->active_head; - while (cur) { - cur = cur->next; - ++count; - } - cur = context->free_head; - while (cur) { - cur = cur->next; - ++count; - } - STBRP_ASSERT(count == context->num_nodes+2); - } -#endif - - return res; -} - -static int STBRP__CDECL rect_height_compare(const void *a, const void *b) -{ - const stbrp_rect *p = (const stbrp_rect *) a; - const stbrp_rect *q = (const stbrp_rect *) b; - if (p->h > q->h) - return -1; - if (p->h < q->h) - return 1; - return (p->w > q->w) ? -1 : (p->w < q->w); -} - -static int STBRP__CDECL rect_original_order(const void *a, const void *b) -{ - const stbrp_rect *p = (const stbrp_rect *) a; - const stbrp_rect *q = (const stbrp_rect *) b; - return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); -} - -STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) -{ - int i, all_rects_packed = 1; - - // we use the 'was_packed' field internally to allow sorting/unsorting - for (i=0; i < num_rects; ++i) { - rects[i].was_packed = i; - } - - // sort according to heuristic - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); - - for (i=0; i < num_rects; ++i) { - if (rects[i].w == 0 || rects[i].h == 0) { - rects[i].x = rects[i].y = 0; // empty rect needs no space - } else { - stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); - if (fr.prev_link) { - rects[i].x = (stbrp_coord) fr.x; - rects[i].y = (stbrp_coord) fr.y; - } else { - rects[i].x = rects[i].y = STBRP__MAXVAL; - } - } - } - - // unsort - STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); - - // set was_packed flags and all_rects_packed status - for (i=0; i < num_rects; ++i) { - rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); - if (!rects[i].was_packed) - all_rects_packed = 0; - } - - // return the all_rects_packed status - return all_rects_packed; -} -#endif - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/test/stb_truetype.h b/test/stb_truetype.h deleted file mode 100644 index bbf2284..0000000 --- a/test/stb_truetype.h +++ /dev/null @@ -1,5077 +0,0 @@ -// stb_truetype.h - v1.26 - public domain -// authored from 2009-2021 by Sean Barrett / RAD Game Tools -// -// ======================================================================= -// -// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES -// -// This library does no range checking of the offsets found in the file, -// meaning an attacker can use it to read arbitrary memory. -// -// ======================================================================= -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe -// Cass Everitt Martins Mozeiko github:aloucks -// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam -// Brian Hook Omar Cornut github:vassvik -// Walter van Niftrik Ryan Griege -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. Brian Costabile -// Ken Voskuil (kaesve) -// -// VERSION HISTORY -// -// 1.26 (2021-08-28) fix broken rasterizer -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places need to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct -{ - unsigned char *data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); -// Query the font vertical metrics without having to create a font first. - - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; - float xoff2,yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct -{ - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar *chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); -// If skip != 0, this tells stb_truetype to skip any codepoints for which -// there is no corresponding glyph. If skip=0, which is the default, then -// codepoints without a glyph recived the font's "missing character" glyph, -// typically an empty box by convention. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void *user_allocator_context; - void *pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - int skip_missing; - unsigned int h_oversample, v_oversample; - unsigned char *pixels; - void *nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publicly so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. -// Returns 0 if the character codepoint is not defined in the font. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - -typedef struct stbtt_kerningentry -{ - int glyph1; // use stbtt_FindGlyphIndex - int glyph2; - int advance; -} stbtt_kerningentry; - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); -// Retrieves a complete list of all of the kerning pairs provided by the font -// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. -// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy,cx1,cy1; - unsigned char type,padding; - } stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of contours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); -// fills svg with the character's SVG data. -// returns data size or 0 if SVG not found. - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex *vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void *userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap -// glyph/codepoint -- the character to generate the SDF for -// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) -// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - - - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf *b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf *b, int o) -{ - stbtt__buf_seek(b, b->cursor + o); -} - -static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void *p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*) p; - r.size = (int) size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) return b0 - 139; - else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) return stbtt__buf_get16(b); - else if (b0 == 29) return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf *b) { - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) op = stbtt__buf_get8(b) | 0x100; - if (op == key) return stbtt__buf_range(b, start, end-start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf *b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i*offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - return ttLONG(font_collection+8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1]+subrsoff); - return stbtt__cff_get_index(&cff); -} - -// since most people won't use this, find this table the first time it's needed -static int stbtt__get_svg(stbtt_fontinfo *info) -{ - stbtt_uint32 t; - if (info->svg < 0) { - t = stbtt__find_table(info->data, info->fontstart, "SVG "); - if (t) { - stbtt_uint32 offset = ttULONG(info->data + t + 2); - info->svg = t + offset; - } else { - info->svg = 0; - } - } - return info->svg; -} - -static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data+cff, 512*1024*1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) return 0; - if (charstrings == 0) return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - info->svg = -1; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start, last; - stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); - - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - last = ttUSHORT(data + endCount + 2*item); - if (unicode_codepoint < start || unicode_codepoint > last) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours < 0) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct -{ - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex *pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} - -static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) c->max_x = x; - if (y > c->max_y || !c->started) c->max_y = y; - if (x < c->min_x || !c->started) c->min_x = x; - if (y < c->min_y || !c->started) c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx *ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp-1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x18: // rcurveline - if (sp < 8) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { f = s[i]; i++; } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // FALLTHROUGH - case 0x1D: // callgsubr - if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); - v = (int) s[--sp]; - if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - //fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1+dx2+dx3+dx4+dx5; - dy = dy1+dy2+dy3+dy4+dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && b0 < 32) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) *x0 = r ? c.min_x : 0; - if (y0) *y0 = r ? c.min_y : 0; - if (x1) *x1 = r ? c.max_x : 0; - if (y1) *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) -{ - stbtt_uint8 *data = info->data + info->kern; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - return ttUSHORT(data+10); -} - -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) -{ - stbtt_uint8 *data = info->data + info->kern; - int k, length; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - length = ttUSHORT(data+10); - if (table_length < length) - length = table_length; - - for (k = 0; k < length; k++) - { - table[k].glyph1 = ttUSHORT(data+18+(k*6)); - table[k].glyph2 = ttUSHORT(data+20+(k*6)); - table[k].advance = ttSHORT(data+22+(k*6)); - } - - return length; -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch (coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l=0, r=glyphCount-1, m; - int straw, needle=glyph; - while (l <= r) { - stbtt_uint8 *glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - break; - } - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8 *rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l=0, r=rangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - break; - } - - default: return -1; // unsupported - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch (classDefFormat) - { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8 *classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - break; - } - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8 *classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l=0, r=classRangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - break; - } - - default: - return -1; // Unsupported definition type, return an error. - } - - // "All glyphs not assigned to a class fall into class 0". (OpenType spec) - return 0; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8 *lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8 *data; - stbtt_int32 i, sti; - - if (!info->gpos) return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data+0) != 1) return 0; // Major version 1 - if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data+8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i=0; i= pairSetCount) return 0; - - needle=glyph2; - r=pairValueCount-1; - l=0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8 *pairValue; - m = (l + r) >> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } else - return 0; - break; - } - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - stbtt_uint8 *class1Records, *class2Records; - stbtt_int16 xAdvance; - - if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed - if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed - - class1Records = table + 16; - class2Records = class1Records + 2 * (glyph1class * class2Count); - xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } else - return 0; - break; - } - - default: - return 0; // Unsupported position format - } - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - else if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); - if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); - if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) -{ - int i; - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); - - int numEntries = ttUSHORT(svg_doc_list); - stbtt_uint8 *svg_docs = svg_doc_list + 2; - - for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) - return svg_doc; - } - return 0; -} - -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) -{ - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc; - - if (info->svg == 0) - return 0; - - svg_doc = stbtt_FindSVGDoc(info, gl); - if (svg_doc != NULL) { - *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); - return ttULONG(svg_doc + 8); - } else { - return 0; - } -} - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) -{ - return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { - // e.g. space character - if (ix0) *ix0 = 0; - if (iy0) *iy0 = 0; - if (ix1) *ix1 = 0; - if (iy1) *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); - if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); - if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk -{ - struct stbtt__hheap_chunk *next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap -{ - struct stbtt__hheap_chunk *head; - void *first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) -{ - if (hh->first_free) { - void *p = hh->first_free; - hh->first_free = * (void **) p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap *hh, void *p) -{ - *(void **) p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) -{ - stbtt__hheap_chunk *c = hh->head; - while (c) { - stbtt__hheap_chunk *n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - - -typedef struct stbtt__active_edge -{ - struct stbtt__active_edge *next; - #if STBTT_RASTERIZER_VERSION==1 - int x,dx; - float ey; - int direction; - #elif STBTT_RASTERIZER_VERSION==2 - float fx,fdx,fdy; - float direction; - float sy; - float ey; - #else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" - #endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX-1) - -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - //STBTT_assert(e->y0 <= start_point); - if (!z) return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->direction; - } else { - int x1 = e->x; w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) -{ - if (y0 == y1) return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) return; - if (y1 < e->sy) return; - if (y0 < e->sy) { - x0 += (x1-x0) * (e->sy - y0) / (y1-y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1-x0) * (e->ey - y1) / (y1-y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x+1); - else if (x0 == x+1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x+1) - STBTT_assert(x1 >= x+1); - else - STBTT_assert(x1 >= x && x1 <= x+1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1-y0); - else if (x0 >= x+1 && x1 >= x+1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); - scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position - } -} - -static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) -{ - STBTT_assert(top_width >= 0); - STBTT_assert(bottom_width >= 0); - return (top_width + bottom_width) / 2.0f * height; -} - -static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) -{ - return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); -} - -static float stbtt__sized_triangle_area(float height, float width) -{ - return height * width / 2; -} - -static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) -{ - float y_bottom = y_top+1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); - stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0,sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int) x_top == (int) x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int) x_top; - height = (sy1 - sy0) * e->direction; - STBTT_assert(x >= 0 && x < len); - scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); - scanline_fill[x] += height; // everything right of this pixel is filled - } else { - int x,x1,x2; - float y_crossing, y_final, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - STBTT_assert(dy >= 0); - STBTT_assert(dx >= 0); - - x1 = (int) x_top; - x2 = (int) x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = y_top + dy * (x1+1 - x0); - - // compute intersection with y axis at x2 - y_final = y_top + dy * (x2 - x0); - - // x1 x_top x2 x_bottom - // y_top +------|-----+------------+------------+--------|---+------------+ - // | | | | | | - // | | | | | | - // sy0 | Txxxxx|............|............|............|............| - // y_crossing | *xxxxx.......|............|............|............| - // | | xxxxx..|............|............|............| - // | | /- xx*xxxx........|............|............| - // | | dy < | xxxxxx..|............|............| - // y_final | | \- | xx*xxx.........|............| - // sy1 | | | | xxxxxB...|............| - // | | | | | | - // | | | | | | - // y_bottom +------------+------------+------------+------------+------------+ - // - // goal is to measure the area covered by '.' in each pixel - - // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 - // @TODO: maybe test against sy1 rather than y_bottom? - if (y_crossing > y_bottom) - y_crossing = y_bottom; - - sign = e->direction; - - // area of the rectangle covered from sy0..y_crossing - area = sign * (y_crossing-sy0); - - // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) - scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); - - // check if final y_crossing is blown up; no test case for this - if (y_final > y_bottom) { - y_final = y_bottom; - dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom - } - - // in second pixel, area covered by line segment found in first pixel - // is always a rectangle 1 wide * the height of that line segment; this - // is exactly what the variable 'area' stores. it also gets a contribution - // from the line segment within it. the THIRD pixel will get the first - // pixel's rectangle contribution, the second pixel's rectangle contribution, - // and its own contribution. the 'own contribution' is the same in every pixel except - // the leftmost and rightmost, a trapezoid that slides down in each pixel. - // the second pixel's contribution to the third pixel will be the - // rectangle 1 wide times the height change in the second pixel, which is dy. - - step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, - // which multiplied by 1-pixel-width is how much pixel area changes for each step in x - // so the area advances by 'step' every time - - for (x = x1+1; x < x2; ++x) { - scanline[x] += area + step/2; // area of trapezoid is 1*step/2 - area += step; - } - STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down - STBTT_assert(sy1 > y_final-0.01f); - - // area covered in the last pixel is the rectangle from all the pixels to the left, - // plus the trapezoid filled by the line segment in this pixel all the way to the right edge - scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); - - // the rest of the line is filled based on the total height of the line segment in this pixel - scanline_fill[x2] += sign * (sy1-sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - // note though that this does happen some of the time because - // x_top and x_bottom can be extrapolated at the top & bottom of - // the shape and actually lie outside the bounding box - int x; - for (x=0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float) (x); - float x2 = (float) (x+1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x+1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float) (off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge **step = &active; - - STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - if (j == 0 && off_y != 0) { - if (z->ey < scan_y_top) { - // this can happen due to subpixel positioning and some kind of fp rounding error i think - z->ey = scan_y_top; - } - } - STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i=0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float) STBTT_fabs(k)*255 + 0.5f; - m = (int) k; - if (m > 255) m = 255; - result->pixels[j*result->stride + i] = (unsigned char) m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge *z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) -{ - int i,j; - for (i=1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge *b = &p[j-1]; - int c = STBTT__COMPARE(a,b); - if (!c) break; - p[j] = p[j-1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) -{ - /* threshold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01,c12,c,m,i,j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0],&p[m]); - c12 = STBTT__COMPARE(&p[m],&p[n-1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0],&p[n-1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n-1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i=1; - j=n-1; - for(;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;;++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) break; - } - for (;;--j) { - if (!STBTT__COMPARE(&p[0], &p[j])) break; - } - /* make sure we haven't crossed */ - if (i >= j) break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n-i)) { - stbtt__sort_edges_quicksort(p,j); - p = p+i; - n = n-i; - } else { - stbtt__sort_edges_quicksort(p+i, n-i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge *p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1-x0; - float dy0 = y1-y0; - float dx1 = x2-x1; - float dy1 = y2-y1; - float dx2 = x3-x2; - float dy2 = y3-y2; - float dx = x3-x0; - float dy = y3-y0; - float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); - float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); - float flatness_squared = longlen*longlen-shortlen*shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0+x1)/2; - float y01 = (y0+y1)/2; - float x12 = (x1+x2)/2; - float y12 = (y1+y2)/2; - float x23 = (x2+x3)/2; - float y23 = (y2+y3)/2; - - float xa = (x01+x12)/2; - float ya = (y01+y12)/2; - float xb = (x12+x23)/2; - float yb = (y12+y23)/2; - - float mx = (xa+xb)/2; - float my = (ya+yb)/2; - - stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x3,y3); - *num_points = *num_points+1; - } -} - -// returns number of contours -static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].cx1, vertices[i].cy1, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int *winding_lengths = NULL; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 1; - if (y+gh+1 > bottom_y) - bottom_y = y+gh+1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - int width,height; - int x,y,bottom_y; -} stbrp_context; - -typedef struct -{ - unsigned char x; -} stbrp_node; - -struct stbrp_rect -{ - stbrp_coord x,y; - int id,w,h,was_packed; -}; - -static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) -{ - int i; - for (i=0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for ( ; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) -{ - stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); - int num_nodes = pw - padding; - stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) STBTT_free(context, alloc_context); - if (nodes != NULL) STBTT_free(nodes , alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - spc->skip_missing = 0; - - stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) -{ - STBTT_free(spc->nodes , spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) -{ - spc->skip_missing = skip; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) - -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char) (total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i*stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k; - int missing_glyph_added = 0; - - k=0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char) spc->h_oversample; - ranges[i].v_oversample = (unsigned char) spc->v_oversample; - for (j=0; j < ranges[i].num_chars; ++j) { - int x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { - rects[k].w = rects[k].h = 0; - } else { - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); - if (glyph == 0) - missing_glyph_added = 1; - } - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, - output, - out_w - (prefilter_x - 1), - out_h - (prefilter_y - 1), - out_stride, - scale_x, - scale_y, - shift_x, - shift_y, - glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k, missing_glyph = -1, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h,recip_v,sub_x,sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j=0; j < ranges[i].num_chars; ++j) { - stbrp_rect *r = &rects[k]; - if (r->was_packed && r->w != 0 && r->h != 0) { - stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord) spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - &x0,&y0,&x1,&y1); - stbtt_MakeGlyphBitmapSubpixel(info, - spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w - spc->h_oversample+1, - r->h - spc->v_oversample+1, - spc->stride_in_bytes, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16) r->x; - bc->y0 = (stbtt_int16) r->y; - bc->x1 = (stbtt_int16) (r->x + r->w); - bc->y1 = (stbtt_int16) (r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float) x0 * recip_h + sub_x; - bc->yoff = (float) y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - - if (glyph == 0) - missing_glyph = j; - } else if (spc->skip_missing) { - return_value = 0; - } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { - ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i,j,n, return_value = 1; - //stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect *rects; - - // flag all characters as NOT packed - for (i=0; i < num_ranges; ++i) - for (j=0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = - ranges[i].chardata_for_range[j].y0 = - ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i=0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) -{ - int i_ascent, i_descent, i_lineGap; - float scale; - stbtt_fontinfo info; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); - scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); - stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); - *ascent = (float) i_ascent * scale; - *descent = (float) i_descent * scale; - *lineGap = (float) i_lineGap * scale; -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar *b = chardata + char_index; - - if (align_to_integer) { - float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) -{ - float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; - float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; - float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; - float roperp = orig[1]*ray[0] - orig[0]*ray[1]; - - float a = q0perp - 2*q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b*b - a*c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float) STBTT_sqrt(discr); - s0 = (b+d) * rcpna; - s1 = (b-d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0]*rayn_x + q0[1]*rayn_y; - float q1d = q1[0]*rayn_x + q1[1]*rayn_y; - float q2d = q2[0]*rayn_x + q2[1]*rayn_y; - float rod = orig[0]*rayn_x + orig[1]*rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; - hits[0][1] = a*s0+b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; - hits[1][1] = a*s1+b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float *a, float *b) -{ - return (a[0] == b[0] && a[1] == b[1]); -} - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - // make sure y never passes through a vertex of the shape - y_frac = (float) STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - - orig[0] = x; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i=0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; - int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; - int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; - int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; - int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); - int by = STBTT_max(y0,STBTT_max(y1,y2)); - if (y > ay && y < by && x > ax) { - float q0[2],q1[2],q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; - x1 = (int)verts[i ].x; - y1 = (int)verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot( float x ) -{ - if (x<0) - return -(float) STBTT_pow(-x,1.0f/3.0f); - else - return (float) STBTT_pow( x,1.0f/3.0f); -} - -// x^3 + a*x^2 + b*x + c = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a*a / 3; - float q = a * (2*a*a - 9*b) / 27 + c; - float p3 = p*p*p; - float d = q*q + 4*p3 / 27; - if (d >= 0) { - float z = (float) STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float) STBTT_sqrt(-p/3); - float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float) STBTT_cos(v); - float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? - //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0,iy0,ix1,iy1; - int w,h; - unsigned char *data; - - if (scale == 0) return NULL; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width ) *width = w; - if (height) *height = h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - int x,y,i,j; - float *precompute; - stbtt_vertex *verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); - precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i=0,j=num_verts-1; i < num_verts; j=i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; - float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; - float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; - float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float len2 = bx*bx + by*by; - if (len2 != 0.0f) - precompute[i] = 1.0f / (bx*bx + by*by); - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y=iy0; y < iy1; ++y) { - for (x=ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float) x + 0.5f; - float sy = (float) y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path - - for (i=0; i < num_verts; ++i) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - - if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { - float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; - - float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - // coarse culling against bbox - //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1-x0, dy = y1-y0; - float px = x0-sx, py = y0-sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px*dx + py*dy) / (dx*dx + dy*dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; - float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; - float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); - float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); - float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); - float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { - int num=0; - float ax = x1-x0, ay = y1-y0; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3] = {0.f,0.f,0.f}; - float px,py,t,it,dist2; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3*(ax*bx + ay*by); - float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); - float c = mx*ax+my*ay; - if (a == 0.0) { // if a is 0, it's linear - if (b != 0.0) { - res[num++] = -c/b; - } - } else { - float discriminant = b*b - 4*a*c; - if (discriminant < 0) - num = 0; - else { - float root = (float) STBTT_sqrt(discriminant); - res[0] = (-b - root)/(2*a); - res[1] = (-b + root)/(2*a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; - float d = (mx*ax+my*ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, - float pixel_height, unsigned char *pixels, int pw, int ph, - int first_char, int num_chars, stbtt_bakedchar *chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char *) data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char *) data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - - -// FULL VERSION HISTORY -// -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/test/stbttf.h b/test/stbttf.h deleted file mode 100644 index e28607d..0000000 --- a/test/stbttf.h +++ /dev/null @@ -1,212 +0,0 @@ -#ifndef __STBTTF_H__ -#define __STBTTF_H__ - -#include - -#include "stb_rect_pack.h" -#include "stb_truetype.h" - -/* STBTTF: A quick and dirty SDL2 text renderer based on stb_truetype and stdb_rect_pack. - * Benoit Favre 2019 - * - * This header-only addon to the stb_truetype library allows to draw text with SDL2 from - * TTF fonts with a similar API to SDL_TTF without the bloat. - * The renderer is however limited by the integral positioning of SDL blit functions. - * It also does not parse utf8 text and only prints ASCII characters. - * - * This code is public domain. - */ - -typedef struct { - stbtt_fontinfo* info; - stbtt_packedchar* chars; - SDL_Texture* atlas; - int texture_size; - float size; - float scale; - int ascent; - int baseline; -} STBTTF_Font; - -/* Release the memory and textures associated with a font */ -void STBTTF_CloseFont(STBTTF_Font* font); - -/* Open a TTF font given a SDL abstract IO handler, for a given renderer and a given font size. - * Returns NULL on failure. The font must be deallocated with STBTTF_CloseFont when not used anymore. - * This function creates a texture atlas with prerendered ASCII characters (32-128). - */ -STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size); - -/* Open a TTF font given a filename, for a given renderer and a given font size. - * Convinience function which calls STBTTF_OpenFontRW. - */ -STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size); - -/* Draw some text using the renderer draw color at location (x, y). - * Characters are copied from the texture atlas using the renderer SDL_RenderCopy function. - * Since that function only supports integral coordinates, the result is not great. - * Only ASCII characters (32 <= c < 128) are supported. Anything outside this range is ignored. - */ -void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text); - -/* Return the length in pixels of a text. - * You can get the height of a line by using font->baseline. - */ -float STBTTF_MeasureText(STBTTF_Font* font, const char *text); - -#ifdef STBTTF_IMPLEMENTATION - -void STBTTF_CloseFont(STBTTF_Font* font) { - if(font->atlas) SDL_DestroyTexture(font->atlas); - if(font->info) free(font->info); - if(font->chars) free(font->chars); - free(font); -} - -STBTTF_Font* STBTTF_OpenFontRW(SDL_Renderer* renderer, SDL_RWops* rw, float size) { - Sint64 file_size = SDL_RWsize(rw); - unsigned char* buffer = malloc(file_size); - if(SDL_RWread(rw, buffer, file_size, 1) != 1) return NULL; - SDL_RWclose(rw); - - STBTTF_Font* font = calloc(sizeof(STBTTF_Font), 1); - font->info = malloc(sizeof(stbtt_fontinfo)); - font->chars = malloc(sizeof(stbtt_packedchar) * 96); - - if(stbtt_InitFont(font->info, buffer, 0) == 0) { - free(buffer); - STBTTF_CloseFont(font); - return NULL; - } - - // fill bitmap atlas with packed characters - unsigned char* bitmap = NULL; - font->texture_size = 32; - while(1) { - bitmap = malloc(font->texture_size * font->texture_size); - stbtt_pack_context pack_context; - stbtt_PackBegin(&pack_context, bitmap, font->texture_size, font->texture_size, 0, 1, 0); - stbtt_PackSetOversampling(&pack_context, 1, 1); - if(!stbtt_PackFontRange(&pack_context, buffer, 0, size, 32, 95, font->chars)) { - // too small - free(bitmap); - stbtt_PackEnd(&pack_context); - font->texture_size *= 2; - } else { - stbtt_PackEnd(&pack_context); - break; - } - } - - // convert bitmap to texture - font->atlas = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, font->texture_size, font->texture_size); - SDL_SetTextureBlendMode(font->atlas, SDL_BLENDMODE_BLEND); - - Uint32* pixels = malloc(font->texture_size * font->texture_size * sizeof(Uint32)); - static SDL_PixelFormat* format = NULL; - if(format == NULL) format = SDL_AllocFormat(SDL_PIXELFORMAT_RGBA32); - for(int i = 0; i < font->texture_size * font->texture_size; i++) { - pixels[i] = SDL_MapRGBA(format, 0xff, 0xff, 0xff, bitmap[i]); - } - SDL_UpdateTexture(font->atlas, NULL, pixels, font->texture_size * sizeof(Uint32)); - free(pixels); - free(bitmap); - - // setup additional info - font->scale = stbtt_ScaleForPixelHeight(font->info, size); - stbtt_GetFontVMetrics(font->info, &font->ascent, 0, 0); - font->baseline = (int) (font->ascent * font->scale); - - free(buffer); - - return font; -} - -STBTTF_Font* STBTTF_OpenFont(SDL_Renderer* renderer, const char* filename, float size) { - SDL_RWops *rw = SDL_RWFromFile(filename, "rb"); - if(rw == NULL) return NULL; - return STBTTF_OpenFontRW(renderer, rw, size); -} - -void STBTTF_RenderText(SDL_Renderer* renderer, STBTTF_Font* font, float x, float y, const char *text) { - Uint8 r, g, b, a; - SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); - SDL_SetTextureColorMod(font->atlas, r, g, b); - SDL_SetTextureAlphaMod(font->atlas, a); - for(int i = 0; text[i]; i++) { - if (text[i] >= 32 && text[i] < 128) { - //if(i > 0) x += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; - - stbtt_packedchar* info = &font->chars[text[i] - 32]; - SDL_Rect src_rect = {info->x0, info->y0, info->x1 - info->x0, info->y1 - info->y0}; - SDL_Rect dst_rect = {x + info->xoff, y + info->yoff, info->x1 - info->x0, info->y1 - info->y0}; - - SDL_RenderCopy(renderer, font->atlas, &src_rect, &dst_rect); - x += info->xadvance; - } - } -} - -float STBTTF_MeasureText(STBTTF_Font* font, const char *text) { - float width = 0; - for(int i = 0; text[i]; i++) { - if (text[i] >= 32 && text[i] < 128) { - //if(i > 0) width += stbtt_GetCodepointKernAdvance(font->info, text[i - 1], text[i]) * font->scale; - - stbtt_packedchar* info = &font->chars[text[i] - 32]; - width += info->xadvance; - } - } - return width; -} - -/******************* - * Example program * - ******************* - -#include - -#define STB_RECT_PACK_IMPLEMENTATION -#define STB_TRUETYPE_IMPLEMENTATION -#define STBTTF_IMPLEMENTATION - -#include "stbttf.h" - -int main(int argc, char** argv) { - if(argc != 2) { - fprintf(stderr, "usage: %s \n", argv[0]); - exit(1); - } - SDL_Init(SDL_INIT_VIDEO); - SDL_Window* window = SDL_CreateWindow("stbttf", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 640, 480, SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL); - SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - SDL_RenderSetLogicalSize(renderer, 640, 480); - SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); - - STBTTF_Font* font = STBTTF_OpenFont(renderer, argv[1], 32); - - while(1) { - SDL_Event event; - while(SDL_PollEvent(&event)) { - if(event.type == SDL_QUIT) exit(0); - } - SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); - SDL_RenderClear(renderer); - // set color and render some text - SDL_SetRenderDrawColor(renderer, 128, 0, 0, 255); - STBTTF_RenderText(renderer, font, 50, 50, "This is a test"); - // render the atlas to check its content - //SDL_Rect dest = {0, 0, font->texturesize, font->texturesize}; - //SDL_RenderCopy(renderer, font->atlas, &dest, &dest); - SDL_RenderPresent(renderer); - SDL_Delay(1000 / 60); - } - STBTTF_CloseFont(font); - SDL_Quit(); -} - -*/ - -#endif - -#endif \ No newline at end of file diff --git a/text_rendering/.gitignore b/text_rendering/.gitignore deleted file mode 100644 index 94078d3..0000000 --- a/text_rendering/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -*.png -test -obj/** -objlist \ No newline at end of file diff --git a/text_rendering/Makefile b/text_rendering/Makefile deleted file mode 100644 index 0a04aff..0000000 --- a/text_rendering/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -CC = gcc -# instrumentation flags -INFLAGS = -fsanitize=address,builtin,undefined -LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL ${INFLAGS} -CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 -Wno-unused-function -fno-omit-frame-pointer ${INFLAGS} - -.PHONY: clean all -all: test - -font.o: font.c font.h stb_truetype.h stb_image_write.h util.h \ - generic_cache.h generic_hash.h -main.o: main.c ren.h util.h -ren.o: ren.c util.h font.h ren.h generic_stack.h -util.o: util.c util.h -test: font.o main.o ren.o util.o - ${CC} ${LDFLAGS} -o test font.o main.o ren.o util.o -clean: - rm -f test font.o main.o ren.o util.o diff --git a/text_rendering/box_fragshader.glsl b/text_rendering/box_fragshader.glsl deleted file mode 100644 index 5c1120c..0000000 --- a/text_rendering/box_fragshader.glsl +++ /dev/null @@ -1,8 +0,0 @@ -#version 330 core - -in vec4 col; - -void main() -{ - gl_FragColor = col; -} \ No newline at end of file diff --git a/text_rendering/box_vertshader.glsl b/text_rendering/box_vertshader.glsl deleted file mode 100644 index 468665a..0000000 --- a/text_rendering/box_vertshader.glsl +++ /dev/null @@ -1,19 +0,0 @@ -#version 330 core - -uniform ivec2 viewsize; - -layout(location = 0) in vec2 position; -layout(location = 2) in vec4 color; - -out vec4 col; - - -void main() -{ - vec2 v = vec2(float(viewsize.x), float(viewsize.y)); - vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); - vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); - - gl_Position = pos; - col = color; -} \ No newline at end of file diff --git a/text_rendering/font.c b/text_rendering/font.c deleted file mode 100644 index 845dc1a..0000000 --- a/text_rendering/font.c +++ /dev/null @@ -1,195 +0,0 @@ -#define _POSIX_C_SOURCE 200809l -#define STB_TRUETYPE_IMPLEMENTATION -#define STBTT_STATIC -#define STB_IMAGE_WRITE_IMPLEMENTATION - -#include -#include - -#include "font.h" -#include "stb_truetype.h" -#include "stb_image_write.h" -#include "util.h" - -#include "generic_cache.h" -static inline unsigned int hash(unsigned int code) -{ - // identity map the ascii range - return code < 128 ? code : hash_u32(code); -} -CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32) - - -#define UTF8(c) (c&0x80) -#define BDEPTH 1 -#define BORDER 4 - -// FIXME: as of now only monospaced fonts work correctly since no kerning information -// is stored - - -struct priv { - stbtt_fontinfo stb; - float scale; - int baseline; - unsigned char *bitmap; - struct cache c; -}; -#define PRIV(x) ((struct priv *)x->priv) - - -struct font_atlas * font_init(void) -{ - struct font_atlas *p = emalloc(sizeof(struct font_atlas)); - memset(p, 0, sizeof(struct font_atlas)); - p->priv = emalloc(sizeof(struct priv)); - memset(p->priv, 0, sizeof(struct priv)); - PRIV(p)->c = cache_init(); - return p; -} - - -// loads a font into memory, storing all the ASCII characters in the atlas, each font -// atlas structure holds glyphs of a specific size in pixels -// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall) -int font_load(struct font_atlas *atlas, const char *path, int size) -{ - if (!atlas || !path) - return -1; - - int err; - - dump_file(path, &(atlas->file), &(atlas->file_size)); - - err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0); - if (err == 0) return -1; - - int ascent, descent, linegap, baseline; - int x0,y0,x1,y1; - float scale; - stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap); - stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1); - scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size); - baseline = scale * -y0; - atlas->glyph_max_w = (scale*x1) - (scale*x0); - atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0); - atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); - memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); - PRIV(atlas)->baseline = atlas->glyph_max_h - baseline; - PRIV(atlas)->scale = scale; - PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); - // FIXME: make this a square atlas - atlas->width = atlas->glyph_max_w*CACHE_SIZE/4; - atlas->height = atlas->glyph_max_h*4; - atlas->size = size; - - // preallocate all ascii characters - for (char c = ' '; c <= '~'; c++) { - if (!font_get_glyph_texture(atlas, c, NULL)) - return -1; - } - - return 0; -} - - -int font_free(struct font_atlas *atlas) -{ - efree(atlas->atlas); - efree(atlas->file); - efree(PRIV(atlas)->bitmap); - cache_free(&PRIV(atlas)->c); - efree(atlas->priv); - efree(atlas); - return 0; -} - - -// TODO: time and take the median of the time it takes to generate the cache and -// the time it takes to draw the glyph -const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated) -{ - int _u = 0; - if (!updated) updated = &_u; - - const struct font_glyph *r; - if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) { - *updated = 0; - return r; - } - - *updated = 1; - // generate the sdf and put it into the cache - // TODO: generate the whole block at once - int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code); - int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base; - base = atlas->glyph_max_h - PRIV(atlas)->baseline; - stbtt_GetGlyphBitmapBoxSubpixel( - &PRIV(atlas)->stb, - idx, - PRIV(atlas)->scale, - PRIV(atlas)->scale, - 0,0, - &x0,&y0, - &x1, &y1); - gw = x1 - x0; - gh = y1 - y0; - stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l); - adv *= PRIV(atlas)->scale; - off_x = PRIV(atlas)->scale*l; - off_y = atlas->glyph_max_h+y0; - stbtt_MakeGlyphBitmapSubpixel( - &PRIV(atlas)->stb, - PRIV(atlas)->bitmap, - atlas->glyph_max_w, - atlas->glyph_max_h, - atlas->glyph_max_w, - PRIV(atlas)->scale, - PRIV(atlas)->scale, - 0, 0, - idx); - - // TODO: bounds check usign atlas height - // TODO: clear spot area in the atlas before writing on it - unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c); - unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h; - unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width; - unsigned int w = atlas->width; - - unsigned char *a = (void *)atlas->atlas; - - //printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y); - - for (int y = 0; y < gh; y++) { - for (int x = 0; x < gw; x++) { - int c, r; - r = (ty+y)*w; - c = tx+x; - a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x]; - } - } - - struct font_glyph g = { - .codepoint = code, - .u = tx, - .v = ty, - .w = gw, - .h = gh, - .x = off_x, - .y = off_y-base, - .a = adv, - }; - return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot); -} - - -void font_dump(const struct font_atlas *atlas, const char *path) -{ - stbi_write_png( - path, - atlas->width, - atlas->height, - BDEPTH, - atlas->atlas, - BDEPTH*atlas->width); -} diff --git a/text_rendering/font.h b/text_rendering/font.h deleted file mode 100644 index 8ce7a7d..0000000 --- a/text_rendering/font.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef _FONT_H -#define _FONT_H - -#include - -/* width and height of a glyph contain the kering advance - * (u,v) - * +-------------*---+ - - * | ^ | | ^ - * | |oy | | | - * | v | | | - * | .ii. | | | - * | @@@@@@. |<->| | - * | V@Mio@@o |adv| | - * | :i. V@V | | | - * | :oM@@M | | | - * | :@@@MM@M | | | - * | @@o o@M | | | - * |<->:@@. M@M | | | - * |ox @@@o@@@@ | | | - * | :M@@V:@@.| | v - * +-------------*---+ - - * |<------------->| - * w - */ -// TODO: the advance isn't unique for every pair of characters -struct font_glyph { - uint32_t codepoint; - uint32_t u, v; - uint16_t w, h, a, x, y; -}; - -struct font_atlas { - unsigned int width, height; - unsigned char *atlas; - unsigned int glyph_max_w, glyph_max_h; - int size; - int file_size; - char *file; - void *priv; -}; - - -struct font_atlas * font_init(void); -int font_load(struct font_atlas *atlas, const char *path, int size); -int font_free(struct font_atlas *atlas); -const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated); -void font_dump(const struct font_atlas *atlas, const char *path); - -#endif diff --git a/text_rendering/font_fragshader.glsl b/text_rendering/font_fragshader.glsl deleted file mode 100644 index 1e43b1a..0000000 --- a/text_rendering/font_fragshader.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// texture uv coordinate in texture space -in vec2 uv; -uniform sampler2DRect ts; - -const vec3 textcolor = vec3(1.0, 1.0, 1.0); - - -void main() -{ - //gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f); - gl_FragColor = vec4(textcolor, texture(ts, uv)); -} diff --git a/text_rendering/font_vertshader.glsl b/text_rendering/font_vertshader.glsl deleted file mode 100644 index 8d429cb..0000000 --- a/text_rendering/font_vertshader.glsl +++ /dev/null @@ -1,26 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// both position and and uv are in pixels, they where converted to floats when -// passed to the shader -layout(location = 0) in vec2 position; -layout(location = 1) in vec2 txcoord; - -out vec2 uv; - -void main() -{ - vec2 v = vec2(float(viewsize.x), float(viewsize.y)); - -// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f); - vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); - vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); - - gl_Position = pos; - // since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to - // be normalized - uv = vec2(txcoord.x, txcoord.y); -} diff --git a/text_rendering/generic_cache.h b/text_rendering/generic_cache.h deleted file mode 100644 index 80c76a4..0000000 --- a/text_rendering/generic_cache.h +++ /dev/null @@ -1,126 +0,0 @@ -#ifndef CACHE_GENERIC_H -#define CACHE_GENERIC_H - - -#include "generic_hash.h" - -#define CACHE_SIZE 512 -#define CACHE_NCYCLES (CACHE_SIZE*2/3) -#define CACHE_BSIZE (((CACHE_SIZE+0x3f)&(~0x3f))>>6) -#define CACHE_BRESET(b) for (int i = 0; i < CACHE_BSIZE; b[i++] = 0); -#define CACHE_BSET(b, x) b[(x)>>6] |= (uint64_t)1<<((x)&63) -#define CACHE_BTEST(b, x) (b[(x)>>6]&((uint64_t)1<<((x)&63))) - -// FIXME: this cache implementation is not really generic since it expects an unsigned -// as the code and not a generic type - - -#define CACHE_CYCLE(c) \ -{ \ - if (++(c->cycles) > CACHE_NCYCLES) { \ - for (int i = 0; i < CACHE_BSIZE; i++) { \ - c->present[i] &= c->used[i]; \ - c->used[i] = 0; \ - } \ - c->cycles = 0; \ - } \ -} - - -#define CACHE_DECL(name, type, hashfn, cmpfn) \ -HASH_DECL(name##table, uint32_t, uint32_t, hashfn, cmpfn) \ -struct name { \ - struct name##table_ref *table; \ - type *array; \ - uint64_t *present, *used; \ - int cycles; \ -}; \ -\ -\ -/* FIXME: check for allocation errors */ \ -struct name name##_init(void) \ -{ \ - struct name##table_ref *t = name##table_create(CACHE_SIZE); \ - type *a = malloc(sizeof(type)*CACHE_SIZE); \ - uint64_t *p = malloc(sizeof(uint64_t)*CACHE_BSIZE); \ - uint64_t *u = malloc(sizeof(uint64_t)*CACHE_BSIZE); \ - CACHE_BRESET(p); \ - CACHE_BRESET(u); \ - return (struct name){.table=t, .array=a, .present=p, .used=u, 0}; \ -} \ -\ -\ -void name##_free(struct name *cache) \ -{ \ - if (cache) { \ - name##table_destroy(cache->table); \ - free(cache->array); \ - free(cache->present); \ - free(cache->used); \ - } \ -} \ -\ -\ -const type * name##_search(struct name *cache, uint32_t code) \ -{ \ - if (!cache) return NULL; \ - struct name##table_entry *r; \ - r = name##table_search(cache->table, code); \ - /* MISS */ \ - if (!r || !cmpfn(code, r->code)) \ - return NULL; \ - /* MISS, the data is not valid (not present) */ \ - if (!CACHE_BTEST(cache->present, r->data)) \ - return NULL; \ - /* HIT, set as recently used */ \ - CACHE_BSET(cache->used, r->data); \ - return (&cache->array[r->data]); \ -} \ -\ -\ -/* Look for a free spot in the present bitmap and return its index */ \ -/* If there is no free space left then just return the first position */ \ -int name##_get_free_spot(struct name *cache) \ -{ \ - if (!cache) return -1; \ - int x = 0; \ - for (int b = 0; b < CACHE_BSIZE; b++) { \ - if (cache->present[b] == 0) x = 64; \ - else x = __builtin_clzll(cache->present[b]); \ - x = 64-x; \ - if (!CACHE_BTEST(cache->present, x+64*b)) \ - return x+64*b; \ - } \ - return 0; \ -} \ -\ -\ -const type * name##_insert_at(struct name *cache, const type *g, uint32_t code, int x)\ -{ \ - if (!cache) return NULL; \ - type *spot = NULL; \ - /* check if the spot is valid */ \ - if (x < 0) return NULL; \ - /* Set used and present */ \ - CACHE_BSET(cache->present, x); \ - CACHE_BSET(cache->used, x); \ - CACHE_CYCLE(cache) \ - spot = &(cache->array[x]); \ - *spot = *g; \ - struct name##table_entry e = { .code = code, .data = x}; \ - if (!name##table_insert(cache->table, &e)) \ - return NULL; \ - return spot; \ -} \ -\ -\ -const type * name##_insert(struct name *cache, const type *g, uint32_t code, int *x)\ -{ \ - int y; \ - if (!x) x = &y; \ - *x = name##_get_free_spot(cache); \ - return name##_insert_at(cache, g, code, *x); \ -} \ - - -#endif diff --git a/text_rendering/genmake.sh b/text_rendering/genmake.sh deleted file mode 100755 index eac0f7b..0000000 --- a/text_rendering/genmake.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/sh - -genobj() { - #echo "$1" | sed -e 's/^/obj\//' -e 's/\.c/\.o/' - echo "$1" | sed -e 's/\.c/\.o/' -} - -genrule() { - #gcc -MM "$1" | sed 's/^/obj\//' - gcc -MM "$1" -} - - -rm -f objlist - -cat > Makefile << EOF -CC = gcc -# instrumentation flags -INFLAGS = -fsanitize=address,builtin,undefined -LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL \${INFLAGS} -CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 \ --Wno-unused-function -fno-omit-frame-pointer \${INFLAGS} - -.PHONY: clean all -all: test - -EOF - -for f in *.c; do - genrule $f >> Makefile - genobj $f >> objlist -done - -mainrule='test: ' -linkcmd=' ${CC} ${LDFLAGS} -o test ' -cleanrule='clean: - rm -f test ' -while IFS="" read -r line; do - mainrule="$mainrule $line" - linkcmd="$linkcmd $line" - cleanrule="$cleanrule $line" -done < objlist - -echo "$mainrule" >> Makefile -echo "$linkcmd" >> Makefile -echo "$cleanrule" >> Makefile \ No newline at end of file diff --git a/text_rendering/main.c b/text_rendering/main.c deleted file mode 100644 index d276d98..0000000 --- a/text_rendering/main.c +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "ren.h" -#include "util.h" - - -//const char *str1 = "Ciao Mamma!\nprova: òçà°ù§|¬³¼$£ì\t"; -const char *str1 = "ciao\tmamma"; -const char *str2 = "The quick brown fox jumps over the lazy dog\n" - "чащах юга жил бы цитрус? Да, но фальшивый экземпляр!\n" - "Pijamalı hasta, yağız şoföre çabucak güvendi\n" - "Pchnąć w tę łódź jeża lub ośm skrzyń fig\n" - "イロハニホヘト チリヌルヲ ワカヨタレソ ツネナラム ウヰノオクヤマ ケフコエテ アサキユメミシ ヱヒモセスン\n" - "Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa"; -SDL_Window *win; - - -#define red 0xff0000ff -#define blue 0xffff0000 -void draw(void) -{ - static unsigned int frame = 0; - printf("frame: %d\n", frame++); - ren_clear(); - //ren_render_box(10, 10, 400, 50, blue); - //if (ren_render_text(str1, 10, 10, 400, 50, 20)) - // printf("text: %s\n", ren_strerror()); - int w, h; - ren_get_text_box(str2, &w, &h, 20); - //printf("box for: %s -> (%d, %d)\n", str2, w, h); - ren_render_box(0, 0, w, h, blue); - ren_render_text(str2, 0, 0, w, h, 20); - - // fixme: this causes a bug - const char *s = "ciao mamma"; - ren_get_text_box(s, &w, &h, 12); - s = "stuff that was not in font size 12 -> чащаx"; - ren_render_box(0, 200, w, h, red); - if (ren_render_text(s, 0, 200, 0xffff, 0xffff, 12)) - printf("BUG\n"); - - SDL_GL_SwapWindow(win); -} - - -int main(void) -{ - setlocale(LC_ALL, "en_US.UTF-8"); - - SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS); - SDL_SetHint(SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR, "0"); - SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); - SDL_SetHint(SDL_HINT_VIDEO_HIGHDPI_DISABLED, "0"); - - win = SDL_CreateWindow( - "test render", - SDL_WINDOWPOS_UNDEFINED, - SDL_WINDOWPOS_UNDEFINED, - 500, - 500, - SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - if (ren_init(win)) { - printf("renderer init error: %s\n", ren_strerror()); - return 1; - } - - SDL_Event e; - while(1) { - SDL_WaitEvent(&e); - if (e.type == SDL_QUIT) - break; - if (e.type == SDL_WINDOWEVENT) { - switch (e.window.event) { - case SDL_WINDOWEVENT_RESIZED: - case SDL_WINDOWEVENT_SIZE_CHANGED: - ren_update_viewport(e.window.data1, e.window.data2); - draw(); - break; - case SDL_WINDOWEVENT_EXPOSED: - draw(); - break; - default: break; - } - } - } - - ren_free(); - SDL_DestroyWindow(win); - SDL_Quit(); - return 0; -} diff --git a/text_rendering/ren.c b/text_rendering/ren.c deleted file mode 100644 index 29022ba..0000000 --- a/text_rendering/ren.c +++ /dev/null @@ -1,826 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "util.h" -#include "font.h" -#include "ren.h" - - -#define GLERR(x) \ -{ \ - int a = glGetError(); \ - if (a != GL_NO_ERROR) \ - printf("(%s:%d %s:%s) glError: 0x%x %s\n", \ - __FILE__, __LINE__, __func__, x, a, glerr[a&0xff]); \ -} -#define GL(f) f; GLERR(#f) -#define REN_RET(a,b) {ren_errno = b; return a;} - - -enum REN_ERR { - REN_SUCCESS = 0, - REN_ERRNO, - REN_INVAL, - REN_VERTEX, - REN_FRAGMENT, - REN_PROGRAM, - REN_COMPILE, - REN_LINK, - REN_TEXTURE, - REN_CONTEXT, - REN_GLEW, - REN_FONT, - REN_BUFFER, - REN_UNIFORM, -}; - - -// TODO: make a macro for enum-associated string arrays -const char * ren_err_msg[] = { - [REN_SUCCESS] = "Success", - [REN_ERRNO] = "Look at errno", - [REN_INVAL] = "Invalid or NULL arguments", - [REN_VERTEX] = "Failed to create opengl vertex shader", - [REN_FRAGMENT] = "Failed to create opengl fragment shader", - [REN_PROGRAM] = "Failed to create opengl program", - [REN_COMPILE] = "Failed to compile shaders", - [REN_LINK] = "Failed to link shaders", - [REN_TEXTURE] = "Failed to create texture", - [REN_CONTEXT] = "Failed to create SDL OpenGL context", - [REN_GLEW] = "GLEW Error", - [REN_FONT] = "Font Error", - [REN_BUFFER] = "Failed to create opengl buffer", - [REN_UNIFORM] = "Failed to get uniform location", -}; - -#define ELEM(x) [x&0xff] = #x, -const char *glerr[] = { - ELEM(GL_INVALID_ENUM) - ELEM(GL_INVALID_VALUE) - ELEM(GL_INVALID_OPERATION) - ELEM(GL_OUT_OF_MEMORY) -}; -#undef ELEM - - -// different stacks -#include "generic_stack.h" -STACK_DECL(vtstack, struct v_text) -STACK_DECL(vcstack, struct v_col) - - -struct ren_font { - struct font_atlas *font; - GLuint texture; -}; - -struct { - SDL_GLContext *gl; - struct ren_font *fonts; - int fonts_no; - GLuint font_prog; - GLuint box_prog; - GLuint font_buffer; - GLuint box_buffer; - GLint viewsize_loc; - GLint box_viewsize_loc; - GLint texturesize_loc; - struct vtstack font_stack; - struct vcstack box_stack; - int width, height; - int s_x, s_y, s_w, s_h; - int tabsize; -} ren = {0}; - - -static int ren_errno; - - -// print shader compilation errors -static int shader_compile_error(GLuint shader, const char *path) -{ - GLint status; - GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status)) - if (status != GL_FALSE) - return 0; - - GLint log_length; - GL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length)) - - GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar)); - GL(glGetShaderInfoLog(shader, log_length, NULL, log_str)) - - const char *shader_type_str = NULL; - GLint shader_type; - GL(glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type)) - switch(shader_type) { - case GL_VERTEX_SHADER: shader_type_str = "vertex"; break; - case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break; - case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break; - } - - fprintf(stderr, "Compile failure in %s shader %s:\n%s\n", shader_type_str, path, log_str); - efree(log_str); - return -1; -} - - -// print shader link errors -static int shader_link_error(GLuint prog) -{ - GLint status; - GL(glGetProgramiv(prog, GL_LINK_STATUS, &status)) - if (status != GL_FALSE) - return 0; - - GLint log_length; - GL(glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length)) - - GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar)); - GL(glGetProgramInfoLog(prog, log_length, NULL, log_str)) - fprintf(stderr, "Linker failure: %s\n", log_str); - efree(log_str); - - return -1; -} - - -static GLuint shader_compile(const char *path, const char *shader, GLuint type) -{ - GLuint s; - // initialize the vertex shader and get the corresponding id - s = GL(glCreateShader(type)) - if (!s) REN_RET(0, REN_VERTEX) - // get the shader into opengl - GL(glShaderSource(s, 1, &shader, NULL)) - GL(glCompileShader(s)) - if (shader_compile_error(s, path)) - REN_RET(0, REN_COMPILE) - return s; -} - - -const char * ren_strerror(void) -{ - return ren_err_msg[ren_errno % (sizeof(ren_err_msg)/sizeof(char *))]; -} - - -// compile a vertex shader (vs_path) and a fragment shader (fs_path) into an opengl -// program and return it's index -static GLuint ren_compile_program(const char *vs_path, const char *fs_path) -{ - GLuint gl_vertshader, gl_fragshader, prog; - - if (!vs_path || !fs_path) - REN_RET(0, REN_INVAL) - - char *str = NULL; - - dump_file(vs_path, &str, NULL); - if (!str) REN_RET(0, REN_ERRNO) - gl_vertshader = shader_compile(vs_path, str, GL_VERTEX_SHADER); - efree(str); - if (!gl_vertshader) - return 0; - - dump_file(fs_path, &str, NULL); - if (!str) REN_RET(0, REN_ERRNO) - gl_fragshader = shader_compile(fs_path, str, GL_FRAGMENT_SHADER); - efree(str); - if (!gl_fragshader) - return 0; - - - // create the main program object, it is an amalgamation of all shaders - prog = GL(glCreateProgram()) - if (!prog) REN_RET(0, REN_PROGRAM) - - // attach the shaders to the program (set which shaders are present) - GL(glAttachShader(prog, gl_vertshader)) - GL(glAttachShader(prog, gl_fragshader)) - // then link the program (basically the linking stage of the program) - GL(glLinkProgram(prog)) - if (shader_link_error(prog)) - REN_RET(0, REN_LINK) - - // after linking the shaders can be detached and the source freed from - // memory since the program is ready to use - GL(glDetachShader(prog, gl_vertshader)) - GL(glDetachShader(prog, gl_fragshader)) - - return prog; -} - - -static void set_texture_parameters(GLuint type, GLuint wrap_s, GLuint wrap_t, GLuint upscale, GLuint downscale) -{ - GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s)) - GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t)) - GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, upscale)) - GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, downscale)) -} - - -static GLuint ren_texturergb_2d(const char *buf, int w, int h, int upscale, int downscale) -{ - GLuint t; - - if (!buf || w <= 0 || h <= 0) - REN_RET(0, REN_INVAL) - - GL(glGenTextures(1, &t)) - if (!t) REN_RET(0, REN_TEXTURE) - - GL(glBindTexture(GL_TEXTURE_2D, t)) - GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, buf)) - - set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale); - - return t; -} - - -static GLuint ren_texturergba_2d(const char *buf, int w, int h, int upscale, int downscale) -{ - GLuint t; - - if (!buf || w <= 0 || h <= 0) - REN_RET(0, REN_INVAL) - - GL(glGenTextures(1, &t)) - if (!t) REN_RET(0, REN_TEXTURE) - - GL(glBindTexture(GL_TEXTURE_2D, t)) - GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf)) - set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale); - - return t; -} - - -static GLuint ren_texturer_2d(const char *buf, int w, int h, int upscale, int downscale) -{ - GLuint t; - - if (!buf || w <= 0 || h <= 0) - REN_RET(0, REN_INVAL) - - GL(glGenTextures(1, &t)) - if (!t) REN_RET(0, REN_TEXTURE) - - GL(glBindTexture(GL_TEXTURE_2D, t)) - GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf)) - set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale); - - return t; -} - - -static GLuint ren_texturer_rect(const char *buf, int w, int h, int upscale, int downscale) -{ - GLuint t; - - if (!buf || w <= 0 || h <= 0) - REN_RET(0, REN_INVAL) - - GL(glGenTextures(1, &t)) - if (!t) REN_RET(0, REN_TEXTURE) - - GL(glBindTexture(GL_TEXTURE_RECTANGLE, t)) - GL(glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf)) - // a limitation of recatngle textures is that the wrapping mode is limited - // to either clamp-to-edge or clamp-to-border - set_texture_parameters(GL_TEXTURE_RECTANGLE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, upscale, downscale); - - return t; -} - - -// FIXME: update only the newly generated character instead of the whole texture -static int update_font_texture(int idx) -{ - GL(glUseProgram(ren.font_prog)) - GL(glTexSubImage2D( - GL_TEXTURE_RECTANGLE, - 0, 0, 0, - ren.fonts[idx].font->width, - ren.fonts[idx].font->height, - GL_RED, - GL_UNSIGNED_BYTE, - ren.fonts[idx].font->atlas)) - //font_dump(ren.fonts[idx].font, "./atlas.png"); - GL(glUseProgram(0)); - return 0; -} - - -// loads a font and returns it's index in the fonts array -static int ren_load_font(int size, const char *path) -{ - int idx = ren.fonts_no; - struct font_atlas *f; - ren.fonts = erealloc(ren.fonts, sizeof(struct ren_font)*(idx+1)); - ren.fonts[idx].font = font_init(); - f = ren.fonts[idx].font; - if (!f) - REN_RET(-1, REN_FONT) - - if (!path) - path = DEFAULT_FONT; - - if (font_load(f, path, size)) - REN_RET(-1, REN_FONT) - //font_dump(f, "./atlas.png"); - - // load font texture (atlas) - ren.fonts[idx].texture = ren_texturer_rect( - (const char *)f->atlas, - f->width, - f->height, - GL_LINEAR, GL_LINEAR); - if (!ren.fonts[idx].texture) - return -1; - - ren.fonts_no = idx+1; - return idx; -} - - -// returns the index to the ren.fonts array to the font with the correct size -// return -1 on errror -static int ren_get_font(int size) -{ - for (int i = 0; i < ren.fonts_no; i++) { - if (ren.fonts[i].font->size == size) - return i; - } - // TODO: add a way to change font family - return ren_load_font(size, NULL); -} - - -int ren_init(SDL_Window *w) -{ - // Initialize OpenGL - if (!w) - REN_RET(-1, REN_INVAL) - // using version 3 does not allow to use glVertexAttribPointer() without - // vertex buffer objects, so use compatibility mode - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - ren.gl = SDL_GL_CreateContext(w); - if (!ren.gl) { - printf("SDL: %s\n", SDL_GetError()); - REN_RET(-1, REN_CONTEXT) - } - - GL(glEnable(GL_BLEND)) - GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)) - GL(glDisable(GL_CULL_FACE)) - GL(glDisable(GL_DEPTH_TEST)) - GL(glEnable(GL_SCISSOR_TEST)) - GL(glEnable(GL_TEXTURE_2D)) - GL(glEnable(GL_TEXTURE_RECTANGLE)) - - GLenum glew_err = glewInit(); - if (glew_err != GLEW_OK) - REN_RET(glew_err, REN_GLEW); - - // Create stacks - ren.font_stack = vtstack_init(); - ren.box_stack = vcstack_init(); - // generate the font buffer object - GL(glGenBuffers(1, &ren.font_buffer)) - if (!ren.font_buffer) REN_RET(-1, REN_BUFFER) - GL(glGenBuffers(1, &ren.box_buffer)) - if (!ren.box_buffer) REN_RET(-1, REN_BUFFER) - - // Compile font shaders - ren.font_prog = ren_compile_program(FONT_VERSHADER, FONT_FRAGSHADER); - if (!ren.font_prog) return -1; - // create the uniforms, if the returned value is -1 then the uniform may have - // been optimized away, in any case do not return, just give a warning - ren.viewsize_loc = GL(glGetUniformLocation(ren.font_prog, "viewsize")) - if (ren.viewsize_loc == -1) - printf("uniform %s was optimized away\n", "viewsize"); - ren.texturesize_loc = GL(glGetUniformLocation(ren.font_prog, "texturesize")) - if (ren.texturesize_loc == -1) - printf("uniform %s was optimized away\n", "texturesize"); - - // Compile box shaders - ren.box_prog = ren_compile_program(BOX_VERSHADER, BOX_FRAGSHADER); - if (!ren.box_prog) return -1; - ren.box_viewsize_loc = GL(glGetUniformLocation(ren.box_prog, "viewsize")) - if (ren.box_viewsize_loc == -1) - printf("uniform %s was optimized away\n", "viewsize"); - - // Finishing touches - ren.tabsize = REN_TABSIZE; - int width, height; - SDL_GetWindowSize(w, &width, &height); - ren_update_viewport(width, height); - GL(glClearColor(0.3f, 0.3f, 0.3f, 0.f)) - GL(glClear(GL_COLOR_BUFFER_BIT)) - - return 0; -} - - -int ren_clear(void) -{ - GL(glScissor(0, 0, ren.width, ren.height)) - GL(glClear(GL_COLOR_BUFFER_BIT)); - return 0; -} - - -// idx refers to the fonts array index, this means that when drawing the font stack -// only one font size at the time can be used -static int ren_draw_font_stack(int idx) -{ - struct font_atlas *font = ren.fonts[idx].font; - GLuint font_texture = ren.fonts[idx].texture; - - GL(glUseProgram(ren.font_prog)) - GL(glBindTexture(GL_TEXTURE_RECTANGLE, font_texture)) - - GL(glViewport(0, 0, ren.width, ren.height)) - // this has caused me some trouble, convert from image coordiates to viewport - GL(glScissor(ren.s_x, ren.height-ren.s_y-ren.s_h, ren.s_w, ren.s_h)) - GL(glUniform2i(ren.viewsize_loc, ren.width, ren.height)) - GL(glUniform2i(ren.texturesize_loc, font->width, font->height)) - - GL(glBindBuffer(GL_ARRAY_BUFFER, ren.font_buffer)) - if (vtstack_changed(&ren.font_stack)) { - if (vtstack_size_changed(&ren.font_stack)) { - GL(glBufferData( - GL_ARRAY_BUFFER, - ren.font_stack.idx*sizeof(struct v_text), - ren.font_stack.items, - GL_DYNAMIC_DRAW)) - } else { - GL(glBufferSubData( - GL_ARRAY_BUFFER, - 0, - ren.font_stack.idx*sizeof(struct v_text), - ren.font_stack.items)) - } - } - // when passing ints to glVertexAttribPointer they are automatically - // converted to floats - GL(glVertexAttribPointer( - REN_VERTEX_IDX, - 2, - GL_INT, - GL_FALSE, - sizeof(struct v_text), - 0)) - GL(glVertexAttribPointer( - REN_UV_IDX, - 2, - GL_INT, - GL_FALSE, - sizeof(struct v_text), - (void*)sizeof(vec2_i))) - GL(glEnableVertexAttribArray(REN_VERTEX_IDX)) - GL(glEnableVertexAttribArray(REN_UV_IDX)) - - GL(glDrawArrays(GL_TRIANGLES, 0, ren.font_stack.idx)) - - GL(glDisableVertexAttribArray(REN_VERTEX_IDX)) - GL(glDisableVertexAttribArray(REN_UV_IDX)) - GL(glBindBuffer(GL_ARRAY_BUFFER, 0)) - GL(glBindTexture(GL_TEXTURE_RECTANGLE, 0)) - GL(glUseProgram(0)) - - vtstack_clear(&ren.font_stack); - return 0; -} - - -static int ren_draw_box_stack(void) -{ - GL(glUseProgram(ren.box_prog)) - - GL(glViewport(0, 0, ren.width, ren.height)) - GL(glScissor(0, 0, ren.width, ren.height)) - GL(glUniform2i(ren.box_viewsize_loc, ren.width, ren.height)) - - GL(glBindBuffer(GL_ARRAY_BUFFER, ren.box_buffer)) - if(vcstack_changed(&ren.box_stack)) { - if (vcstack_size_changed(&ren.box_stack)) { - GL(glBufferData( - GL_ARRAY_BUFFER, - ren.box_stack.idx*sizeof(struct v_col), - ren.box_stack.items, - GL_DYNAMIC_DRAW)) - } else { - GL(glBufferSubData( - GL_ARRAY_BUFFER, - 0, - ren.box_stack.idx*sizeof(struct v_col), - ren.box_stack.items)) - } - } - // when passing ints to glVertexAttribPointer they are automatically - // converted to floats - GL(glVertexAttribPointer( - REN_VERTEX_IDX, - 2, - GL_INT, - GL_FALSE, - sizeof(struct v_col), - 0)) - // the color gets normalized - GL(glVertexAttribPointer( - REN_COLOR_IDX, - 4, - GL_INT, - GL_TRUE, - sizeof(struct v_col), - (void*)sizeof(vec2_i))) - GL(glEnableVertexAttribArray(REN_VERTEX_IDX)) - GL(glEnableVertexAttribArray(REN_COLOR_IDX)) - - GL(glDrawArrays(GL_TRIANGLES, 0, ren.box_stack.idx)) - - GL(glDisableVertexAttribArray(REN_VERTEX_IDX)) - GL(glDisableVertexAttribArray(REN_COLOR_IDX)) - GL(glBindBuffer(GL_ARRAY_BUFFER, 0)) - GL(glUseProgram(0)) - - vcstack_clear(&ren.box_stack); - return 0; -} - - -int ren_update_viewport(int w, int h) -{ - ren.width = w; - ren.height = h; - return 0; -} - - -int ren_set_scissor(int x, int y, int w, int h) -{ - ren.s_x = x; - ren.s_y = y; - ren.s_w = w; - ren.s_h = h; - return 0; -} - - -static int ren_push_glyph(const struct font_glyph *g, int gx, int gy) -{ - /* x4,y4 x3,y3 - * o-------------+ - * |(x,y) /| - * | / | - * | 2 / | - * | / | - * | / | - * | / 1 | - * |/ | - * +-------------+ - * x1,y1 x2,y2 */ - struct v_text v; - struct font_glyph c; - c = *g; - //printf("g: u=%d v=%d w=%d h=%d a=%d x=%d y=%d\n", c.u, c.v, c.w, c.h, c.a, c.x, c.y); - //printf("v: x=%d y=%d u=%d v=%d\n", v.pos.x, v.pos.y, v.uv.u, v.uv.v); - // x1,y1 - v = (struct v_text){ - .pos = { .x = gx+c.x, .y = gy+c.y+c.h }, - .uv = { .u = c.u, .v = c.v+c.h }, - }; - vtstack_push(&ren.font_stack, &v); - // x2,y2 - v = (struct v_text){ - .pos = { .x = gx+c.x+c.w, .y = gy+c.y+c.h }, - .uv = { .u = c.u+c.w, .v = c.v+c.h }, - }; - vtstack_push(&ren.font_stack, &v); - // x3,y3 - v = (struct v_text){ - .pos = { .x = gx+c.x+c.w, .y = gy+c.y }, - .uv = { .u = c.u+c.w, .v = c.v }, - }; - vtstack_push(&ren.font_stack, &v); - // x1,y1 - v = (struct v_text){ - .pos = { .x = gx+c.x, .y = gy+c.y+c.h }, - .uv = { .u = c.u, .v = c.v+c.h }, - }; - vtstack_push(&ren.font_stack, &v); - // x3,y3 - v = (struct v_text){ - .pos = { .x = gx+c.x+c.w, .y = gy+c.y }, - .uv = { .u = c.u+c.w, .v = c.v }, - }; - vtstack_push(&ren.font_stack, &v); - // x4,y4 - v = (struct v_text){ - .pos = { .x = gx+c.x, .y = gy+c.y }, - .uv = { .u = c.u, .v = c.v }, - }; - vtstack_push(&ren.font_stack, &v); - - return 0; -} - - -static const struct font_glyph * get_glyph(unsigned int code, int idx) -{ - const struct font_glyph *g; - int updated; - g = font_get_glyph_texture(ren.fonts[idx].font, code, &updated); - if (!g) - REN_RET(NULL, REN_FONT); - if (updated) { - if (update_font_texture(idx)) - REN_RET(NULL, REN_TEXTURE); - } - return g; -} - - -// TODO: reduce repeating patterns in ren_get_text_box() and ren_render_text() -int ren_get_text_box(const char *str, int *rw, int *rh, int size) -{ - int w = 0, h = 0, x = 0, y = 0; - const struct font_glyph *g; - size_t off, ret; - uint32_t cp; - int idx = ren_get_font(size); - if (idx < 0) - return -1; - - h = y = ren.fonts[idx].font->glyph_max_h; - for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) { - if (iscntrl(cp)) goto skip_get; - if (!(g = get_glyph(cp, idx))) - return -1; - - x += g->x + g->a; - // FIXME: generalize this thing - skip_get: - switch (cp) { - case '\t': { - const struct font_glyph *sp = get_glyph(' ', idx); - if (!sp) return -1; - x += (sp->x + sp->a)*ren.tabsize; - } - break; - case '\r': - x = 0; - break; - case '\n': - // TODO: encode and/or store line height - y += ren.fonts[idx].font->glyph_max_h; - x = 0; - break; - default: break; - } - - if (x > w) w = x; - if (y > h) h = y; - } - - if (rw) *rw = w; - if (rh) *rh = h; - - return 0; -} - - -int ren_render_text(const char *str, int x, int y, int w, int h, int size) -{ - const struct font_glyph *g; - size_t ret, off; - uint32_t cp; - int gx = x, gy = y; - int idx = ren_get_font(size); - if (idx < 0) - return -1; - for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) { - // skip special characters that render a box (not present in font) - if (iscntrl(cp)) goto skip_render; - if (!(g = get_glyph(cp, idx))) - return -1; - - // only push the glyph if it is inside the bounding box - if (gx <= x+w && gy <= y+h) - ren_push_glyph(g, gx, gy); - // TODO: possible kerning needs to be applied here - // TODO: handle other unicode control characters such as the - // right-to-left isolate (\u2067) - gx += g->x + g->a; - skip_render: - switch (cp) { - case '\t': { - const struct font_glyph *sp = get_glyph(' ', idx); - if (!sp) return -1; - gx += (sp->x + sp->a)*ren.tabsize; - } - break; - case '\r': - gx = x; - break; - case '\n': - gy += ren.fonts[idx].font->glyph_max_h; - gx = x; - break; - default: break; - } - } - - ren_set_scissor(x, y, w, h); - ren_draw_font_stack(idx); - - return 0; -} - - -// re-normalize color from 0-255 to 0-0x7fffffff, technically i'm dividing by 256 here -#define RENORM(x) (((unsigned long long)(x)*0x7fffffff)>>8) -#define R(x) (x&0xff) -#define G(x) ((x>>8)&0xff) -#define B(x) ((x>>16)&0xff) -#define A(x) ((x>>24)&0xff) -static int ren_push_box(int x, int y, int w, int h, unsigned int color) -{ - /* x4,y4 x3,y3 - * o-------------+ - * |(x,y) /| - * | / | - * | 2 / | - * | / | - * | / | - * | / 1 | - * |/ | - * +-------------+ - * x1,y1 x2,y2 */ - struct v_col v; - vec4_i c = { - .r = RENORM(R(color)), - .g = RENORM(G(color)), - .b = RENORM(B(color)), - .a = RENORM(A(color)), - }; - // x1, y1 - v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c }; - vcstack_push(&ren.box_stack, &v); - // x2, y2 - v = (struct v_col){ .pos = { .x = x+w, .y = y+h }, .col = c }; - vcstack_push(&ren.box_stack, &v); - // x3, y3 - v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c }; - vcstack_push(&ren.box_stack, &v); - // x1, y1 - v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c }; - vcstack_push(&ren.box_stack, &v); - // x3, y3 - v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c }; - vcstack_push(&ren.box_stack, &v); - // x4, y4 - v = (struct v_col){ .pos = { .x = x, .y = y }, .col = c }; - vcstack_push(&ren.box_stack, &v); - - return 0; -} - - -int ren_render_box(int x, int y, int w, int h, unsigned int color) -{ - ren_push_box(x, y, w, h, color); - ren_draw_box_stack(); - return 0; -} - - -int ren_free(void) -{ - GL(glUseProgram(0)) - GL(glBindBuffer(GL_ARRAY_BUFFER, 0)) - GL(glDeleteProgram(ren.box_prog)); - GL(glDeleteProgram(ren.font_prog)); - GL(glDeleteBuffers(1, &ren.font_buffer)) - for (int i = 0; i < ren.fonts_no; i++) { - GL(glDeleteTextures(1, &ren.fonts[i].texture)) - font_free(ren.fonts[i].font); - } - SDL_GL_DeleteContext(ren.gl); - vtstack_free(&ren.font_stack); - vcstack_free(&ren.box_stack); - return 0; -} diff --git a/text_rendering/ren.h b/text_rendering/ren.h deleted file mode 100644 index 6219b34..0000000 --- a/text_rendering/ren.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef _RENDERER_H -#define _RENDERER_H - -#include - - -#define DEFAULT_FONT "/usr/share/fonts/TTF/FiraCode-Regular.ttf" -#define FONT_VERSHADER "./font_vertshader.glsl" -#define FONT_FRAGSHADER "./font_fragshader.glsl" -#define BOX_VERSHADER "./box_vertshader.glsl" -#define BOX_FRAGSHADER "./box_fragshader.glsl" -#define REN_VERTEX_IDX 0 -#define REN_UV_IDX 1 -#define REN_COLOR_IDX 2 -#define REN_TABSIZE 8 - - -typedef struct { - union { int x, u; }; - union { int y, v; }; -} vec2_i; - -typedef struct { - union { int x, r; }; - union { int y, g; }; - union { int z, b; }; - union { int w, a; }; -} vec4_i; - -// textured vertex -struct v_text { - vec2_i pos; - vec2_i uv; -}; - -// colored vertex -struct v_col { - vec2_i pos; - vec4_i col; -}; - - -int ren_init(SDL_Window *sdl_window); -int ren_free(void); -const char * ren_strerror(void); -int ren_update_viewport(int w, int h); -int ren_set_scissor(int x, int y, int w, int h); -int ren_get_text_box(const char *str, int *rw, int *rh, int size); -int ren_render_text(const char *str, int x, int y, int w, int h, int size); -int ren_render_box(int x, int y, int w, int h, unsigned int color); -int ren_clear(void); - - -#endif diff --git a/text_rendering/stb_image_write.h b/text_rendering/stb_image_write.h deleted file mode 100644 index e4b32ed..0000000 --- a/text_rendering/stb_image_write.h +++ /dev/null @@ -1,1724 +0,0 @@ -/* stb_image_write - v1.16 - public domain - http://nothings.org/stb - writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 - no warranty implied; use at your own risk - - Before #including, - - #define STB_IMAGE_WRITE_IMPLEMENTATION - - in the file that you want to have the implementation. - - Will probably not work correctly with strict-aliasing optimizations. - -ABOUT: - - This header file is a library for writing images to C stdio or a callback. - - The PNG output is not optimal; it is 20-50% larger than the file - written by a decent optimizing implementation; though providing a custom - zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. - This library is designed for source code compactness and simplicity, - not optimal image file size or run-time performance. - -BUILDING: - - You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. - You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace - malloc,realloc,free. - You can #define STBIW_MEMMOVE() to replace memmove() - You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function - for PNG compression (instead of the builtin one), it must have the following signature: - unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); - The returned data will be freed with STBIW_FREE() (free() by default), - so it must be heap allocated with STBIW_MALLOC() (malloc() by default), - -UNICODE: - - If compiling for Windows and you wish to use Unicode filenames, compile - with - #define STBIW_WINDOWS_UTF8 - and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert - Windows wchar_t filenames to utf8. - -USAGE: - - There are five functions, one for each image file format: - - int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); - int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); - int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); - - void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically - - There are also five equivalent functions that use an arbitrary write function. You are - expected to open/close your file-equivalent before and after calling these: - - int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); - int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); - int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); - int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - - where the callback is: - void stbi_write_func(void *context, void *data, int size); - - You can configure it with these global variables: - int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE - int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression - int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode - - - You can define STBI_WRITE_NO_STDIO to disable the file variant of these - functions, so the library will not use stdio.h at all. However, this will - also disable HDR writing, because it requires stdio for formatted output. - - Each function returns 0 on failure and non-0 on success. - - The functions create an image file defined by the parameters. The image - is a rectangle of pixels stored from left-to-right, top-to-bottom. - Each pixel contains 'comp' channels of data stored interleaved with 8-bits - per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is - monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. - The *data pointer points to the first byte of the top-left-most pixel. - For PNG, "stride_in_bytes" is the distance in bytes from the first byte of - a row of pixels to the first byte of the next row of pixels. - - PNG creates output files with the same number of components as the input. - The BMP format expands Y to RGB in the file format and does not - output alpha. - - PNG supports writing rectangles of data even when the bytes storing rows of - data are not consecutive in memory (e.g. sub-rectangles of a larger image), - by supplying the stride between the beginning of adjacent rows. The other - formats do not. (Thus you cannot write a native-format BMP through the BMP - writer, both because it is in BGR order and because it may have padding - at the end of the line.) - - PNG allows you to set the deflate compression level by setting the global - variable 'stbi_write_png_compression_level' (it defaults to 8). - - HDR expects linear float data. Since the format is always 32-bit rgb(e) - data, alpha (if provided) is discarded, and for monochrome data it is - replicated across all three channels. - - TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed - data, set the global variable 'stbi_write_tga_with_rle' to 0. - - JPEG does ignore alpha channels in input data; quality is between 1 and 100. - Higher quality looks better but results in a bigger image. - JPEG baseline (no JPEG progressive). - -CREDITS: - - - Sean Barrett - PNG/BMP/TGA - Baldur Karlsson - HDR - Jean-Sebastien Guay - TGA monochrome - Tim Kelsey - misc enhancements - Alan Hickman - TGA RLE - Emmanuel Julien - initial file IO callback implementation - Jon Olick - original jo_jpeg.cpp code - Daniel Gibson - integrate JPEG, allow external zlib - Aarni Koskela - allow choosing PNG filter - - bugfixes: - github:Chribba - Guillaume Chereau - github:jry2 - github:romigrou - Sergio Gonzalez - Jonas Karlsson - Filip Wasil - Thatcher Ulrich - github:poppolopoppo - Patrick Boettcher - github:xeekworx - Cap Petschulat - Simon Rodriguez - Ivan Tikhonov - github:ignotion - Adam Schackart - Andrew Kensler - -LICENSE - - See end of file for license information. - -*/ - -#ifndef INCLUDE_STB_IMAGE_WRITE_H -#define INCLUDE_STB_IMAGE_WRITE_H - -#include - -// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' -#ifndef STBIWDEF -#ifdef STB_IMAGE_WRITE_STATIC -#define STBIWDEF static -#else -#ifdef __cplusplus -#define STBIWDEF extern "C" -#else -#define STBIWDEF extern -#endif -#endif -#endif - -#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations -STBIWDEF int stbi_write_tga_with_rle; -STBIWDEF int stbi_write_png_compression_level; -STBIWDEF int stbi_write_force_png_filter; -#endif - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality); - -#ifdef STBIW_WINDOWS_UTF8 -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input); -#endif -#endif - -typedef void stbi_write_func(void *context, void *data, int size); - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); - -STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); - -#endif//INCLUDE_STB_IMAGE_WRITE_H - -#ifdef STB_IMAGE_WRITE_IMPLEMENTATION - -#ifdef _WIN32 - #ifndef _CRT_SECURE_NO_WARNINGS - #define _CRT_SECURE_NO_WARNINGS - #endif - #ifndef _CRT_NONSTDC_NO_DEPRECATE - #define _CRT_NONSTDC_NO_DEPRECATE - #endif -#endif - -#ifndef STBI_WRITE_NO_STDIO -#include -#endif // STBI_WRITE_NO_STDIO - -#include -#include -#include -#include - -#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) -// ok -#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) -// ok -#else -#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." -#endif - -#ifndef STBIW_MALLOC -#define STBIW_MALLOC(sz) malloc(sz) -#define STBIW_REALLOC(p,newsz) realloc(p,newsz) -#define STBIW_FREE(p) free(p) -#endif - -#ifndef STBIW_REALLOC_SIZED -#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) -#endif - - -#ifndef STBIW_MEMMOVE -#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) -#endif - - -#ifndef STBIW_ASSERT -#include -#define STBIW_ASSERT(x) assert(x) -#endif - -#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) - -#ifdef STB_IMAGE_WRITE_STATIC -static int stbi_write_png_compression_level = 8; -static int stbi_write_tga_with_rle = 1; -static int stbi_write_force_png_filter = -1; -#else -int stbi_write_png_compression_level = 8; -int stbi_write_tga_with_rle = 1; -int stbi_write_force_png_filter = -1; -#endif - -static int stbi__flip_vertically_on_write = 0; - -STBIWDEF void stbi_flip_vertically_on_write(int flag) -{ - stbi__flip_vertically_on_write = flag; -} - -typedef struct -{ - stbi_write_func *func; - void *context; - unsigned char buffer[64]; - int buf_used; -} stbi__write_context; - -// initialize a callback-based context -static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) -{ - s->func = c; - s->context = context; -} - -#ifndef STBI_WRITE_NO_STDIO - -static void stbi__stdio_write(void *context, void *data, int size) -{ - fwrite(data,1,size,(FILE*) context); -} - -#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) -#ifdef __cplusplus -#define STBIW_EXTERN extern "C" -#else -#define STBIW_EXTERN extern -#endif -STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char *str, int cbmb, wchar_t *widestr, int cchwide); -STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t *widestr, int cchwide, char *str, int cbmb, const char *defchar, int *used_default); - -STBIWDEF int stbiw_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wchar_t* input) -{ - return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int) bufferlen, NULL, NULL); -} -#endif - -static FILE *stbiw__fopen(char const *filename, char const *mode) -{ - FILE *f; -#if defined(_WIN32) && defined(STBIW_WINDOWS_UTF8) - wchar_t wMode[64]; - wchar_t wFilename[1024]; - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename)/sizeof(*wFilename))) - return 0; - - if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode))) - return 0; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != _wfopen_s(&f, wFilename, wMode)) - f = 0; -#else - f = _wfopen(wFilename, wMode); -#endif - -#elif defined(_MSC_VER) && _MSC_VER >= 1400 - if (0 != fopen_s(&f, filename, mode)) - f=0; -#else - f = fopen(filename, mode); -#endif - return f; -} - -static int stbi__start_write_file(stbi__write_context *s, const char *filename) -{ - FILE *f = stbiw__fopen(filename, "wb"); - stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); - return f != NULL; -} - -static void stbi__end_write_file(stbi__write_context *s) -{ - fclose((FILE *)s->context); -} - -#endif // !STBI_WRITE_NO_STDIO - -typedef unsigned int stbiw_uint32; -typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; - -static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) -{ - while (*fmt) { - switch (*fmt++) { - case ' ': break; - case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); - s->func(s->context,&x,1); - break; } - case '2': { int x = va_arg(v,int); - unsigned char b[2]; - b[0] = STBIW_UCHAR(x); - b[1] = STBIW_UCHAR(x>>8); - s->func(s->context,b,2); - break; } - case '4': { stbiw_uint32 x = va_arg(v,int); - unsigned char b[4]; - b[0]=STBIW_UCHAR(x); - b[1]=STBIW_UCHAR(x>>8); - b[2]=STBIW_UCHAR(x>>16); - b[3]=STBIW_UCHAR(x>>24); - s->func(s->context,b,4); - break; } - default: - STBIW_ASSERT(0); - return; - } - } -} - -static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) -{ - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); -} - -static void stbiw__write_flush(stbi__write_context *s) -{ - if (s->buf_used) { - s->func(s->context, &s->buffer, s->buf_used); - s->buf_used = 0; - } -} - -static void stbiw__putc(stbi__write_context *s, unsigned char c) -{ - s->func(s->context, &c, 1); -} - -static void stbiw__write1(stbi__write_context *s, unsigned char a) -{ - if ((size_t)s->buf_used + 1 > sizeof(s->buffer)) - stbiw__write_flush(s); - s->buffer[s->buf_used++] = a; -} - -static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) -{ - int n; - if ((size_t)s->buf_used + 3 > sizeof(s->buffer)) - stbiw__write_flush(s); - n = s->buf_used; - s->buf_used = n+3; - s->buffer[n+0] = a; - s->buffer[n+1] = b; - s->buffer[n+2] = c; -} - -static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) -{ - unsigned char bg[3] = { 255, 0, 255}, px[3]; - int k; - - if (write_alpha < 0) - stbiw__write1(s, d[comp - 1]); - - switch (comp) { - case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case - case 1: - if (expand_mono) - stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp - else - stbiw__write1(s, d[0]); // monochrome TGA - break; - case 4: - if (!write_alpha) { - // composite against pink background - for (k = 0; k < 3; ++k) - px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; - stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); - break; - } - /* FALLTHROUGH */ - case 3: - stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); - break; - } - if (write_alpha > 0) - stbiw__write1(s, d[comp - 1]); -} - -static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) -{ - stbiw_uint32 zero = 0; - int i,j, j_end; - - if (y <= 0) - return; - - if (stbi__flip_vertically_on_write) - vdir *= -1; - - if (vdir < 0) { - j_end = -1; j = y-1; - } else { - j_end = y; j = 0; - } - - for (; j != j_end; j += vdir) { - for (i=0; i < x; ++i) { - unsigned char *d = (unsigned char *) data + (j*x+i)*comp; - stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); - } - stbiw__write_flush(s); - s->func(s->context, &zero, scanline_pad); - } -} - -static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) -{ - if (y < 0 || x < 0) { - return 0; - } else { - va_list v; - va_start(v, fmt); - stbiw__writefv(s, fmt, v); - va_end(v); - stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); - return 1; - } -} - -static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) -{ - if (comp != 4) { - // write RGB bitmap - int pad = (-x*3) & 3; - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, - "11 4 22 4" "4 44 22 444444", - 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header - 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header - } else { - // RGBA bitmaps need a v4 header - // use BI_BITFIELDS mode with 32bpp and alpha mask - // (straight BI_RGB with alpha mask doesn't work in most readers) - return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *)data,1,0, - "11 4 22 4" "4 44 22 444444 4444 4 444 444 444 444", - 'B', 'M', 14+108+x*y*4, 0, 0, 14+108, // file header - 108, x,y, 1,32, 3,0,0,0,0,0, 0xff0000,0xff00,0xff,0xff000000u, 0, 0,0,0, 0,0,0, 0,0,0, 0,0,0); // bitmap V4 header - } -} - -STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_bmp_core(&s, x, y, comp, data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_bmp_core(&s, x, y, comp, data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif //!STBI_WRITE_NO_STDIO - -static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) -{ - int has_alpha = (comp == 2 || comp == 4); - int colorbytes = has_alpha ? comp-1 : comp; - int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 - - if (y < 0 || x < 0) - return 0; - - if (!stbi_write_tga_with_rle) { - return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, - "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); - } else { - int i,j,k; - int jend, jdir; - - stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); - - if (stbi__flip_vertically_on_write) { - j = 0; - jend = y; - jdir = 1; - } else { - j = y-1; - jend = -1; - jdir = -1; - } - for (; j != jend; j += jdir) { - unsigned char *row = (unsigned char *) data + j * x * comp; - int len; - - for (i = 0; i < x; i += len) { - unsigned char *begin = row + i * comp; - int diff = 1; - len = 1; - - if (i < x - 1) { - ++len; - diff = memcmp(begin, row + (i + 1) * comp, comp); - if (diff) { - const unsigned char *prev = begin; - for (k = i + 2; k < x && len < 128; ++k) { - if (memcmp(prev, row + k * comp, comp)) { - prev += comp; - ++len; - } else { - --len; - break; - } - } - } else { - for (k = i + 2; k < x && len < 128; ++k) { - if (!memcmp(begin, row + k * comp, comp)) { - ++len; - } else { - break; - } - } - } - } - - if (diff) { - unsigned char header = STBIW_UCHAR(len - 1); - stbiw__write1(s, header); - for (k = 0; k < len; ++k) { - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); - } - } else { - unsigned char header = STBIW_UCHAR(len - 129); - stbiw__write1(s, header); - stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); - } - } - } - stbiw__write_flush(s); - } - return 1; -} - -STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_tga_core(&s, x, y, comp, (void *) data); -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -// ************************************************************************************************* -// Radiance RGBE HDR writer -// by Baldur Karlsson - -#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) - -#ifndef STBI_WRITE_NO_STDIO - -static void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) -{ - int exponent; - float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); - - if (maxcomp < 1e-32f) { - rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; - } else { - float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; - - rgbe[0] = (unsigned char)(linear[0] * normalize); - rgbe[1] = (unsigned char)(linear[1] * normalize); - rgbe[2] = (unsigned char)(linear[2] * normalize); - rgbe[3] = (unsigned char)(exponent + 128); - } -} - -static void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) -{ - unsigned char lengthbyte = STBIW_UCHAR(length+128); - STBIW_ASSERT(length+128 <= 255); - s->func(s->context, &lengthbyte, 1); - s->func(s->context, &databyte, 1); -} - -static void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) -{ - unsigned char lengthbyte = STBIW_UCHAR(length); - STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code - s->func(s->context, &lengthbyte, 1); - s->func(s->context, data, length); -} - -static void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) -{ - unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; - unsigned char rgbe[4]; - float linear[3]; - int x; - - scanlineheader[2] = (width&0xff00)>>8; - scanlineheader[3] = (width&0x00ff); - - /* skip RLE for images too small or large */ - if (width < 8 || width >= 32768) { - for (x=0; x < width; x++) { - switch (ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - s->func(s->context, rgbe, 4); - } - } else { - int c,r; - /* encode into scratch buffer */ - for (x=0; x < width; x++) { - switch(ncomp) { - case 4: /* fallthrough */ - case 3: linear[2] = scanline[x*ncomp + 2]; - linear[1] = scanline[x*ncomp + 1]; - linear[0] = scanline[x*ncomp + 0]; - break; - default: - linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; - break; - } - stbiw__linear_to_rgbe(rgbe, linear); - scratch[x + width*0] = rgbe[0]; - scratch[x + width*1] = rgbe[1]; - scratch[x + width*2] = rgbe[2]; - scratch[x + width*3] = rgbe[3]; - } - - s->func(s->context, scanlineheader, 4); - - /* RLE each component separately */ - for (c=0; c < 4; c++) { - unsigned char *comp = &scratch[width*c]; - - x = 0; - while (x < width) { - // find first run - r = x; - while (r+2 < width) { - if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) - break; - ++r; - } - if (r+2 >= width) - r = width; - // dump up to first run - while (x < r) { - int len = r-x; - if (len > 128) len = 128; - stbiw__write_dump_data(s, len, &comp[x]); - x += len; - } - // if there's a run, output it - if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd - // find next byte after run - while (r < width && comp[r] == comp[x]) - ++r; - // output run up to r - while (x < r) { - int len = r-x; - if (len > 127) len = 127; - stbiw__write_run_data(s, len, comp[x]); - x += len; - } - } - } - } - } -} - -static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) -{ - if (y <= 0 || x <= 0 || data == NULL) - return 0; - else { - // Each component is stored separately. Allocate scratch space for full output scanline. - unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); - int i, len; - char buffer[128]; - char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; - s->func(s->context, header, sizeof(header)-1); - -#ifdef __STDC_LIB_EXT1__ - len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#else - len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); -#endif - s->func(s->context, buffer, len); - - for(i=0; i < y; i++) - stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*x*(stbi__flip_vertically_on_write ? y-1-i : i)); - STBIW_FREE(scratch); - return 1; - } -} - -STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_hdr_core(&s, x, y, comp, (float *) data); -} - -STBIWDEF int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif // STBI_WRITE_NO_STDIO - - -////////////////////////////////////////////////////////////////////////////// -// -// PNG writer -// - -#ifndef STBIW_ZLIB_COMPRESS -// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() -#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) -#define stbiw__sbm(a) stbiw__sbraw(a)[0] -#define stbiw__sbn(a) stbiw__sbraw(a)[1] - -#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) -#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) -#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) - -#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) -#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) -#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) - -static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) -{ - int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; - void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); - STBIW_ASSERT(p); - if (p) { - if (!*arr) ((int *) p)[1] = 0; - *arr = (void *) ((int *) p + 2); - stbiw__sbm(*arr) = m; - } - return *arr; -} - -static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) -{ - while (*bitcount >= 8) { - stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); - *bitbuffer >>= 8; - *bitcount -= 8; - } - return data; -} - -static int stbiw__zlib_bitrev(int code, int codebits) -{ - int res=0; - while (codebits--) { - res = (res << 1) | (code & 1); - code >>= 1; - } - return res; -} - -static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) -{ - int i; - for (i=0; i < limit && i < 258; ++i) - if (a[i] != b[i]) break; - return i; -} - -static unsigned int stbiw__zhash(unsigned char *data) -{ - stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); - hash ^= hash << 3; - hash += hash >> 5; - hash ^= hash << 4; - hash += hash >> 17; - hash ^= hash << 25; - hash += hash >> 6; - return hash; -} - -#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) -#define stbiw__zlib_add(code,codebits) \ - (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) -#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) -// default huffman tables -#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) -#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) -#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) -#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) -#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) -#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) - -#define stbiw__ZHASH 16384 - -#endif // STBIW_ZLIB_COMPRESS - -STBIWDEF unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) -{ -#ifdef STBIW_ZLIB_COMPRESS - // user provided a zlib compress implementation, use that - return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); -#else // use builtin - static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; - static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; - static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; - static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; - unsigned int bitbuf=0; - int i,j, bitcount=0; - unsigned char *out = NULL; - unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); - if (hash_table == NULL) - return NULL; - if (quality < 5) quality = 5; - - stbiw__sbpush(out, 0x78); // DEFLATE 32K window - stbiw__sbpush(out, 0x5e); // FLEVEL = 1 - stbiw__zlib_add(1,1); // BFINAL = 1 - stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman - - for (i=0; i < stbiw__ZHASH; ++i) - hash_table[i] = NULL; - - i=0; - while (i < data_len-3) { - // hash next 3 bytes of data to be compressed - int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; - unsigned char *bestloc = 0; - unsigned char **hlist = hash_table[h]; - int n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32768) { // if entry lies within window - int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); - if (d >= best) { best=d; bestloc=hlist[j]; } - } - } - // when hash table entry is too long, delete half the entries - if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { - STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); - stbiw__sbn(hash_table[h]) = quality; - } - stbiw__sbpush(hash_table[h],data+i); - - if (bestloc) { - // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal - h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); - hlist = hash_table[h]; - n = stbiw__sbcount(hlist); - for (j=0; j < n; ++j) { - if (hlist[j]-data > i-32767) { - int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); - if (e > best) { // if next match is better, bail on current match - bestloc = NULL; - break; - } - } - } - } - - if (bestloc) { - int d = (int) (data+i - bestloc); // distance back - STBIW_ASSERT(d <= 32767 && best <= 258); - for (j=0; best > lengthc[j+1]-1; ++j); - stbiw__zlib_huff(j+257); - if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); - for (j=0; d > distc[j+1]-1; ++j); - stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); - if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); - i += best; - } else { - stbiw__zlib_huffb(data[i]); - ++i; - } - } - // write out final bytes - for (;i < data_len; ++i) - stbiw__zlib_huffb(data[i]); - stbiw__zlib_huff(256); // end of block - // pad with 0 bits to byte boundary - while (bitcount) - stbiw__zlib_add(0,1); - - for (i=0; i < stbiw__ZHASH; ++i) - (void) stbiw__sbfree(hash_table[i]); - STBIW_FREE(hash_table); - - // store uncompressed instead if compression was worse - if (stbiw__sbn(out) > data_len + 2 + ((data_len+32766)/32767)*5) { - stbiw__sbn(out) = 2; // truncate to DEFLATE 32K window and FLEVEL = 1 - for (j = 0; j < data_len;) { - int blocklen = data_len - j; - if (blocklen > 32767) blocklen = 32767; - stbiw__sbpush(out, data_len - j == blocklen); // BFINAL = ?, BTYPE = 0 -- no compression - stbiw__sbpush(out, STBIW_UCHAR(blocklen)); // LEN - stbiw__sbpush(out, STBIW_UCHAR(blocklen >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(~blocklen)); // NLEN - stbiw__sbpush(out, STBIW_UCHAR(~blocklen >> 8)); - memcpy(out+stbiw__sbn(out), data+j, blocklen); - stbiw__sbn(out) += blocklen; - j += blocklen; - } - } - - { - // compute adler32 on input - unsigned int s1=1, s2=0; - int blocklen = (int) (data_len % 5552); - j=0; - while (j < data_len) { - for (i=0; i < blocklen; ++i) { s1 += data[j+i]; s2 += s1; } - s1 %= 65521; s2 %= 65521; - j += blocklen; - blocklen = 5552; - } - stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s2)); - stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); - stbiw__sbpush(out, STBIW_UCHAR(s1)); - } - *out_len = stbiw__sbn(out); - // make returned pointer freeable - STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); - return (unsigned char *) stbiw__sbraw(out); -#endif // STBIW_ZLIB_COMPRESS -} - -static unsigned int stbiw__crc32(unsigned char *buffer, int len) -{ -#ifdef STBIW_CRC32 - return STBIW_CRC32(buffer, len); -#else - static unsigned int crc_table[256] = - { - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D - }; - - unsigned int crc = ~0u; - int i; - for (i=0; i < len; ++i) - crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; - return ~crc; -#endif -} - -#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) -#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); -#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) - -static void stbiw__wpcrc(unsigned char **data, int len) -{ - unsigned int crc = stbiw__crc32(*data - len - 4, len+4); - stbiw__wp32(*data, crc); -} - -static unsigned char stbiw__paeth(int a, int b, int c) -{ - int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); - if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); - if (pb <= pc) return STBIW_UCHAR(b); - return STBIW_UCHAR(c); -} - -// @OPTIMIZE: provide an option that always forces left-predict or paeth predict -static void stbiw__encode_png_line(unsigned char *pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char *line_buffer) -{ - static int mapping[] = { 0,1,2,3,4 }; - static int firstmap[] = { 0,1,0,5,6 }; - int *mymap = (y != 0) ? mapping : firstmap; - int i; - int type = mymap[filter_type]; - unsigned char *z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height-1-y : y); - int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; - - if (type==0) { - memcpy(line_buffer, z, width*n); - return; - } - - // first loop isn't optimized since it's just one pixel - for (i = 0; i < n; ++i) { - switch (type) { - case 1: line_buffer[i] = z[i]; break; - case 2: line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: line_buffer[i] = z[i] - (z[i-signed_stride]>>1); break; - case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-signed_stride],0)); break; - case 5: line_buffer[i] = z[i]; break; - case 6: line_buffer[i] = z[i]; break; - } - } - switch (type) { - case 1: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-n]; break; - case 2: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - z[i-signed_stride]; break; - case 3: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - ((z[i-n] + z[i-signed_stride])>>1); break; - case 4: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-signed_stride], z[i-signed_stride-n]); break; - case 5: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - (z[i-n]>>1); break; - case 6: for (i=n; i < width*n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; - } -} - -STBIWDEF unsigned char *stbi_write_png_to_mem(const unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) -{ - int force_filter = stbi_write_force_png_filter; - int ctype[5] = { -1, 0, 4, 2, 6 }; - unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; - unsigned char *out,*o, *filt, *zlib; - signed char *line_buffer; - int j,zlen; - - if (stride_bytes == 0) - stride_bytes = x * n; - - if (force_filter >= 5) { - force_filter = -1; - } - - filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; - line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } - for (j=0; j < y; ++j) { - int filter_type; - if (force_filter > -1) { - filter_type = force_filter; - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); - } else { // Estimate the best filter by running through all of them: - int best_filter = 0, best_filter_val = 0x7fffffff, est, i; - for (filter_type = 0; filter_type < 5; filter_type++) { - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); - - // Estimate the entropy of the line using this filter; the less, the better. - est = 0; - for (i = 0; i < x*n; ++i) { - est += abs((signed char) line_buffer[i]); - } - if (est < best_filter_val) { - best_filter_val = est; - best_filter = filter_type; - } - } - if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it - stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); - filter_type = best_filter; - } - } - // when we get here, filter_type contains the filter type, and line_buffer contains the data - filt[j*(x*n+1)] = (unsigned char) filter_type; - STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); - } - STBIW_FREE(line_buffer); - zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, stbi_write_png_compression_level); - STBIW_FREE(filt); - if (!zlib) return 0; - - // each tag requires 12 bytes of overhead - out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); - if (!out) return 0; - *out_len = 8 + 12+13 + 12+zlen + 12; - - o=out; - STBIW_MEMMOVE(o,sig,8); o+= 8; - stbiw__wp32(o, 13); // header length - stbiw__wptag(o, "IHDR"); - stbiw__wp32(o, x); - stbiw__wp32(o, y); - *o++ = 8; - *o++ = STBIW_UCHAR(ctype[n]); - *o++ = 0; - *o++ = 0; - *o++ = 0; - stbiw__wpcrc(&o,13); - - stbiw__wp32(o, zlen); - stbiw__wptag(o, "IDAT"); - STBIW_MEMMOVE(o, zlib, zlen); - o += zlen; - STBIW_FREE(zlib); - stbiw__wpcrc(&o, zlen); - - stbiw__wp32(o,0); - stbiw__wptag(o, "IEND"); - stbiw__wpcrc(&o,0); - - STBIW_ASSERT(o == out + *out_len); - - return out; -} - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) -{ - FILE *f; - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - - f = stbiw__fopen(filename, "wb"); - if (!f) { STBIW_FREE(png); return 0; } - fwrite(png, 1, len, f); - fclose(f); - STBIW_FREE(png); - return 1; -} -#endif - -STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) -{ - int len; - unsigned char *png = stbi_write_png_to_mem((const unsigned char *) data, stride_bytes, x, y, comp, &len); - if (png == NULL) return 0; - func(context, png, len); - STBIW_FREE(png); - return 1; -} - - -/* *************************************************************************** - * - * JPEG writer - * - * This is based on Jon Olick's jo_jpeg.cpp: - * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html - */ - -static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, - 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; - -static void stbiw__jpg_writeBits(stbi__write_context *s, int *bitBufP, int *bitCntP, const unsigned short *bs) { - int bitBuf = *bitBufP, bitCnt = *bitCntP; - bitCnt += bs[1]; - bitBuf |= bs[0] << (24 - bitCnt); - while(bitCnt >= 8) { - unsigned char c = (bitBuf >> 16) & 255; - stbiw__putc(s, c); - if(c == 255) { - stbiw__putc(s, 0); - } - bitBuf <<= 8; - bitCnt -= 8; - } - *bitBufP = bitBuf; - *bitCntP = bitCnt; -} - -static void stbiw__jpg_DCT(float *d0p, float *d1p, float *d2p, float *d3p, float *d4p, float *d5p, float *d6p, float *d7p) { - float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; - float z1, z2, z3, z4, z5, z11, z13; - - float tmp0 = d0 + d7; - float tmp7 = d0 - d7; - float tmp1 = d1 + d6; - float tmp6 = d1 - d6; - float tmp2 = d2 + d5; - float tmp5 = d2 - d5; - float tmp3 = d3 + d4; - float tmp4 = d3 - d4; - - // Even part - float tmp10 = tmp0 + tmp3; // phase 2 - float tmp13 = tmp0 - tmp3; - float tmp11 = tmp1 + tmp2; - float tmp12 = tmp1 - tmp2; - - d0 = tmp10 + tmp11; // phase 3 - d4 = tmp10 - tmp11; - - z1 = (tmp12 + tmp13) * 0.707106781f; // c4 - d2 = tmp13 + z1; // phase 5 - d6 = tmp13 - z1; - - // Odd part - tmp10 = tmp4 + tmp5; // phase 2 - tmp11 = tmp5 + tmp6; - tmp12 = tmp6 + tmp7; - - // The rotator is modified from fig 4-8 to avoid extra negations. - z5 = (tmp10 - tmp12) * 0.382683433f; // c6 - z2 = tmp10 * 0.541196100f + z5; // c2-c6 - z4 = tmp12 * 1.306562965f + z5; // c2+c6 - z3 = tmp11 * 0.707106781f; // c4 - - z11 = tmp7 + z3; // phase 5 - z13 = tmp7 - z3; - - *d5p = z13 + z2; // phase 6 - *d3p = z13 - z2; - *d1p = z11 + z4; - *d7p = z11 - z4; - - *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; -} - -static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { - int tmp1 = val < 0 ? -val : val; - val = val < 0 ? val-1 : val; - bits[1] = 1; - while(tmp1 >>= 1) { - ++bits[1]; - } - bits[0] = val & ((1<0)&&(DU[end0pos]==0); --end0pos) { - } - // end0pos = first element in reverse order !=0 - if(end0pos == 0) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - return DU[0]; - } - for(i = 1; i <= end0pos; ++i) { - int startpos = i; - int nrzeroes; - unsigned short bits[2]; - for (; DU[i]==0 && i<=end0pos; ++i) { - } - nrzeroes = i-startpos; - if ( nrzeroes >= 16 ) { - int lng = nrzeroes>>4; - int nrmarker; - for (nrmarker=1; nrmarker <= lng; ++nrmarker) - stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); - nrzeroes &= 15; - } - stbiw__jpg_calcBits(DU[i], bits); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes<<4)+bits[1]]); - stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); - } - if(end0pos != 63) { - stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); - } - return DU[0]; -} - -static int stbi_write_jpg_core(stbi__write_context *s, int width, int height, int comp, const void* data, int quality) { - // Constants that don't pollute global namespace - static const unsigned char std_dc_luminance_nrcodes[] = {0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0}; - static const unsigned char std_dc_luminance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_luminance_nrcodes[] = {0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d}; - static const unsigned char std_ac_luminance_values[] = { - 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, - 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, - 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, - 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, - 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, - 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, - 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - static const unsigned char std_dc_chrominance_nrcodes[] = {0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0}; - static const unsigned char std_dc_chrominance_values[] = {0,1,2,3,4,5,6,7,8,9,10,11}; - static const unsigned char std_ac_chrominance_nrcodes[] = {0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77}; - static const unsigned char std_ac_chrominance_values[] = { - 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, - 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, - 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, - 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, - 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, - 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, - 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa - }; - // Huffman tables - static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9}}; - static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11}}; - static const unsigned short YAC_HT[256][2] = { - {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const unsigned short UVAC_HT[256][2] = { - {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, - {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, - {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} - }; - static const int YQT[] = {16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, - 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99}; - static const int UVQT[] = {17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, - 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99}; - static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, - 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; - - int row, col, i, k, subsample; - float fdtbl_Y[64], fdtbl_UV[64]; - unsigned char YTable[64], UVTable[64]; - - if(!data || !width || !height || comp > 4 || comp < 1) { - return 0; - } - - quality = quality ? quality : 90; - subsample = quality <= 90 ? 1 : 0; - quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; - quality = quality < 50 ? 5000 / quality : 200 - quality * 2; - - for(i = 0; i < 64; ++i) { - int uvti, yti = (YQT[i]*quality+50)/100; - YTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (yti < 1 ? 1 : yti > 255 ? 255 : yti); - uvti = (UVQT[i]*quality+50)/100; - UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char) (uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); - } - - for(row = 0, k = 0; row < 8; ++row) { - for(col = 0; col < 8; ++col, ++k) { - fdtbl_Y[k] = 1 / (YTable [stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); - } - } - - // Write Headers - { - static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; - static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; - const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height>>8),STBIW_UCHAR(height),(unsigned char)(width>>8),STBIW_UCHAR(width), - 3,1,(unsigned char)(subsample?0x22:0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; - s->func(s->context, (void*)head0, sizeof(head0)); - s->func(s->context, (void*)YTable, sizeof(YTable)); - stbiw__putc(s, 1); - s->func(s->context, UVTable, sizeof(UVTable)); - s->func(s->context, (void*)head1, sizeof(head1)); - s->func(s->context, (void*)(std_dc_luminance_nrcodes+1), sizeof(std_dc_luminance_nrcodes)-1); - s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); - stbiw__putc(s, 0x10); // HTYACinfo - s->func(s->context, (void*)(std_ac_luminance_nrcodes+1), sizeof(std_ac_luminance_nrcodes)-1); - s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); - stbiw__putc(s, 1); // HTUDCinfo - s->func(s->context, (void*)(std_dc_chrominance_nrcodes+1), sizeof(std_dc_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); - stbiw__putc(s, 0x11); // HTUACinfo - s->func(s->context, (void*)(std_ac_chrominance_nrcodes+1), sizeof(std_ac_chrominance_nrcodes)-1); - s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); - s->func(s->context, (void*)head2, sizeof(head2)); - } - - // Encode 8x8 macroblocks - { - static const unsigned short fillBits[] = {0x7F, 7}; - int DCY=0, DCU=0, DCV=0; - int bitBuf=0, bitCnt=0; - // comp == 2 is grey+alpha (alpha is ignored) - int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; - const unsigned char *dataR = (const unsigned char *)data; - const unsigned char *dataG = dataR + ofsG; - const unsigned char *dataB = dataR + ofsB; - int x, y, pos; - if(subsample) { - for(y = 0; y < height; y += 16) { - for(x = 0; x < width; x += 16) { - float Y[256], U[256], V[256]; - for(row = y, pos = 0; row < y+16; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+16; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y+136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); - - // subsample U,V - { - float subU[64], subV[64]; - int yy, xx; - for(yy = 0, pos = 0; yy < 8; ++yy) { - for(xx = 0; xx < 8; ++xx, ++pos) { - int j = yy*32+xx*2; - subU[pos] = (U[j+0] + U[j+1] + U[j+16] + U[j+17]) * 0.25f; - subV[pos] = (V[j+0] + V[j+1] + V[j+16] + V[j+17]) * 0.25f; - } - } - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - } else { - for(y = 0; y < height; y += 8) { - for(x = 0; x < width; x += 8) { - float Y[64], U[64], V[64]; - for(row = y, pos = 0; row < y+8; ++row) { - // row >= height => use last input row - int clamped_row = (row < height) ? row : height - 1; - int base_p = (stbi__flip_vertically_on_write ? (height-1-clamped_row) : clamped_row)*width*comp; - for(col = x; col < x+8; ++col, ++pos) { - // if col >= width => use pixel from last input column - int p = base_p + ((col < width) ? col : (width-1))*comp; - float r = dataR[p], g = dataG[p], b = dataB[p]; - Y[pos]= +0.29900f*r + 0.58700f*g + 0.11400f*b - 128; - U[pos]= -0.16874f*r - 0.33126f*g + 0.50000f*b; - V[pos]= +0.50000f*r - 0.41869f*g - 0.08131f*b; - } - } - - DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); - DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); - DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); - } - } - } - - // Do the bit alignment of the EOI marker - stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); - } - - // EOI - stbiw__putc(s, 0xFF); - stbiw__putc(s, 0xD9); - - return 1; -} - -STBIWDEF int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s = { 0 }; - stbi__start_write_callbacks(&s, func, context); - return stbi_write_jpg_core(&s, x, y, comp, (void *) data, quality); -} - - -#ifndef STBI_WRITE_NO_STDIO -STBIWDEF int stbi_write_jpg(char const *filename, int x, int y, int comp, const void *data, int quality) -{ - stbi__write_context s = { 0 }; - if (stbi__start_write_file(&s,filename)) { - int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); - stbi__end_write_file(&s); - return r; - } else - return 0; -} -#endif - -#endif // STB_IMAGE_WRITE_IMPLEMENTATION - -/* Revision history - 1.16 (2021-07-11) - make Deflate code emit uncompressed blocks when it would otherwise expand - support writing BMPs with alpha channel - 1.15 (2020-07-13) unknown - 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels - 1.13 - 1.12 - 1.11 (2019-08-11) - - 1.10 (2019-02-07) - support utf8 filenames in Windows; fix warnings and platform ifdefs - 1.09 (2018-02-11) - fix typo in zlib quality API, improve STB_I_W_STATIC in C++ - 1.08 (2018-01-29) - add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter - 1.07 (2017-07-24) - doc fix - 1.06 (2017-07-23) - writing JPEG (using Jon Olick's code) - 1.05 ??? - 1.04 (2017-03-03) - monochrome BMP expansion - 1.03 ??? - 1.02 (2016-04-02) - avoid allocating large structures on the stack - 1.01 (2016-01-16) - STBIW_REALLOC_SIZED: support allocators with no realloc support - avoid race-condition in crc initialization - minor compile issues - 1.00 (2015-09-14) - installable file IO function - 0.99 (2015-09-13) - warning fixes; TGA rle support - 0.98 (2015-04-08) - added STBIW_MALLOC, STBIW_ASSERT etc - 0.97 (2015-01-18) - fixed HDR asserts, rewrote HDR rle logic - 0.96 (2015-01-17) - add HDR output - fix monochrome BMP - 0.95 (2014-08-17) - add monochrome TGA output - 0.94 (2014-05-31) - rename private functions to avoid conflicts with stb_image.h - 0.93 (2014-05-27) - warning fixes - 0.92 (2010-08-01) - casts to unsigned char to fix warnings - 0.91 (2010-07-17) - first public release - 0.90 first internal release -*/ - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/text_rendering/stb_truetype.h b/text_rendering/stb_truetype.h deleted file mode 100644 index bbf2284..0000000 --- a/text_rendering/stb_truetype.h +++ /dev/null @@ -1,5077 +0,0 @@ -// stb_truetype.h - v1.26 - public domain -// authored from 2009-2021 by Sean Barrett / RAD Game Tools -// -// ======================================================================= -// -// NO SECURITY GUARANTEE -- DO NOT USE THIS ON UNTRUSTED FONT FILES -// -// This library does no range checking of the offsets found in the file, -// meaning an attacker can use it to read arbitrary memory. -// -// ======================================================================= -// -// This library processes TrueType files: -// parse files -// extract glyph metrics -// extract glyph shapes -// render glyphs to one-channel bitmaps with antialiasing (box filter) -// render glyphs to one-channel SDF bitmaps (signed-distance field/function) -// -// Todo: -// non-MS cmaps -// crashproof on bad data -// hinting? (no longer patented) -// cleartype-style AA? -// optimize: use simple memory allocator for intermediates -// optimize: build edge-list directly from curves -// optimize: rasterize directly from curves? -// -// ADDITIONAL CONTRIBUTORS -// -// Mikko Mononen: compound shape support, more cmap formats -// Tor Andersson: kerning, subpixel rendering -// Dougall Johnson: OpenType / Type 2 font handling -// Daniel Ribeiro Maciel: basic GPOS-based kerning -// -// Misc other: -// Ryan Gordon -// Simon Glass -// github:IntellectualKitty -// Imanol Celaya -// Daniel Ribeiro Maciel -// -// Bug/warning reports/fixes: -// "Zer" on mollyrocket Fabian "ryg" Giesen github:NiLuJe -// Cass Everitt Martins Mozeiko github:aloucks -// stoiko (Haemimont Games) Cap Petschulat github:oyvindjam -// Brian Hook Omar Cornut github:vassvik -// Walter van Niftrik Ryan Griege -// David Gow Peter LaValle -// David Given Sergey Popov -// Ivan-Assen Ivanov Giumo X. Clanjor -// Anthony Pesch Higor Euripedes -// Johan Duparc Thomas Fields -// Hou Qiming Derek Vinyard -// Rob Loach Cort Stratton -// Kenney Phillis Jr. Brian Costabile -// Ken Voskuil (kaesve) -// -// VERSION HISTORY -// -// 1.26 (2021-08-28) fix broken rasterizer -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// variant PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// -// Full history can be found at the end of this file. -// -// LICENSE -// -// See end of file for license information. -// -// USAGE -// -// Include this file in whatever places need to refer to it. In ONE C/C++ -// file, write: -// #define STB_TRUETYPE_IMPLEMENTATION -// before the #include of this file. This expands out the actual -// implementation into that C/C++ file. -// -// To make the implementation private to the file that generates the implementation, -// #define STBTT_STATIC -// -// Simple 3D API (don't ship this, but it's fine for tools and quick start) -// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture -// stbtt_GetBakedQuad() -- compute quad to draw for a given char -// -// Improved 3D API (more shippable): -// #include "stb_rect_pack.h" -- optional, but you really want it -// stbtt_PackBegin() -// stbtt_PackSetOversampling() -- for improved quality on small fonts -// stbtt_PackFontRanges() -- pack and renders -// stbtt_PackEnd() -// stbtt_GetPackedQuad() -// -// "Load" a font file from a memory buffer (you have to keep the buffer loaded) -// stbtt_InitFont() -// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections -// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections -// -// Render a unicode codepoint to a bitmap -// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap -// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide -// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be -// -// Character advance/positioning -// stbtt_GetCodepointHMetrics() -// stbtt_GetFontVMetrics() -// stbtt_GetFontVMetricsOS2() -// stbtt_GetCodepointKernAdvance() -// -// Starting with version 1.06, the rasterizer was replaced with a new, -// faster and generally-more-precise rasterizer. The new rasterizer more -// accurately measures pixel coverage for anti-aliasing, except in the case -// where multiple shapes overlap, in which case it overestimates the AA pixel -// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If -// this turns out to be a problem, you can re-enable the old rasterizer with -// #define STBTT_RASTERIZER_VERSION 1 -// which will incur about a 15% speed hit. -// -// ADDITIONAL DOCUMENTATION -// -// Immediately after this block comment are a series of sample programs. -// -// After the sample programs is the "header file" section. This section -// includes documentation for each API function. -// -// Some important concepts to understand to use this library: -// -// Codepoint -// Characters are defined by unicode codepoints, e.g. 65 is -// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is -// the hiragana for "ma". -// -// Glyph -// A visual character shape (every codepoint is rendered as -// some glyph) -// -// Glyph index -// A font-specific integer ID representing a glyph -// -// Baseline -// Glyph shapes are defined relative to a baseline, which is the -// bottom of uppercase characters. Characters extend both above -// and below the baseline. -// -// Current Point -// As you draw text to the screen, you keep track of a "current point" -// which is the origin of each character. The current point's vertical -// position is the baseline. Even "baked fonts" use this model. -// -// Vertical Font Metrics -// The vertical qualities of the font, used to vertically position -// and space the characters. See docs for stbtt_GetFontVMetrics. -// -// Font Size in Pixels or Points -// The preferred interface for specifying font sizes in stb_truetype -// is to specify how tall the font's vertical extent should be in pixels. -// If that sounds good enough, skip the next paragraph. -// -// Most font APIs instead use "points", which are a common typographic -// measurement for describing font size, defined as 72 points per inch. -// stb_truetype provides a point API for compatibility. However, true -// "per inch" conventions don't make much sense on computer displays -// since different monitors have different number of pixels per -// inch. For example, Windows traditionally uses a convention that -// there are 96 pixels per inch, thus making 'inch' measurements have -// nothing to do with inches, and thus effectively defining a point to -// be 1.333 pixels. Additionally, the TrueType font data provides -// an explicit scale factor to scale a given font's glyphs to points, -// but the author has observed that this scale factor is often wrong -// for non-commercial fonts, thus making fonts scaled in points -// according to the TrueType spec incoherently sized in practice. -// -// DETAILED USAGE: -// -// Scale: -// Select how high you want the font to be, in points or pixels. -// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute -// a scale factor SF that will be used by all other functions. -// -// Baseline: -// You need to select a y-coordinate that is the baseline of where -// your text will appear. Call GetFontBoundingBox to get the baseline-relative -// bounding box for all characters. SF*-y0 will be the distance in pixels -// that the worst-case character could extend above the baseline, so if -// you want the top edge of characters to appear at the top of the -// screen where y=0, then you would set the baseline to SF*-y0. -// -// Current point: -// Set the current point where the first character will appear. The -// first character could extend left of the current point; this is font -// dependent. You can either choose a current point that is the leftmost -// point and hope, or add some padding, or check the bounding box or -// left-side-bearing of the first character to be displayed and set -// the current point based on that. -// -// Displaying a character: -// Compute the bounding box of the character. It will contain signed values -// relative to . I.e. if it returns x0,y0,x1,y1, -// then the character should be displayed in the rectangle from -// to = 32 && *text < 128) { - stbtt_aligned_quad q; - stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 - glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y0); - glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y0); - glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y1); - glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y1); - } - ++text; - } - glEnd(); -} -#endif -// -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program (this compiles): get a single bitmap, print as ASCII art -// -#if 0 -#include -#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation -#include "stb_truetype.h" - -char ttf_buffer[1<<25]; - -int main(int argc, char **argv) -{ - stbtt_fontinfo font; - unsigned char *bitmap; - int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); - - fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); - - stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); - bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); - - for (j=0; j < h; ++j) { - for (i=0; i < w; ++i) - putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); - putchar('\n'); - } - return 0; -} -#endif -// -// Output: -// -// .ii. -// @@@@@@. -// V@Mio@@o -// :i. V@V -// :oM@@M -// :@@@MM@M -// @@o o@M -// :@@. M@M -// @@@o@@@@ -// :M@@V:@@. -// -////////////////////////////////////////////////////////////////////////////// -// -// Complete program: print "Hello World!" banner, with bugs -// -#if 0 -char buffer[24<<20]; -unsigned char screen[20][79]; - -int main(int arg, char **argv) -{ - stbtt_fontinfo font; - int i,j,ascent,baseline,ch=0; - float scale, xpos=2; // leave a little padding in case the character extends left - char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness - - fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); - stbtt_InitFont(&font, buffer, 0); - - scale = stbtt_ScaleForPixelHeight(&font, 15); - stbtt_GetFontVMetrics(&font, &ascent,0,0); - baseline = (int) (ascent*scale); - - while (text[ch]) { - int advance,lsb,x0,y0,x1,y1; - float x_shift = xpos - (float) floor(xpos); - stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); - stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); - stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); - // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong - // because this API is really for baking character bitmaps into textures. if you want to render - // a sequence of characters, you really need to render each bitmap to a temp buffer, then - // "alpha blend" that into the working buffer - xpos += (advance * scale); - if (text[ch+1]) - xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); - ++ch; - } - - for (j=0; j < 20; ++j) { - for (i=0; i < 78; ++i) - putchar(" .:ioVM@"[screen[j][i]>>5]); - putchar('\n'); - } - - return 0; -} -#endif - - -////////////////////////////////////////////////////////////////////////////// -////////////////////////////////////////////////////////////////////////////// -//// -//// INTEGRATION WITH YOUR CODEBASE -//// -//// The following sections allow you to supply alternate definitions -//// of C library functions used by stb_truetype, e.g. if you don't -//// link with the C runtime library. - -#ifdef STB_TRUETYPE_IMPLEMENTATION - // #define your own (u)stbtt_int8/16/32 before including to override this - #ifndef stbtt_uint8 - typedef unsigned char stbtt_uint8; - typedef signed char stbtt_int8; - typedef unsigned short stbtt_uint16; - typedef signed short stbtt_int16; - typedef unsigned int stbtt_uint32; - typedef signed int stbtt_int32; - #endif - - typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; - typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; - - // e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h - #ifndef STBTT_ifloor - #include - #define STBTT_ifloor(x) ((int) floor(x)) - #define STBTT_iceil(x) ((int) ceil(x)) - #endif - - #ifndef STBTT_sqrt - #include - #define STBTT_sqrt(x) sqrt(x) - #define STBTT_pow(x,y) pow(x,y) - #endif - - #ifndef STBTT_fmod - #include - #define STBTT_fmod(x,y) fmod(x,y) - #endif - - #ifndef STBTT_cos - #include - #define STBTT_cos(x) cos(x) - #define STBTT_acos(x) acos(x) - #endif - - #ifndef STBTT_fabs - #include - #define STBTT_fabs(x) fabs(x) - #endif - - // #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h - #ifndef STBTT_malloc - #include - #define STBTT_malloc(x,u) ((void)(u),malloc(x)) - #define STBTT_free(x,u) ((void)(u),free(x)) - #endif - - #ifndef STBTT_assert - #include - #define STBTT_assert(x) assert(x) - #endif - - #ifndef STBTT_strlen - #include - #define STBTT_strlen(x) strlen(x) - #endif - - #ifndef STBTT_memcpy - #include - #define STBTT_memcpy memcpy - #define STBTT_memset memset - #endif -#endif - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// INTERFACE -//// -//// - -#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ -#define __STB_INCLUDE_STB_TRUETYPE_H__ - -#ifdef STBTT_STATIC -#define STBTT_DEF static -#else -#define STBTT_DEF extern -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -// private structure -typedef struct -{ - unsigned char *data; - int cursor; - int size; -} stbtt__buf; - -////////////////////////////////////////////////////////////////////////////// -// -// TEXTURE BAKING API -// -// If you use this API, you only have to call two functions ever. -// - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; -} stbtt_bakedchar; - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata); // you allocate this, it's num_chars long -// if return is positive, the first unused row of the bitmap -// if return is negative, returns the negative of the number of characters that fit -// if return is 0, no characters fit and no rows were used -// This uses a very crappy packing. - -typedef struct -{ - float x0,y0,s0,t0; // top-left - float x1,y1,s1,t1; // bottom-right -} stbtt_aligned_quad; - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier -// Call GetBakedQuad with char_index = 'character - first_char', and it -// creates the quad you need to draw and advances the current position. -// -// The coordinate system used assumes y increases downwards. -// -// Characters will extend both above and below the current position; -// see discussion of "BASELINE" above. -// -// It's inefficient; you might want to c&p it and optimize it. - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap); -// Query the font vertical metrics without having to create a font first. - - -////////////////////////////////////////////////////////////////////////////// -// -// NEW TEXTURE BAKING API -// -// This provides options for packing multiple fonts into one atlas, not -// perfectly but better than nothing. - -typedef struct -{ - unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap - float xoff,yoff,xadvance; - float xoff2,yoff2; -} stbtt_packedchar; - -typedef struct stbtt_pack_context stbtt_pack_context; -typedef struct stbtt_fontinfo stbtt_fontinfo; -#ifndef STB_RECT_PACK_VERSION -typedef struct stbrp_rect stbrp_rect; -#endif - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); -// Initializes a packing context stored in the passed-in stbtt_pack_context. -// Future calls using this context will pack characters into the bitmap passed -// in here: a 1-channel bitmap that is width * height. stride_in_bytes is -// the distance from one row to the next (or 0 to mean they are packed tightly -// together). "padding" is the amount of padding to leave between each -// character (normally you want '1' for bitmaps you'll use as textures with -// bilinear filtering). -// -// Returns 0 on failure, 1 on success. - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); -// Cleans up the packing context and frees all memory. - -#define STBTT_POINT_SIZE(x) (-(x)) - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); -// Creates character bitmaps from the font_index'th font found in fontdata (use -// font_index=0 if you don't know what that is). It creates num_chars_in_range -// bitmaps for characters with unicode values starting at first_unicode_char_in_range -// and increasing. Data for how to render them is stored in chardata_for_range; -// pass these to stbtt_GetPackedQuad to get back renderable quads. -// -// font_size is the full height of the character from ascender to descender, -// as computed by stbtt_ScaleForPixelHeight. To use a point size as computed -// by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() -// and pass that result as 'font_size': -// ..., 20 , ... // font max minus min y is 20 pixels tall -// ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall - -typedef struct -{ - float font_size; - int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint - int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints - int num_chars; - stbtt_packedchar *chardata_for_range; // output - unsigned char h_oversample, v_oversample; // don't set these, they're used internally -} stbtt_pack_range; - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); -// Creates character bitmaps from multiple ranges of characters stored in -// ranges. This will usually create a better-packed bitmap than multiple -// calls to stbtt_PackFontRange. Note that you can call this multiple -// times within a single PackBegin/PackEnd. - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); -// Oversampling a font increases the quality by allowing higher-quality subpixel -// positioning, and is especially valuable at smaller text sizes. -// -// This function sets the amount of oversampling for all following calls to -// stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given -// pack context. The default (no oversampling) is achieved by h_oversample=1 -// and v_oversample=1. The total number of pixels required is -// h_oversample*v_oversample larger than the default; for example, 2x2 -// oversampling requires 4x the storage of 1x1. For best results, render -// oversampled textures with bilinear filtering. Look at the readme in -// stb/tests/oversample for information about oversampled fonts -// -// To use with PackFontRangesGather etc., you must set it before calls -// call to PackFontRangesGatherRects. - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip); -// If skip != 0, this tells stb_truetype to skip any codepoints for which -// there is no corresponding glyph. If skip=0, which is the default, then -// codepoints without a glyph recived the font's "missing character" glyph, -// typically an empty box by convention. - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above - int char_index, // character to display - float *xpos, float *ypos, // pointers to current position in screen pixel space - stbtt_aligned_quad *q, // output: quad to draw - int align_to_integer); - -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); -// Calling these functions in sequence is roughly equivalent to calling -// stbtt_PackFontRanges(). If you more control over the packing of multiple -// fonts, or if you want to pack custom data into a font texture, take a look -// at the source to of stbtt_PackFontRanges() and create a custom version -// using these functions, e.g. call GatherRects multiple times, -// building up a single array of rects, then call PackRects once, -// then call RenderIntoRects repeatedly. This may result in a -// better packing than calling PackFontRanges multiple times -// (or it may not). - -// this is an opaque structure that you shouldn't mess with which holds -// all the context needed from PackBegin to PackEnd. -struct stbtt_pack_context { - void *user_allocator_context; - void *pack_info; - int width; - int height; - int stride_in_bytes; - int padding; - int skip_missing; - unsigned int h_oversample, v_oversample; - unsigned char *pixels; - void *nodes; -}; - -////////////////////////////////////////////////////////////////////////////// -// -// FONT LOADING -// -// - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); -// This function will determine the number of fonts in a font file. TrueType -// collection (.ttc) files may contain multiple fonts, while TrueType font -// (.ttf) files only contain one font. The number of fonts can be used for -// indexing with the previous function where the index is between zero and one -// less than the total fonts. If an error occurs, -1 is returned. - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); -// Each .ttf/.ttc file may have more than one font. Each font has a sequential -// index number starting from 0. Call this function to get the font offset for -// a given index; it returns -1 if the index is out of range. A regular .ttf -// file will only define one font and it always be at offset 0, so it will -// return '0' for index 0, and -1 for all other indices. - -// The following structure is defined publicly so you can declare one on -// the stack or as a global or etc, but you should treat it as opaque. -struct stbtt_fontinfo -{ - void * userdata; - unsigned char * data; // pointer to .ttf file - int fontstart; // offset of start of font - - int numGlyphs; // number of glyphs, needed for range checking - - int loca,head,glyf,hhea,hmtx,kern,gpos,svg; // table locations as offset from start of .ttf - int index_map; // a cmap mapping for our chosen character encoding - int indexToLocFormat; // format needed to map from glyph index to glyph - - stbtt__buf cff; // cff font data - stbtt__buf charstrings; // the charstring index - stbtt__buf gsubrs; // global charstring subroutines index - stbtt__buf subrs; // private charstring subroutines index - stbtt__buf fontdicts; // array of font dicts - stbtt__buf fdselect; // map from glyph to fontdict -}; - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); -// Given an offset into the file that defines a font, this function builds -// the necessary cached info for the rest of the system. You must allocate -// the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't -// need to do anything special to free it, because the contents are pure -// value data with no additional data structures. Returns 0 on failure. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER TO GLYPH-INDEX CONVERSIOn - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); -// If you're going to perform multiple operations on the same character -// and you want a speed-up, call this function with the character you're -// going to process, then use glyph-based functions instead of the -// codepoint-based functions. -// Returns 0 if the character codepoint is not defined in the font. - - -////////////////////////////////////////////////////////////////////////////// -// -// CHARACTER PROPERTIES -// - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose "height" is 'pixels' tall. -// Height is measured as the distance from the highest ascender to the lowest -// descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics -// and computing: -// scale = pixels / (ascent - descent) -// so if you prefer to measure height by the ascent only, use a similar calculation. - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); -// computes a scale factor to produce a font whose EM size is mapped to -// 'pixels' tall. This is probably what traditional APIs compute, but -// I'm not positive. - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); -// ascent is the coordinate above the baseline the font extends; descent -// is the coordinate below the baseline the font extends (i.e. it is typically negative) -// lineGap is the spacing between one row's descent and the next row's ascent... -// so you should advance the vertical position by "*ascent - *descent + *lineGap" -// these are expressed in unscaled coordinates, so you must multiply by -// the scale factor for a given size - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); -// analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 -// table (specific to MS/Windows TTF files). -// -// Returns 1 on success (table present), 0 on failure. - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); -// the bounding box around all possible characters - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); -// leftSideBearing is the offset from the current horizontal position to the left edge of the character -// advanceWidth is the offset from the current horizontal position to the next horizontal position -// these are expressed in unscaled coordinates - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); -// an additional amount to add to the 'advance' value between ch1 and ch2 - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); -// Gets the bounding box of the visible part of the glyph, in unscaled coordinates - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); -// as above, but takes one or more glyph indices for greater efficiency - -typedef struct stbtt_kerningentry -{ - int glyph1; // use stbtt_FindGlyphIndex - int glyph2; - int advance; -} stbtt_kerningentry; - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info); -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length); -// Retrieves a complete list of all of the kerning pairs provided by the font -// stbtt_GetKerningTable never writes more than table_length entries and returns how many entries it did write. -// The table will be sorted by (a.glyph1 == b.glyph1)?(a.glyph2 < b.glyph2):(a.glyph1 < b.glyph1) - -////////////////////////////////////////////////////////////////////////////// -// -// GLYPH SHAPES (you probably don't need these, but they have to go before -// the bitmaps for C declaration-order reasons) -// - -#ifndef STBTT_vmove // you can predefine these to use different values (but why?) - enum { - STBTT_vmove=1, - STBTT_vline, - STBTT_vcurve, - STBTT_vcubic - }; -#endif - -#ifndef stbtt_vertex // you can predefine this to use different values - // (we share this with other code at RAD) - #define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file - typedef struct - { - stbtt_vertex_type x,y,cx,cy,cx1,cy1; - unsigned char type,padding; - } stbtt_vertex; -#endif - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); -// returns non-zero if nothing is drawn for this glyph - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); -// returns # of vertices and fills *vertices with the pointer to them -// these are expressed in "unscaled" coordinates -// -// The shape is a series of contours. Each one starts with -// a STBTT_moveto, then consists of a series of mixed -// STBTT_lineto and STBTT_curveto segments. A lineto -// draws a line from previous endpoint to its x,y; a curveto -// draws a quadratic bezier from previous endpoint to -// its x,y, using cx,cy as the bezier control point. - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); -// frees the data allocated above - -STBTT_DEF unsigned char *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl); -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg); -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg); -// fills svg with the character's SVG data. -// returns data size or 0 if SVG not found. - -////////////////////////////////////////////////////////////////////////////// -// -// BITMAP RENDERING -// - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); -// frees the bitmap allocated below - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// allocates a large-enough single-channel 8bpp bitmap and renders the -// specified character/glyph at the specified scale into it, with -// antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). -// *width & *height are filled out with the width & height of the bitmap, -// which is stored left-to-right, top-to-bottom. -// -// xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); -// the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); -// the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap -// in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap -// is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the -// width and height and positioning info for it first. - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); -// same as stbtt_MakeCodepointBitmap, but you can specify a subpixel -// shift for the character - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); -// same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering -// is performed (see stbtt_PackSetOversampling) - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -// get the bbox of the bitmap centered around the glyph origin; so the -// bitmap width is ix1-ix0, height is iy1-iy0, and location to place -// the bitmap top left is (leftSideBearing*scale,iy0). -// (Note that the bitmap uses y-increases-down, but the shape uses -// y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); -// same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel -// shift for the character - -// the following functions are equivalent to the above functions, but operate -// on glyph indices instead of Unicode codepoints (for efficiency) -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); - - -// @TODO: don't expose this structure -typedef struct -{ - int w,h,stride; - unsigned char *pixels; -} stbtt__bitmap; - -// rasterize a shape with quadratic beziers into a bitmap -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into - float flatness_in_pixels, // allowable error of curve in pixels - stbtt_vertex *vertices, // array of vertices defining shape - int num_verts, // number of vertices in above array - float scale_x, float scale_y, // scale applied to input vertices - float shift_x, float shift_y, // translation applied to input vertices - int x_off, int y_off, // another translation applied to input - int invert, // if non-zero, vertically flip shape - void *userdata); // context for to STBTT_MALLOC - -////////////////////////////////////////////////////////////////////////////// -// -// Signed Distance Function (or Field) rendering - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); -// frees the SDF bitmap allocated below - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); -// These functions compute a discretized SDF field for a single character, suitable for storing -// in a single-channel texture, sampling with bilinear filtering, and testing against -// larger than some threshold to produce scalable fonts. -// info -- the font -// scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap -// glyph/codepoint -- the character to generate the SDF for -// padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), -// which allows effects like bit outlines -// onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) -// pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) -// if positive, > onedge_value is inside; if negative, < onedge_value is inside -// width,height -- output height & width of the SDF bitmap (including padding) -// xoff,yoff -- output origin of the character -// return value -- a 2D array of bytes 0..255, width*height in size -// -// pixel_dist_scale & onedge_value are a scale & bias that allows you to make -// optimal use of the limited 0..255 for your application, trading off precision -// and special effects. SDF values outside the range 0..255 are clamped to 0..255. -// -// Example: -// scale = stbtt_ScaleForPixelHeight(22) -// padding = 5 -// onedge_value = 180 -// pixel_dist_scale = 180/5.0 = 36.0 -// -// This will create an SDF bitmap in which the character is about 22 pixels -// high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled -// shape, sample the SDF at each pixel and fill the pixel if the SDF value -// is greater than or equal to 180/255. (You'll actually want to antialias, -// which is beyond the scope of this example.) Additionally, you can compute -// offset outlines (e.g. to stroke the character border inside & outside, -// or only outside). For example, to fill outside the character up to 3 SDF -// pixels, you would compare against (180-36.0*3)/255 = 72/255. The above -// choice of variables maps a range from 5 pixels outside the shape to -// 2 pixels inside the shape to 0..255; this is intended primarily for apply -// outside effects only (the interior range is needed to allow proper -// antialiasing of the font at *smaller* sizes) -// -// The function computes the SDF analytically at each SDF pixel, not by e.g. -// building a higher-res bitmap and approximating it. In theory the quality -// should be as high as possible for an SDF of this size & representation, but -// unclear if this is true in practice (perhaps building a higher-res bitmap -// and computing from that can allow drop-out prevention). -// -// The algorithm has not been optimized at all, so expect it to be slow -// if computing lots of characters or very large sizes. - - - -////////////////////////////////////////////////////////////////////////////// -// -// Finding the right font... -// -// You should really just solve this offline, keep your own tables -// of what font is what, and don't try to get it out of the .ttf file. -// That's because getting it out of the .ttf file is really hard, because -// the names in the file can appear in many possible encodings, in many -// possible languages, and e.g. if you need a case-insensitive comparison, -// the details of that depend on the encoding & language in a complex way -// (actually underspecified in truetype, but also gigantic). -// -// But you can use the provided functions in two possible ways: -// stbtt_FindMatchingFont() will use *case-sensitive* comparisons on -// unicode-encoded names to try to find the font you want; -// you can run this before calling stbtt_InitFont() -// -// stbtt_GetFontNameString() lets you get any of the various strings -// from the file yourself and do your own comparisons on them. -// You have to have called stbtt_InitFont() first. - - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); -// returns the offset (not index) of the font that matches, or -1 if none -// if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". -// if you use any other flag, use a font name like "Arial"; this checks -// the 'macStyle' header field; i don't know if fonts set this consistently -#define STBTT_MACSTYLE_DONTCARE 0 -#define STBTT_MACSTYLE_BOLD 1 -#define STBTT_MACSTYLE_ITALIC 2 -#define STBTT_MACSTYLE_UNDERSCORE 4 -#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); -// returns 1/0 whether the first string interpreted as utf8 is identical to -// the second string interpreted as big-endian utf16... useful for strings from next func - -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); -// returns the string (which may be big-endian double byte, e.g. for unicode) -// and puts the length in bytes in *length. -// -// some of the values for the IDs are below; for more see the truetype spec: -// http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html -// http://www.microsoft.com/typography/otspec/name.htm - -enum { // platformID - STBTT_PLATFORM_ID_UNICODE =0, - STBTT_PLATFORM_ID_MAC =1, - STBTT_PLATFORM_ID_ISO =2, - STBTT_PLATFORM_ID_MICROSOFT =3 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_UNICODE - STBTT_UNICODE_EID_UNICODE_1_0 =0, - STBTT_UNICODE_EID_UNICODE_1_1 =1, - STBTT_UNICODE_EID_ISO_10646 =2, - STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, - STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT - STBTT_MS_EID_SYMBOL =0, - STBTT_MS_EID_UNICODE_BMP =1, - STBTT_MS_EID_SHIFTJIS =2, - STBTT_MS_EID_UNICODE_FULL =10 -}; - -enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes - STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, - STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, - STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, - STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 -}; - -enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... - // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs - STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, - STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, - STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, - STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, - STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, - STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D -}; - -enum { // languageID for STBTT_PLATFORM_ID_MAC - STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, - STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, - STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, - STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , - STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , - STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, - STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 -}; - -#ifdef __cplusplus -} -#endif - -#endif // __STB_INCLUDE_STB_TRUETYPE_H__ - -/////////////////////////////////////////////////////////////////////////////// -/////////////////////////////////////////////////////////////////////////////// -//// -//// IMPLEMENTATION -//// -//// - -#ifdef STB_TRUETYPE_IMPLEMENTATION - -#ifndef STBTT_MAX_OVERSAMPLE -#define STBTT_MAX_OVERSAMPLE 8 -#endif - -#if STBTT_MAX_OVERSAMPLE > 255 -#error "STBTT_MAX_OVERSAMPLE cannot be > 255" -#endif - -typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; - -#ifndef STBTT_RASTERIZER_VERSION -#define STBTT_RASTERIZER_VERSION 2 -#endif - -#ifdef _MSC_VER -#define STBTT__NOTUSED(v) (void)(v) -#else -#define STBTT__NOTUSED(v) (void)sizeof(v) -#endif - -////////////////////////////////////////////////////////////////////////// -// -// stbtt__buf helpers to parse data from file -// - -static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor++]; -} - -static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) -{ - if (b->cursor >= b->size) - return 0; - return b->data[b->cursor]; -} - -static void stbtt__buf_seek(stbtt__buf *b, int o) -{ - STBTT_assert(!(o > b->size || o < 0)); - b->cursor = (o > b->size || o < 0) ? b->size : o; -} - -static void stbtt__buf_skip(stbtt__buf *b, int o) -{ - stbtt__buf_seek(b, b->cursor + o); -} - -static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) -{ - stbtt_uint32 v = 0; - int i; - STBTT_assert(n >= 1 && n <= 4); - for (i = 0; i < n; i++) - v = (v << 8) | stbtt__buf_get8(b); - return v; -} - -static stbtt__buf stbtt__new_buf(const void *p, size_t size) -{ - stbtt__buf r; - STBTT_assert(size < 0x40000000); - r.data = (stbtt_uint8*) p; - r.size = (int) size; - r.cursor = 0; - return r; -} - -#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) -#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) - -static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) -{ - stbtt__buf r = stbtt__new_buf(NULL, 0); - if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; - r.data = b->data + o; - r.size = s; - return r; -} - -static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) -{ - int count, start, offsize; - start = b->cursor; - count = stbtt__buf_get16(b); - if (count) { - offsize = stbtt__buf_get8(b); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(b, offsize * count); - stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); - } - return stbtt__buf_range(b, start, b->cursor - start); -} - -static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) -{ - int b0 = stbtt__buf_get8(b); - if (b0 >= 32 && b0 <= 246) return b0 - 139; - else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; - else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; - else if (b0 == 28) return stbtt__buf_get16(b); - else if (b0 == 29) return stbtt__buf_get32(b); - STBTT_assert(0); - return 0; -} - -static void stbtt__cff_skip_operand(stbtt__buf *b) { - int v, b0 = stbtt__buf_peek8(b); - STBTT_assert(b0 >= 28); - if (b0 == 30) { - stbtt__buf_skip(b, 1); - while (b->cursor < b->size) { - v = stbtt__buf_get8(b); - if ((v & 0xF) == 0xF || (v >> 4) == 0xF) - break; - } - } else { - stbtt__cff_int(b); - } -} - -static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) -{ - stbtt__buf_seek(b, 0); - while (b->cursor < b->size) { - int start = b->cursor, end, op; - while (stbtt__buf_peek8(b) >= 28) - stbtt__cff_skip_operand(b); - end = b->cursor; - op = stbtt__buf_get8(b); - if (op == 12) op = stbtt__buf_get8(b) | 0x100; - if (op == key) return stbtt__buf_range(b, start, end-start); - } - return stbtt__buf_range(b, 0, 0); -} - -static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) -{ - int i; - stbtt__buf operands = stbtt__dict_get(b, key); - for (i = 0; i < outcount && operands.cursor < operands.size; i++) - out[i] = stbtt__cff_int(&operands); -} - -static int stbtt__cff_index_count(stbtt__buf *b) -{ - stbtt__buf_seek(b, 0); - return stbtt__buf_get16(b); -} - -static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) -{ - int count, offsize, start, end; - stbtt__buf_seek(&b, 0); - count = stbtt__buf_get16(&b); - offsize = stbtt__buf_get8(&b); - STBTT_assert(i >= 0 && i < count); - STBTT_assert(offsize >= 1 && offsize <= 4); - stbtt__buf_skip(&b, i*offsize); - start = stbtt__buf_get(&b, offsize); - end = stbtt__buf_get(&b, offsize); - return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); -} - -////////////////////////////////////////////////////////////////////////// -// -// accessors to parse data from file -// - -// on platforms that don't allow misaligned reads, if we want to allow -// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE - -#define ttBYTE(p) (* (stbtt_uint8 *) (p)) -#define ttCHAR(p) (* (stbtt_int8 *) (p)) -#define ttFixed(p) ttLONG(p) - -static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } -static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } -static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } - -#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) -#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) - -static int stbtt__isfont(stbtt_uint8 *font) -{ - // check the version number - if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 - if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! - if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF - if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 - if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts - return 0; -} - -// @OPTIMIZE: binary search -static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) -{ - stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); - stbtt_uint32 tabledir = fontstart + 12; - stbtt_int32 i; - for (i=0; i < num_tables; ++i) { - stbtt_uint32 loc = tabledir + 16*i; - if (stbtt_tag(data+loc+0, tag)) - return ttULONG(data+loc+8); - } - return 0; -} - -static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) -{ - // if it's just a font, there's only one valid index - if (stbtt__isfont(font_collection)) - return index == 0 ? 0 : -1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - stbtt_int32 n = ttLONG(font_collection+8); - if (index >= n) - return -1; - return ttULONG(font_collection+12+index*4); - } - } - return -1; -} - -static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) -{ - // if it's just a font, there's only one valid font - if (stbtt__isfont(font_collection)) - return 1; - - // check if it's a TTC - if (stbtt_tag(font_collection, "ttcf")) { - // version 1? - if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { - return ttLONG(font_collection+8); - } - } - return 0; -} - -static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) -{ - stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; - stbtt__buf pdict; - stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); - if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); - pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); - stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); - if (!subrsoff) return stbtt__new_buf(NULL, 0); - stbtt__buf_seek(&cff, private_loc[1]+subrsoff); - return stbtt__cff_get_index(&cff); -} - -// since most people won't use this, find this table the first time it's needed -static int stbtt__get_svg(stbtt_fontinfo *info) -{ - stbtt_uint32 t; - if (info->svg < 0) { - t = stbtt__find_table(info->data, info->fontstart, "SVG "); - if (t) { - stbtt_uint32 offset = ttULONG(info->data + t + 2); - info->svg = t + offset; - } else { - info->svg = 0; - } - } - return info->svg; -} - -static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) -{ - stbtt_uint32 cmap, t; - stbtt_int32 i,numTables; - - info->data = data; - info->fontstart = fontstart; - info->cff = stbtt__new_buf(NULL, 0); - - cmap = stbtt__find_table(data, fontstart, "cmap"); // required - info->loca = stbtt__find_table(data, fontstart, "loca"); // required - info->head = stbtt__find_table(data, fontstart, "head"); // required - info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required - info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required - info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required - info->kern = stbtt__find_table(data, fontstart, "kern"); // not required - info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required - - if (!cmap || !info->head || !info->hhea || !info->hmtx) - return 0; - if (info->glyf) { - // required for truetype - if (!info->loca) return 0; - } else { - // initialization for CFF / Type2 fonts (OTF) - stbtt__buf b, topdict, topdictidx; - stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; - stbtt_uint32 cff; - - cff = stbtt__find_table(data, fontstart, "CFF "); - if (!cff) return 0; - - info->fontdicts = stbtt__new_buf(NULL, 0); - info->fdselect = stbtt__new_buf(NULL, 0); - - // @TODO this should use size from table (not 512MB) - info->cff = stbtt__new_buf(data+cff, 512*1024*1024); - b = info->cff; - - // read the header - stbtt__buf_skip(&b, 2); - stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize - - // @TODO the name INDEX could list multiple fonts, - // but we just use the first one. - stbtt__cff_get_index(&b); // name INDEX - topdictidx = stbtt__cff_get_index(&b); - topdict = stbtt__cff_index_get(topdictidx, 0); - stbtt__cff_get_index(&b); // string INDEX - info->gsubrs = stbtt__cff_get_index(&b); - - stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); - stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); - stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); - stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); - info->subrs = stbtt__get_subrs(b, topdict); - - // we only support Type 2 charstrings - if (cstype != 2) return 0; - if (charstrings == 0) return 0; - - if (fdarrayoff) { - // looks like a CID font - if (!fdselectoff) return 0; - stbtt__buf_seek(&b, fdarrayoff); - info->fontdicts = stbtt__cff_get_index(&b); - info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); - } - - stbtt__buf_seek(&b, charstrings); - info->charstrings = stbtt__cff_get_index(&b); - } - - t = stbtt__find_table(data, fontstart, "maxp"); - if (t) - info->numGlyphs = ttUSHORT(data+t+4); - else - info->numGlyphs = 0xffff; - - info->svg = -1; - - // find a cmap encoding table we understand *now* to avoid searching - // later. (todo: could make this installable) - // the same regardless of glyph. - numTables = ttUSHORT(data + cmap + 2); - info->index_map = 0; - for (i=0; i < numTables; ++i) { - stbtt_uint32 encoding_record = cmap + 4 + 8 * i; - // find an encoding we understand: - switch(ttUSHORT(data+encoding_record)) { - case STBTT_PLATFORM_ID_MICROSOFT: - switch (ttUSHORT(data+encoding_record+2)) { - case STBTT_MS_EID_UNICODE_BMP: - case STBTT_MS_EID_UNICODE_FULL: - // MS/Unicode - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - break; - case STBTT_PLATFORM_ID_UNICODE: - // Mac/iOS has these - // all the encodingIDs are unicode, so we don't bother to check it - info->index_map = cmap + ttULONG(data+encoding_record+4); - break; - } - } - if (info->index_map == 0) - return 0; - - info->indexToLocFormat = ttUSHORT(data+info->head + 50); - return 1; -} - -STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) -{ - stbtt_uint8 *data = info->data; - stbtt_uint32 index_map = info->index_map; - - stbtt_uint16 format = ttUSHORT(data + index_map + 0); - if (format == 0) { // apple byte encoding - stbtt_int32 bytes = ttUSHORT(data + index_map + 2); - if (unicode_codepoint < bytes-6) - return ttBYTE(data + index_map + 6 + unicode_codepoint); - return 0; - } else if (format == 6) { - stbtt_uint32 first = ttUSHORT(data + index_map + 6); - stbtt_uint32 count = ttUSHORT(data + index_map + 8); - if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) - return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); - return 0; - } else if (format == 2) { - STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean - return 0; - } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges - stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; - stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; - stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); - stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; - - // do a binary search of the segments - stbtt_uint32 endCount = index_map + 14; - stbtt_uint32 search = endCount; - - if (unicode_codepoint > 0xffff) - return 0; - - // they lie from endCount .. endCount + segCount - // but searchRange is the nearest power of two, so... - if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) - search += rangeShift*2; - - // now decrement to bias correctly to find smallest - search -= 2; - while (entrySelector) { - stbtt_uint16 end; - searchRange >>= 1; - end = ttUSHORT(data + search + searchRange*2); - if (unicode_codepoint > end) - search += searchRange*2; - --entrySelector; - } - search += 2; - - { - stbtt_uint16 offset, start, last; - stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); - - start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); - last = ttUSHORT(data + endCount + 2*item); - if (unicode_codepoint < start || unicode_codepoint > last) - return 0; - - offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); - if (offset == 0) - return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); - - return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); - } - } else if (format == 12 || format == 13) { - stbtt_uint32 ngroups = ttULONG(data+index_map+12); - stbtt_int32 low,high; - low = 0; high = (stbtt_int32)ngroups; - // Binary search the right group. - while (low < high) { - stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high - stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); - stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); - if ((stbtt_uint32) unicode_codepoint < start_char) - high = mid; - else if ((stbtt_uint32) unicode_codepoint > end_char) - low = mid+1; - else { - stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); - if (format == 12) - return start_glyph + unicode_codepoint-start_char; - else // format == 13 - return start_glyph; - } - } - return 0; // not found - } - // @TODO - STBTT_assert(0); - return 0; -} - -STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) -{ - return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); -} - -static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) -{ - v->type = type; - v->x = (stbtt_int16) x; - v->y = (stbtt_int16) y; - v->cx = (stbtt_int16) cx; - v->cy = (stbtt_int16) cy; -} - -static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) -{ - int g1,g2; - - STBTT_assert(!info->cff.size); - - if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range - if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format - - if (info->indexToLocFormat == 0) { - g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; - g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; - } else { - g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); - g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); - } - - return g1==g2 ? -1 : g1; // if length is 0, return -1 -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); - -STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - if (info->cff.size) { - stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); - } else { - int g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 0; - - if (x0) *x0 = ttSHORT(info->data + g + 2); - if (y0) *y0 = ttSHORT(info->data + g + 4); - if (x1) *x1 = ttSHORT(info->data + g + 6); - if (y1) *y1 = ttSHORT(info->data + g + 8); - } - return 1; -} - -STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) -{ - return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); -} - -STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt_int16 numberOfContours; - int g; - if (info->cff.size) - return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; - g = stbtt__GetGlyfOffset(info, glyph_index); - if (g < 0) return 1; - numberOfContours = ttSHORT(info->data + g); - return numberOfContours == 0; -} - -static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, - stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) -{ - if (start_off) { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); - } - return num_vertices; -} - -static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - stbtt_int16 numberOfContours; - stbtt_uint8 *endPtsOfContours; - stbtt_uint8 *data = info->data; - stbtt_vertex *vertices=0; - int num_vertices=0; - int g = stbtt__GetGlyfOffset(info, glyph_index); - - *pvertices = NULL; - - if (g < 0) return 0; - - numberOfContours = ttSHORT(data + g); - - if (numberOfContours > 0) { - stbtt_uint8 flags=0,flagcount; - stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; - stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; - stbtt_uint8 *points; - endPtsOfContours = (data + g + 10); - ins = ttUSHORT(data + g + 10 + numberOfContours * 2); - points = data + g + 10 + numberOfContours * 2 + 2 + ins; - - n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); - - m = n + 2*numberOfContours; // a loose bound on how many vertices we might need - vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); - if (vertices == 0) - return 0; - - next_move = 0; - flagcount=0; - - // in first pass, we load uninterpreted data into the allocated array - // above, shifted to the end of the array so we won't overwrite it when - // we create our final data starting from the front - - off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated - - // first load flags - - for (i=0; i < n; ++i) { - if (flagcount == 0) { - flags = *points++; - if (flags & 8) - flagcount = *points++; - } else - --flagcount; - vertices[off+i].type = flags; - } - - // now load x coordinates - x=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 2) { - stbtt_int16 dx = *points++; - x += (flags & 16) ? dx : -dx; // ??? - } else { - if (!(flags & 16)) { - x = x + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].x = (stbtt_int16) x; - } - - // now load y coordinates - y=0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - if (flags & 4) { - stbtt_int16 dy = *points++; - y += (flags & 32) ? dy : -dy; // ??? - } else { - if (!(flags & 32)) { - y = y + (stbtt_int16) (points[0]*256 + points[1]); - points += 2; - } - } - vertices[off+i].y = (stbtt_int16) y; - } - - // now convert them to our format - num_vertices=0; - sx = sy = cx = cy = scx = scy = 0; - for (i=0; i < n; ++i) { - flags = vertices[off+i].type; - x = (stbtt_int16) vertices[off+i].x; - y = (stbtt_int16) vertices[off+i].y; - - if (next_move == i) { - if (i != 0) - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - - // now start the new one - start_off = !(flags & 1); - if (start_off) { - // if we start off with an off-curve point, then when we need to find a point on the curve - // where we can start, and we need to save some state for when we wraparound. - scx = x; - scy = y; - if (!(vertices[off+i+1].type & 1)) { - // next point is also a curve point, so interpolate an on-point curve - sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; - sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; - } else { - // otherwise just use the next point as our start point - sx = (stbtt_int32) vertices[off+i+1].x; - sy = (stbtt_int32) vertices[off+i+1].y; - ++i; // we're using point i+1 as the starting point, so skip it - } - } else { - sx = x; - sy = y; - } - stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); - was_off = 0; - next_move = 1 + ttUSHORT(endPtsOfContours+j*2); - ++j; - } else { - if (!(flags & 1)) { // if it's a curve - if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); - cx = x; - cy = y; - was_off = 1; - } else { - if (was_off) - stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); - else - stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); - was_off = 0; - } - } - } - num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); - } else if (numberOfContours < 0) { - // Compound shapes. - int more = 1; - stbtt_uint8 *comp = data + g + 10; - num_vertices = 0; - vertices = 0; - while (more) { - stbtt_uint16 flags, gidx; - int comp_num_verts = 0, i; - stbtt_vertex *comp_verts = 0, *tmp = 0; - float mtx[6] = {1,0,0,1,0,0}, m, n; - - flags = ttSHORT(comp); comp+=2; - gidx = ttSHORT(comp); comp+=2; - - if (flags & 2) { // XY values - if (flags & 1) { // shorts - mtx[4] = ttSHORT(comp); comp+=2; - mtx[5] = ttSHORT(comp); comp+=2; - } else { - mtx[4] = ttCHAR(comp); comp+=1; - mtx[5] = ttCHAR(comp); comp+=1; - } - } - else { - // @TODO handle matching point - STBTT_assert(0); - } - if (flags & (1<<3)) { // WE_HAVE_A_SCALE - mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = mtx[2] = 0; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO - mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; - mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; - } - - // Find transformation scales. - m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); - n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); - - // Get indexed glyph. - comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); - if (comp_num_verts > 0) { - // Transform vertices. - for (i = 0; i < comp_num_verts; ++i) { - stbtt_vertex* v = &comp_verts[i]; - stbtt_vertex_type x,y; - x=v->x; y=v->y; - v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - x=v->cx; y=v->cy; - v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); - v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); - } - // Append vertices. - tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); - if (!tmp) { - if (vertices) STBTT_free(vertices, info->userdata); - if (comp_verts) STBTT_free(comp_verts, info->userdata); - return 0; - } - if (num_vertices > 0 && vertices) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); - STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); - if (vertices) STBTT_free(vertices, info->userdata); - vertices = tmp; - STBTT_free(comp_verts, info->userdata); - num_vertices += comp_num_verts; - } - // More components ? - more = flags & (1<<5); - } - } else { - // numberOfCounters == 0, do nothing - } - - *pvertices = vertices; - return num_vertices; -} - -typedef struct -{ - int bounds; - int started; - float first_x, first_y; - float x, y; - stbtt_int32 min_x, max_x, min_y, max_y; - - stbtt_vertex *pvertices; - int num_vertices; -} stbtt__csctx; - -#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} - -static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) -{ - if (x > c->max_x || !c->started) c->max_x = x; - if (y > c->max_y || !c->started) c->max_y = y; - if (x < c->min_x || !c->started) c->min_x = x; - if (y < c->min_y || !c->started) c->min_y = y; - c->started = 1; -} - -static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) -{ - if (c->bounds) { - stbtt__track_vertex(c, x, y); - if (type == STBTT_vcubic) { - stbtt__track_vertex(c, cx, cy); - stbtt__track_vertex(c, cx1, cy1); - } - } else { - stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); - c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; - c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; - } - c->num_vertices++; -} - -static void stbtt__csctx_close_shape(stbtt__csctx *ctx) -{ - if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) -{ - stbtt__csctx_close_shape(ctx); - ctx->first_x = ctx->x = ctx->x + dx; - ctx->first_y = ctx->y = ctx->y + dy; - stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) -{ - ctx->x += dx; - ctx->y += dy; - stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); -} - -static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) -{ - float cx1 = ctx->x + dx1; - float cy1 = ctx->y + dy1; - float cx2 = cx1 + dx2; - float cy2 = cy1 + dy2; - ctx->x = cx2 + dx3; - ctx->y = cy2 + dy3; - stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); -} - -static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) -{ - int count = stbtt__cff_index_count(&idx); - int bias = 107; - if (count >= 33900) - bias = 32768; - else if (count >= 1240) - bias = 1131; - n += bias; - if (n < 0 || n >= count) - return stbtt__new_buf(NULL, 0); - return stbtt__cff_index_get(idx, n); -} - -static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) -{ - stbtt__buf fdselect = info->fdselect; - int nranges, start, end, v, fmt, fdselector = -1, i; - - stbtt__buf_seek(&fdselect, 0); - fmt = stbtt__buf_get8(&fdselect); - if (fmt == 0) { - // untested - stbtt__buf_skip(&fdselect, glyph_index); - fdselector = stbtt__buf_get8(&fdselect); - } else if (fmt == 3) { - nranges = stbtt__buf_get16(&fdselect); - start = stbtt__buf_get16(&fdselect); - for (i = 0; i < nranges; i++) { - v = stbtt__buf_get8(&fdselect); - end = stbtt__buf_get16(&fdselect); - if (glyph_index >= start && glyph_index < end) { - fdselector = v; - break; - } - start = end; - } - } - if (fdselector == -1) stbtt__new_buf(NULL, 0); - return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); -} - -static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) -{ - int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; - int has_subrs = 0, clear_stack; - float s[48]; - stbtt__buf subr_stack[10], subrs = info->subrs, b; - float f; - -#define STBTT__CSERR(s) (0) - - // this currently ignores the initial width value, which isn't needed if we have hmtx - b = stbtt__cff_index_get(info->charstrings, glyph_index); - while (b.cursor < b.size) { - i = 0; - clear_stack = 1; - b0 = stbtt__buf_get8(&b); - switch (b0) { - // @TODO implement hinting - case 0x13: // hintmask - case 0x14: // cntrmask - if (in_header) - maskbits += (sp / 2); // implicit "vstem" - in_header = 0; - stbtt__buf_skip(&b, (maskbits + 7) / 8); - break; - - case 0x01: // hstem - case 0x03: // vstem - case 0x12: // hstemhm - case 0x17: // vstemhm - maskbits += (sp / 2); - break; - - case 0x15: // rmoveto - in_header = 0; - if (sp < 2) return STBTT__CSERR("rmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); - break; - case 0x04: // vmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("vmoveto stack"); - stbtt__csctx_rmove_to(c, 0, s[sp-1]); - break; - case 0x16: // hmoveto - in_header = 0; - if (sp < 1) return STBTT__CSERR("hmoveto stack"); - stbtt__csctx_rmove_to(c, s[sp-1], 0); - break; - - case 0x05: // rlineto - if (sp < 2) return STBTT__CSERR("rlineto stack"); - for (; i + 1 < sp; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical - // starting from a different place. - - case 0x07: // vlineto - if (sp < 1) return STBTT__CSERR("vlineto stack"); - goto vlineto; - case 0x06: // hlineto - if (sp < 1) return STBTT__CSERR("hlineto stack"); - for (;;) { - if (i >= sp) break; - stbtt__csctx_rline_to(c, s[i], 0); - i++; - vlineto: - if (i >= sp) break; - stbtt__csctx_rline_to(c, 0, s[i]); - i++; - } - break; - - case 0x1F: // hvcurveto - if (sp < 4) return STBTT__CSERR("hvcurveto stack"); - goto hvcurveto; - case 0x1E: // vhcurveto - if (sp < 4) return STBTT__CSERR("vhcurveto stack"); - for (;;) { - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); - i += 4; - hvcurveto: - if (i + 3 >= sp) break; - stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); - i += 4; - } - break; - - case 0x08: // rrcurveto - if (sp < 6) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x18: // rcurveline - if (sp < 8) return STBTT__CSERR("rcurveline stack"); - for (; i + 5 < sp - 2; i += 6) - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); - stbtt__csctx_rline_to(c, s[i], s[i+1]); - break; - - case 0x19: // rlinecurve - if (sp < 8) return STBTT__CSERR("rlinecurve stack"); - for (; i + 1 < sp - 6; i += 2) - stbtt__csctx_rline_to(c, s[i], s[i+1]); - if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); - stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); - break; - - case 0x1A: // vvcurveto - case 0x1B: // hhcurveto - if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); - f = 0.0; - if (sp & 1) { f = s[i]; i++; } - for (; i + 3 < sp; i += 4) { - if (b0 == 0x1B) - stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); - else - stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); - f = 0.0; - } - break; - - case 0x0A: // callsubr - if (!has_subrs) { - if (info->fdselect.size) - subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); - has_subrs = 1; - } - // FALLTHROUGH - case 0x1D: // callgsubr - if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); - v = (int) s[--sp]; - if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); - subr_stack[subr_stack_height++] = b; - b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); - if (b.size == 0) return STBTT__CSERR("subr not found"); - b.cursor = 0; - clear_stack = 0; - break; - - case 0x0B: // return - if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); - b = subr_stack[--subr_stack_height]; - clear_stack = 0; - break; - - case 0x0E: // endchar - stbtt__csctx_close_shape(c); - return 1; - - case 0x0C: { // two-byte escape - float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; - float dx, dy; - int b1 = stbtt__buf_get8(&b); - switch (b1) { - // @TODO These "flex" implementations ignore the flex-depth and resolution, - // and always draw beziers. - case 0x22: // hflex - if (sp < 7) return STBTT__CSERR("hflex stack"); - dx1 = s[0]; - dx2 = s[1]; - dy2 = s[2]; - dx3 = s[3]; - dx4 = s[4]; - dx5 = s[5]; - dx6 = s[6]; - stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); - break; - - case 0x23: // flex - if (sp < 13) return STBTT__CSERR("flex stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = s[10]; - dy6 = s[11]; - //fd is s[12] - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - case 0x24: // hflex1 - if (sp < 9) return STBTT__CSERR("hflex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dx4 = s[5]; - dx5 = s[6]; - dy5 = s[7]; - dx6 = s[8]; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); - stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); - break; - - case 0x25: // flex1 - if (sp < 11) return STBTT__CSERR("flex1 stack"); - dx1 = s[0]; - dy1 = s[1]; - dx2 = s[2]; - dy2 = s[3]; - dx3 = s[4]; - dy3 = s[5]; - dx4 = s[6]; - dy4 = s[7]; - dx5 = s[8]; - dy5 = s[9]; - dx6 = dy6 = s[10]; - dx = dx1+dx2+dx3+dx4+dx5; - dy = dy1+dy2+dy3+dy4+dy5; - if (STBTT_fabs(dx) > STBTT_fabs(dy)) - dy6 = -dy; - else - dx6 = -dx; - stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); - stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); - break; - - default: - return STBTT__CSERR("unimplemented"); - } - } break; - - default: - if (b0 != 255 && b0 != 28 && b0 < 32) - return STBTT__CSERR("reserved operator"); - - // push immediate - if (b0 == 255) { - f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; - } else { - stbtt__buf_skip(&b, -1); - f = (float)(stbtt_int16)stbtt__cff_int(&b); - } - if (sp >= 48) return STBTT__CSERR("push stack overflow"); - s[sp++] = f; - clear_stack = 0; - break; - } - if (clear_stack) sp = 0; - } - return STBTT__CSERR("no endchar"); - -#undef STBTT__CSERR -} - -static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - // runs the charstring twice, once to count and once to output (to avoid realloc) - stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); - stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); - if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { - *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); - output_ctx.pvertices = *pvertices; - if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { - STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); - return output_ctx.num_vertices; - } - } - *pvertices = NULL; - return 0; -} - -static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) -{ - stbtt__csctx c = STBTT__CSCTX_INIT(1); - int r = stbtt__run_charstring(info, glyph_index, &c); - if (x0) *x0 = r ? c.min_x : 0; - if (y0) *y0 = r ? c.min_y : 0; - if (x1) *x1 = r ? c.max_x : 0; - if (y1) *y1 = r ? c.max_y : 0; - return r ? c.num_vertices : 0; -} - -STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) -{ - if (!info->cff.size) - return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); - else - return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); -} - -STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) -{ - stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); - if (glyph_index < numOfLongHorMetrics) { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); - } else { - if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); - if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); - } -} - -STBTT_DEF int stbtt_GetKerningTableLength(const stbtt_fontinfo *info) -{ - stbtt_uint8 *data = info->data + info->kern; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - return ttUSHORT(data+10); -} - -STBTT_DEF int stbtt_GetKerningTable(const stbtt_fontinfo *info, stbtt_kerningentry* table, int table_length) -{ - stbtt_uint8 *data = info->data + info->kern; - int k, length; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - length = ttUSHORT(data+10); - if (table_length < length) - length = table_length; - - for (k = 0; k < length; k++) - { - table[k].glyph1 = ttUSHORT(data+18+(k*6)); - table[k].glyph2 = ttUSHORT(data+20+(k*6)); - table[k].advance = ttSHORT(data+22+(k*6)); - } - - return length; -} - -static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint8 *data = info->data + info->kern; - stbtt_uint32 needle, straw; - int l, r, m; - - // we only look at the first table. it must be 'horizontal' and format 0. - if (!info->kern) - return 0; - if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 - return 0; - if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format - return 0; - - l = 0; - r = ttUSHORT(data+10) - 1; - needle = glyph1 << 16 | glyph2; - while (l <= r) { - m = (l + r) >> 1; - straw = ttULONG(data+18+(m*6)); // note: unaligned read - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else - return ttSHORT(data+22+(m*6)); - } - return 0; -} - -static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) -{ - stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); - switch (coverageFormat) { - case 1: { - stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); - - // Binary search. - stbtt_int32 l=0, r=glyphCount-1, m; - int straw, needle=glyph; - while (l <= r) { - stbtt_uint8 *glyphArray = coverageTable + 4; - stbtt_uint16 glyphID; - m = (l + r) >> 1; - glyphID = ttUSHORT(glyphArray + 2 * m); - straw = glyphID; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - return m; - } - } - break; - } - - case 2: { - stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); - stbtt_uint8 *rangeArray = coverageTable + 4; - - // Binary search. - stbtt_int32 l=0, r=rangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *rangeRecord; - m = (l + r) >> 1; - rangeRecord = rangeArray + 6 * m; - strawStart = ttUSHORT(rangeRecord); - strawEnd = ttUSHORT(rangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else { - stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); - return startCoverageIndex + glyph - strawStart; - } - } - break; - } - - default: return -1; // unsupported - } - - return -1; -} - -static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) -{ - stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); - switch (classDefFormat) - { - case 1: { - stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); - stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); - stbtt_uint8 *classDef1ValueArray = classDefTable + 6; - - if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) - return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); - break; - } - - case 2: { - stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); - stbtt_uint8 *classRangeRecords = classDefTable + 4; - - // Binary search. - stbtt_int32 l=0, r=classRangeCount-1, m; - int strawStart, strawEnd, needle=glyph; - while (l <= r) { - stbtt_uint8 *classRangeRecord; - m = (l + r) >> 1; - classRangeRecord = classRangeRecords + 6 * m; - strawStart = ttUSHORT(classRangeRecord); - strawEnd = ttUSHORT(classRangeRecord + 2); - if (needle < strawStart) - r = m - 1; - else if (needle > strawEnd) - l = m + 1; - else - return (stbtt_int32)ttUSHORT(classRangeRecord + 4); - } - break; - } - - default: - return -1; // Unsupported definition type, return an error. - } - - // "All glyphs not assigned to a class fall into class 0". (OpenType spec) - return 0; -} - -// Define to STBTT_assert(x) if you want to break on unimplemented formats. -#define STBTT_GPOS_TODO_assert(x) - -static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) -{ - stbtt_uint16 lookupListOffset; - stbtt_uint8 *lookupList; - stbtt_uint16 lookupCount; - stbtt_uint8 *data; - stbtt_int32 i, sti; - - if (!info->gpos) return 0; - - data = info->data + info->gpos; - - if (ttUSHORT(data+0) != 1) return 0; // Major version 1 - if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 - - lookupListOffset = ttUSHORT(data+8); - lookupList = data + lookupListOffset; - lookupCount = ttUSHORT(lookupList); - - for (i=0; i= pairSetCount) return 0; - - needle=glyph2; - r=pairValueCount-1; - l=0; - - // Binary search. - while (l <= r) { - stbtt_uint16 secondGlyph; - stbtt_uint8 *pairValue; - m = (l + r) >> 1; - pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; - secondGlyph = ttUSHORT(pairValue); - straw = secondGlyph; - if (needle < straw) - r = m - 1; - else if (needle > straw) - l = m + 1; - else { - stbtt_int16 xAdvance = ttSHORT(pairValue + 2); - return xAdvance; - } - } - } else - return 0; - break; - } - - case 2: { - stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); - stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); - if (valueFormat1 == 4 && valueFormat2 == 0) { // Support more formats? - stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); - stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); - int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); - int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); - - stbtt_uint16 class1Count = ttUSHORT(table + 12); - stbtt_uint16 class2Count = ttUSHORT(table + 14); - stbtt_uint8 *class1Records, *class2Records; - stbtt_int16 xAdvance; - - if (glyph1class < 0 || glyph1class >= class1Count) return 0; // malformed - if (glyph2class < 0 || glyph2class >= class2Count) return 0; // malformed - - class1Records = table + 16; - class2Records = class1Records + 2 * (glyph1class * class2Count); - xAdvance = ttSHORT(class2Records + 2 * glyph2class); - return xAdvance; - } else - return 0; - break; - } - - default: - return 0; // Unsupported position format - } - } - } - - return 0; -} - -STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) -{ - int xAdvance = 0; - - if (info->gpos) - xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); - else if (info->kern) - xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); - - return xAdvance; -} - -STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) -{ - if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs - return 0; - return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); -} - -STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) -{ - stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); -} - -STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) -{ - if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); - if (descent) *descent = ttSHORT(info->data+info->hhea + 6); - if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); -} - -STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) -{ - int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); - if (!tab) - return 0; - if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); - if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); - if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); - return 1; -} - -STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) -{ - *x0 = ttSHORT(info->data + info->head + 36); - *y0 = ttSHORT(info->data + info->head + 38); - *x1 = ttSHORT(info->data + info->head + 40); - *y1 = ttSHORT(info->data + info->head + 42); -} - -STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) -{ - int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); - return (float) height / fheight; -} - -STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) -{ - int unitsPerEm = ttUSHORT(info->data + info->head + 18); - return pixels / unitsPerEm; -} - -STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) -{ - STBTT_free(v, info->userdata); -} - -STBTT_DEF stbtt_uint8 *stbtt_FindSVGDoc(const stbtt_fontinfo *info, int gl) -{ - int i; - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc_list = data + stbtt__get_svg((stbtt_fontinfo *) info); - - int numEntries = ttUSHORT(svg_doc_list); - stbtt_uint8 *svg_docs = svg_doc_list + 2; - - for(i=0; i= ttUSHORT(svg_doc)) && (gl <= ttUSHORT(svg_doc + 2))) - return svg_doc; - } - return 0; -} - -STBTT_DEF int stbtt_GetGlyphSVG(const stbtt_fontinfo *info, int gl, const char **svg) -{ - stbtt_uint8 *data = info->data; - stbtt_uint8 *svg_doc; - - if (info->svg == 0) - return 0; - - svg_doc = stbtt_FindSVGDoc(info, gl); - if (svg_doc != NULL) { - *svg = (char *) data + info->svg + ttULONG(svg_doc + 4); - return ttULONG(svg_doc + 8); - } else { - return 0; - } -} - -STBTT_DEF int stbtt_GetCodepointSVG(const stbtt_fontinfo *info, int unicode_codepoint, const char **svg) -{ - return stbtt_GetGlyphSVG(info, stbtt_FindGlyphIndex(info, unicode_codepoint), svg); -} - -////////////////////////////////////////////////////////////////////////////// -// -// antialiasing software rasterizer -// - -STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning - if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { - // e.g. space character - if (ix0) *ix0 = 0; - if (iy0) *iy0 = 0; - if (ix1) *ix1 = 0; - if (iy1) *iy1 = 0; - } else { - // move to integral bboxes (treating pixels as little squares, what pixels get touched)? - if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); - if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); - if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); - if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); - } -} - -STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); -} - -STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) -{ - stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); -} - -////////////////////////////////////////////////////////////////////////////// -// -// Rasterizer - -typedef struct stbtt__hheap_chunk -{ - struct stbtt__hheap_chunk *next; -} stbtt__hheap_chunk; - -typedef struct stbtt__hheap -{ - struct stbtt__hheap_chunk *head; - void *first_free; - int num_remaining_in_head_chunk; -} stbtt__hheap; - -static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) -{ - if (hh->first_free) { - void *p = hh->first_free; - hh->first_free = * (void **) p; - return p; - } else { - if (hh->num_remaining_in_head_chunk == 0) { - int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); - stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); - if (c == NULL) - return NULL; - c->next = hh->head; - hh->head = c; - hh->num_remaining_in_head_chunk = count; - } - --hh->num_remaining_in_head_chunk; - return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; - } -} - -static void stbtt__hheap_free(stbtt__hheap *hh, void *p) -{ - *(void **) p = hh->first_free; - hh->first_free = p; -} - -static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) -{ - stbtt__hheap_chunk *c = hh->head; - while (c) { - stbtt__hheap_chunk *n = c->next; - STBTT_free(c, userdata); - c = n; - } -} - -typedef struct stbtt__edge { - float x0,y0, x1,y1; - int invert; -} stbtt__edge; - - -typedef struct stbtt__active_edge -{ - struct stbtt__active_edge *next; - #if STBTT_RASTERIZER_VERSION==1 - int x,dx; - float ey; - int direction; - #elif STBTT_RASTERIZER_VERSION==2 - float fx,fdx,fdy; - float direction; - float sy; - float ey; - #else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" - #endif -} stbtt__active_edge; - -#if STBTT_RASTERIZER_VERSION == 1 -#define STBTT_FIXSHIFT 10 -#define STBTT_FIX (1 << STBTT_FIXSHIFT) -#define STBTT_FIXMASK (STBTT_FIX-1) - -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - if (!z) return z; - - // round dx down to avoid overshooting - if (dxdy < 0) - z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); - else - z->dx = STBTT_ifloor(STBTT_FIX * dxdy); - - z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount - z->x -= off_x * STBTT_FIX; - - z->ey = e->y1; - z->next = 0; - z->direction = e->invert ? 1 : -1; - return z; -} -#elif STBTT_RASTERIZER_VERSION == 2 -static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) -{ - stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); - float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); - STBTT_assert(z != NULL); - //STBTT_assert(e->y0 <= start_point); - if (!z) return z; - z->fdx = dxdy; - z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; - z->fx = e->x0 + dxdy * (start_point - e->y0); - z->fx -= off_x; - z->direction = e->invert ? 1.0f : -1.0f; - z->sy = e->y0; - z->ey = e->y1; - z->next = 0; - return z; -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#if STBTT_RASTERIZER_VERSION == 1 -// note: this routine clips fills that extend off the edges... ideally this -// wouldn't happen, but it could happen if the truetype glyph bounding boxes -// are wrong, or if the user supplies a too-small bitmap -static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) -{ - // non-zero winding fill - int x0=0, w=0; - - while (e) { - if (w == 0) { - // if we're currently at zero, we need to record the edge start point - x0 = e->x; w += e->direction; - } else { - int x1 = e->x; w += e->direction; - // if we went to zero, we need to draw - if (w == 0) { - int i = x0 >> STBTT_FIXSHIFT; - int j = x1 >> STBTT_FIXSHIFT; - - if (i < len && j >= 0) { - if (i == j) { - // x0,x1 are the same pixel, so compute combined coverage - scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); - } else { - if (i >= 0) // add antialiasing for x0 - scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); - else - i = -1; // clip - - if (j < len) // add antialiasing for x1 - scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); - else - j = len; // clip - - for (++i; i < j; ++i) // fill pixels between x0 and x1 - scanline[i] = scanline[i] + (stbtt_uint8) max_weight; - } - } - } - } - - e = e->next; - } -} - -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0; - int max_weight = (255 / vsubsample); // weight per vertical scanline - int s; // vertical subsample index - unsigned char scanline_data[512], *scanline; - - if (result->w > 512) - scanline = (unsigned char *) STBTT_malloc(result->w, userdata); - else - scanline = scanline_data; - - y = off_y * vsubsample; - e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; - - while (j < result->h) { - STBTT_memset(scanline, 0, result->w); - for (s=0; s < vsubsample; ++s) { - // find center of pixel for this scanline - float scan_y = y + 0.5f; - stbtt__active_edge **step = &active; - - // update all active edges; - // remove all active edges that terminate before the center of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - z->x += z->dx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - } - - // resort the list if needed - for(;;) { - int changed=0; - step = &active; - while (*step && (*step)->next) { - if ((*step)->x > (*step)->next->x) { - stbtt__active_edge *t = *step; - stbtt__active_edge *q = t->next; - - t->next = q->next; - q->next = t; - *step = q; - changed = 1; - } - step = &(*step)->next; - } - if (!changed) break; - } - - // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline - while (e->y0 <= scan_y) { - if (e->y1 > scan_y) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); - if (z != NULL) { - // find insertion point - if (active == NULL) - active = z; - else if (z->x < active->x) { - // insert at front - z->next = active; - active = z; - } else { - // find thing to insert AFTER - stbtt__active_edge *p = active; - while (p->next && p->next->x < z->x) - p = p->next; - // at this point, p->next->x is NOT < z->x - z->next = p->next; - p->next = z; - } - } - } - ++e; - } - - // now process all active edges in XOR fashion - if (active) - stbtt__fill_active_edges(scanline, result->w, active, max_weight); - - ++y; - } - STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} - -#elif STBTT_RASTERIZER_VERSION == 2 - -// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 -// (i.e. it has already been clipped to those) -static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) -{ - if (y0 == y1) return; - STBTT_assert(y0 < y1); - STBTT_assert(e->sy <= e->ey); - if (y0 > e->ey) return; - if (y1 < e->sy) return; - if (y0 < e->sy) { - x0 += (x1-x0) * (e->sy - y0) / (y1-y0); - y0 = e->sy; - } - if (y1 > e->ey) { - x1 += (x1-x0) * (e->ey - y1) / (y1-y0); - y1 = e->ey; - } - - if (x0 == x) - STBTT_assert(x1 <= x+1); - else if (x0 == x+1) - STBTT_assert(x1 >= x); - else if (x0 <= x) - STBTT_assert(x1 <= x); - else if (x0 >= x+1) - STBTT_assert(x1 >= x+1); - else - STBTT_assert(x1 >= x && x1 <= x+1); - - if (x0 <= x && x1 <= x) - scanline[x] += e->direction * (y1-y0); - else if (x0 >= x+1 && x1 >= x+1) - ; - else { - STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); - scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position - } -} - -static float stbtt__sized_trapezoid_area(float height, float top_width, float bottom_width) -{ - STBTT_assert(top_width >= 0); - STBTT_assert(bottom_width >= 0); - return (top_width + bottom_width) / 2.0f * height; -} - -static float stbtt__position_trapezoid_area(float height, float tx0, float tx1, float bx0, float bx1) -{ - return stbtt__sized_trapezoid_area(height, tx1 - tx0, bx1 - bx0); -} - -static float stbtt__sized_triangle_area(float height, float width) -{ - return height * width / 2; -} - -static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) -{ - float y_bottom = y_top+1; - - while (e) { - // brute force every pixel - - // compute intersection points with top & bottom - STBTT_assert(e->ey >= y_top); - - if (e->fdx == 0) { - float x0 = e->fx; - if (x0 < len) { - if (x0 >= 0) { - stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); - stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); - } else { - stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); - } - } - } else { - float x0 = e->fx; - float dx = e->fdx; - float xb = x0 + dx; - float x_top, x_bottom; - float sy0,sy1; - float dy = e->fdy; - STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); - - // compute endpoints of line segment clipped to this scanline (if the - // line segment starts on this scanline. x0 is the intersection of the - // line with y_top, but that may be off the line segment. - if (e->sy > y_top) { - x_top = x0 + dx * (e->sy - y_top); - sy0 = e->sy; - } else { - x_top = x0; - sy0 = y_top; - } - if (e->ey < y_bottom) { - x_bottom = x0 + dx * (e->ey - y_top); - sy1 = e->ey; - } else { - x_bottom = xb; - sy1 = y_bottom; - } - - if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { - // from here on, we don't have to range check x values - - if ((int) x_top == (int) x_bottom) { - float height; - // simple case, only spans one pixel - int x = (int) x_top; - height = (sy1 - sy0) * e->direction; - STBTT_assert(x >= 0 && x < len); - scanline[x] += stbtt__position_trapezoid_area(height, x_top, x+1.0f, x_bottom, x+1.0f); - scanline_fill[x] += height; // everything right of this pixel is filled - } else { - int x,x1,x2; - float y_crossing, y_final, step, sign, area; - // covers 2+ pixels - if (x_top > x_bottom) { - // flip scanline vertically; signed area is the same - float t; - sy0 = y_bottom - (sy0 - y_top); - sy1 = y_bottom - (sy1 - y_top); - t = sy0, sy0 = sy1, sy1 = t; - t = x_bottom, x_bottom = x_top, x_top = t; - dx = -dx; - dy = -dy; - t = x0, x0 = xb, xb = t; - } - STBTT_assert(dy >= 0); - STBTT_assert(dx >= 0); - - x1 = (int) x_top; - x2 = (int) x_bottom; - // compute intersection with y axis at x1+1 - y_crossing = y_top + dy * (x1+1 - x0); - - // compute intersection with y axis at x2 - y_final = y_top + dy * (x2 - x0); - - // x1 x_top x2 x_bottom - // y_top +------|-----+------------+------------+--------|---+------------+ - // | | | | | | - // | | | | | | - // sy0 | Txxxxx|............|............|............|............| - // y_crossing | *xxxxx.......|............|............|............| - // | | xxxxx..|............|............|............| - // | | /- xx*xxxx........|............|............| - // | | dy < | xxxxxx..|............|............| - // y_final | | \- | xx*xxx.........|............| - // sy1 | | | | xxxxxB...|............| - // | | | | | | - // | | | | | | - // y_bottom +------------+------------+------------+------------+------------+ - // - // goal is to measure the area covered by '.' in each pixel - - // if x2 is right at the right edge of x1, y_crossing can blow up, github #1057 - // @TODO: maybe test against sy1 rather than y_bottom? - if (y_crossing > y_bottom) - y_crossing = y_bottom; - - sign = e->direction; - - // area of the rectangle covered from sy0..y_crossing - area = sign * (y_crossing-sy0); - - // area of the triangle (x_top,sy0), (x1+1,sy0), (x1+1,y_crossing) - scanline[x1] += stbtt__sized_triangle_area(area, x1+1 - x_top); - - // check if final y_crossing is blown up; no test case for this - if (y_final > y_bottom) { - y_final = y_bottom; - dy = (y_final - y_crossing ) / (x2 - (x1+1)); // if denom=0, y_final = y_crossing, so y_final <= y_bottom - } - - // in second pixel, area covered by line segment found in first pixel - // is always a rectangle 1 wide * the height of that line segment; this - // is exactly what the variable 'area' stores. it also gets a contribution - // from the line segment within it. the THIRD pixel will get the first - // pixel's rectangle contribution, the second pixel's rectangle contribution, - // and its own contribution. the 'own contribution' is the same in every pixel except - // the leftmost and rightmost, a trapezoid that slides down in each pixel. - // the second pixel's contribution to the third pixel will be the - // rectangle 1 wide times the height change in the second pixel, which is dy. - - step = sign * dy * 1; // dy is dy/dx, change in y for every 1 change in x, - // which multiplied by 1-pixel-width is how much pixel area changes for each step in x - // so the area advances by 'step' every time - - for (x = x1+1; x < x2; ++x) { - scanline[x] += area + step/2; // area of trapezoid is 1*step/2 - area += step; - } - STBTT_assert(STBTT_fabs(area) <= 1.01f); // accumulated error from area += step unless we round step down - STBTT_assert(sy1 > y_final-0.01f); - - // area covered in the last pixel is the rectangle from all the pixels to the left, - // plus the trapezoid filled by the line segment in this pixel all the way to the right edge - scanline[x2] += area + sign * stbtt__position_trapezoid_area(sy1-y_final, (float) x2, x2+1.0f, x_bottom, x2+1.0f); - - // the rest of the line is filled based on the total height of the line segment in this pixel - scanline_fill[x2] += sign * (sy1-sy0); - } - } else { - // if edge goes outside of box we're drawing, we require - // clipping logic. since this does not match the intended use - // of this library, we use a different, very slow brute - // force implementation - // note though that this does happen some of the time because - // x_top and x_bottom can be extrapolated at the top & bottom of - // the shape and actually lie outside the bounding box - int x; - for (x=0; x < len; ++x) { - // cases: - // - // there can be up to two intersections with the pixel. any intersection - // with left or right edges can be handled by splitting into two (or three) - // regions. intersections with top & bottom do not necessitate case-wise logic. - // - // the old way of doing this found the intersections with the left & right edges, - // then used some simple logic to produce up to three segments in sorted order - // from top-to-bottom. however, this had a problem: if an x edge was epsilon - // across the x border, then the corresponding y position might not be distinct - // from the other y segment, and it might ignored as an empty segment. to avoid - // that, we need to explicitly produce segments based on x positions. - - // rename variables to clearly-defined pairs - float y0 = y_top; - float x1 = (float) (x); - float x2 = (float) (x+1); - float x3 = xb; - float y3 = y_bottom; - - // x = e->x + e->dx * (y-y_top) - // (y-y_top) = (x - e->x) / e->dx - // y = (x - e->x) / e->dx + y_top - float y1 = (x - x0) / dx + y_top; - float y2 = (x+1 - x0) / dx + y_top; - - if (x0 < x1 && x3 > x2) { // three segments descending down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x1 && x0 > x2) { // three segments descending down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); - stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); - } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); - stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); - } else { // one segment - stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); - } - } - } - } - e = e->next; - } -} - -// directly AA rasterize edges w/o supersampling -static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) -{ - stbtt__hheap hh = { 0, 0, 0 }; - stbtt__active_edge *active = NULL; - int y,j=0, i; - float scanline_data[129], *scanline, *scanline2; - - STBTT__NOTUSED(vsubsample); - - if (result->w > 64) - scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); - else - scanline = scanline_data; - - scanline2 = scanline + result->w; - - y = off_y; - e[n].y0 = (float) (off_y + result->h) + 1; - - while (j < result->h) { - // find center of pixel for this scanline - float scan_y_top = y + 0.0f; - float scan_y_bottom = y + 1.0f; - stbtt__active_edge **step = &active; - - STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); - STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); - - // update all active edges; - // remove all active edges that terminate before the top of this scanline - while (*step) { - stbtt__active_edge * z = *step; - if (z->ey <= scan_y_top) { - *step = z->next; // delete from list - STBTT_assert(z->direction); - z->direction = 0; - stbtt__hheap_free(&hh, z); - } else { - step = &((*step)->next); // advance through list - } - } - - // insert all edges that start before the bottom of this scanline - while (e->y0 <= scan_y_bottom) { - if (e->y0 != e->y1) { - stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); - if (z != NULL) { - if (j == 0 && off_y != 0) { - if (z->ey < scan_y_top) { - // this can happen due to subpixel positioning and some kind of fp rounding error i think - z->ey = scan_y_top; - } - } - STBTT_assert(z->ey >= scan_y_top); // if we get really unlucky a tiny bit of an edge can be out of bounds - // insert at front - z->next = active; - active = z; - } - } - ++e; - } - - // now process all active edges - if (active) - stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); - - { - float sum = 0; - for (i=0; i < result->w; ++i) { - float k; - int m; - sum += scanline2[i]; - k = scanline[i] + sum; - k = (float) STBTT_fabs(k)*255 + 0.5f; - m = (int) k; - if (m > 255) m = 255; - result->pixels[j*result->stride + i] = (unsigned char) m; - } - } - // advance all the edges - step = &active; - while (*step) { - stbtt__active_edge *z = *step; - z->fx += z->fdx; // advance to position for current scanline - step = &((*step)->next); // advance through list - } - - ++y; - ++j; - } - - stbtt__hheap_cleanup(&hh, userdata); - - if (scanline != scanline_data) - STBTT_free(scanline, userdata); -} -#else -#error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - -#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) - -static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) -{ - int i,j; - for (i=1; i < n; ++i) { - stbtt__edge t = p[i], *a = &t; - j = i; - while (j > 0) { - stbtt__edge *b = &p[j-1]; - int c = STBTT__COMPARE(a,b); - if (!c) break; - p[j] = p[j-1]; - --j; - } - if (i != j) - p[j] = t; - } -} - -static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) -{ - /* threshold for transitioning to insertion sort */ - while (n > 12) { - stbtt__edge t; - int c01,c12,c,m,i,j; - - /* compute median of three */ - m = n >> 1; - c01 = STBTT__COMPARE(&p[0],&p[m]); - c12 = STBTT__COMPARE(&p[m],&p[n-1]); - /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ - if (c01 != c12) { - /* otherwise, we'll need to swap something else to middle */ - int z; - c = STBTT__COMPARE(&p[0],&p[n-1]); - /* 0>mid && midn => n; 0 0 */ - /* 0n: 0>n => 0; 0 n */ - z = (c == c12) ? 0 : n-1; - t = p[z]; - p[z] = p[m]; - p[m] = t; - } - /* now p[m] is the median-of-three */ - /* swap it to the beginning so it won't move around */ - t = p[0]; - p[0] = p[m]; - p[m] = t; - - /* partition loop */ - i=1; - j=n-1; - for(;;) { - /* handling of equality is crucial here */ - /* for sentinels & efficiency with duplicates */ - for (;;++i) { - if (!STBTT__COMPARE(&p[i], &p[0])) break; - } - for (;;--j) { - if (!STBTT__COMPARE(&p[0], &p[j])) break; - } - /* make sure we haven't crossed */ - if (i >= j) break; - t = p[i]; - p[i] = p[j]; - p[j] = t; - - ++i; - --j; - } - /* recurse on smaller side, iterate on larger */ - if (j < (n-i)) { - stbtt__sort_edges_quicksort(p,j); - p = p+i; - n = n-i; - } else { - stbtt__sort_edges_quicksort(p+i, n-i); - n = j; - } - } -} - -static void stbtt__sort_edges(stbtt__edge *p, int n) -{ - stbtt__sort_edges_quicksort(p, n); - stbtt__sort_edges_ins_sort(p, n); -} - -typedef struct -{ - float x,y; -} stbtt__point; - -static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) -{ - float y_scale_inv = invert ? -scale_y : scale_y; - stbtt__edge *e; - int n,i,j,k,m; -#if STBTT_RASTERIZER_VERSION == 1 - int vsubsample = result->h < 8 ? 15 : 5; -#elif STBTT_RASTERIZER_VERSION == 2 - int vsubsample = 1; -#else - #error "Unrecognized value of STBTT_RASTERIZER_VERSION" -#endif - // vsubsample should divide 255 evenly; otherwise we won't reach full opacity - - // now we have to blow out the windings into explicit edge lists - n = 0; - for (i=0; i < windings; ++i) - n += wcount[i]; - - e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel - if (e == 0) return; - n = 0; - - m=0; - for (i=0; i < windings; ++i) { - stbtt__point *p = pts + m; - m += wcount[i]; - j = wcount[i]-1; - for (k=0; k < wcount[i]; j=k++) { - int a=k,b=j; - // skip the edge if horizontal - if (p[j].y == p[k].y) - continue; - // add edge from j to k to the list - e[n].invert = 0; - if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { - e[n].invert = 1; - a=j,b=k; - } - e[n].x0 = p[a].x * scale_x + shift_x; - e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; - e[n].x1 = p[b].x * scale_x + shift_x; - e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; - ++n; - } - } - - // now sort the edges by their highest point (should snap to integer, and then by x) - //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); - stbtt__sort_edges(e, n); - - // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule - stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); - - STBTT_free(e, userdata); -} - -static void stbtt__add_point(stbtt__point *points, int n, float x, float y) -{ - if (!points) return; // during first pass, it's unallocated - points[n].x = x; - points[n].y = y; -} - -// tessellate until threshold p is happy... @TODO warped to compensate for non-linear stretching -static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) -{ - // midpoint - float mx = (x0 + 2*x1 + x2)/4; - float my = (y0 + 2*y1 + y2)/4; - // versus directly drawn line - float dx = (x0+x2)/2 - mx; - float dy = (y0+y2)/2 - my; - if (n > 16) // 65536 segments on one curve better be enough! - return 1; - if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA - stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x2,y2); - *num_points = *num_points+1; - } - return 1; -} - -static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) -{ - // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough - float dx0 = x1-x0; - float dy0 = y1-y0; - float dx1 = x2-x1; - float dy1 = y2-y1; - float dx2 = x3-x2; - float dy2 = y3-y2; - float dx = x3-x0; - float dy = y3-y0; - float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); - float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); - float flatness_squared = longlen*longlen-shortlen*shortlen; - - if (n > 16) // 65536 segments on one curve better be enough! - return; - - if (flatness_squared > objspace_flatness_squared) { - float x01 = (x0+x1)/2; - float y01 = (y0+y1)/2; - float x12 = (x1+x2)/2; - float y12 = (y1+y2)/2; - float x23 = (x2+x3)/2; - float y23 = (y2+y3)/2; - - float xa = (x01+x12)/2; - float ya = (y01+y12)/2; - float xb = (x12+x23)/2; - float yb = (y12+y23)/2; - - float mx = (xa+xb)/2; - float my = (ya+yb)/2; - - stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); - stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); - } else { - stbtt__add_point(points, *num_points,x3,y3); - *num_points = *num_points+1; - } -} - -// returns number of contours -static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) -{ - stbtt__point *points=0; - int num_points=0; - - float objspace_flatness_squared = objspace_flatness * objspace_flatness; - int i,n=0,start=0, pass; - - // count how many "moves" there are to get the contour count - for (i=0; i < num_verts; ++i) - if (vertices[i].type == STBTT_vmove) - ++n; - - *num_contours = n; - if (n == 0) return 0; - - *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); - - if (*contour_lengths == 0) { - *num_contours = 0; - return 0; - } - - // make two passes through the points so we don't need to realloc - for (pass=0; pass < 2; ++pass) { - float x=0,y=0; - if (pass == 1) { - points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); - if (points == NULL) goto error; - } - num_points = 0; - n= -1; - for (i=0; i < num_verts; ++i) { - switch (vertices[i].type) { - case STBTT_vmove: - // start the next contour - if (n >= 0) - (*contour_lengths)[n] = num_points - start; - ++n; - start = num_points; - - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x,y); - break; - case STBTT_vline: - x = vertices[i].x, y = vertices[i].y; - stbtt__add_point(points, num_points++, x, y); - break; - case STBTT_vcurve: - stbtt__tesselate_curve(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - case STBTT_vcubic: - stbtt__tesselate_cubic(points, &num_points, x,y, - vertices[i].cx, vertices[i].cy, - vertices[i].cx1, vertices[i].cy1, - vertices[i].x, vertices[i].y, - objspace_flatness_squared, 0); - x = vertices[i].x, y = vertices[i].y; - break; - } - } - (*contour_lengths)[n] = num_points - start; - } - - return points; -error: - STBTT_free(points, userdata); - STBTT_free(*contour_lengths, userdata); - *contour_lengths = 0; - *num_contours = 0; - return NULL; -} - -STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) -{ - float scale = scale_x > scale_y ? scale_y : scale_x; - int winding_count = 0; - int *winding_lengths = NULL; - stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); - if (windings) { - stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); - STBTT_free(winding_lengths, userdata); - STBTT_free(windings, userdata); - } -} - -STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - int ix0,iy0,ix1,iy1; - stbtt__bitmap gbm; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - - if (scale_x == 0) scale_x = scale_y; - if (scale_y == 0) { - if (scale_x == 0) { - STBTT_free(vertices, info->userdata); - return NULL; - } - scale_y = scale_x; - } - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); - - // now we get the size - gbm.w = (ix1 - ix0); - gbm.h = (iy1 - iy0); - gbm.pixels = NULL; // in case we error - - if (width ) *width = gbm.w; - if (height) *height = gbm.h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - if (gbm.w && gbm.h) { - gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); - if (gbm.pixels) { - gbm.stride = gbm.w; - - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); - } - } - STBTT_free(vertices, info->userdata); - return gbm.pixels; -} - -STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) -{ - int ix0,iy0; - stbtt_vertex *vertices; - int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); - stbtt__bitmap gbm; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); - gbm.pixels = output; - gbm.w = out_w; - gbm.h = out_h; - gbm.stride = out_stride; - - if (gbm.w && gbm.h) - stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); - - STBTT_free(vertices, info->userdata); -} - -STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) -{ - stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); -} - -STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); -} - -STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) -{ - stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); -} - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-CRAPPY packing to keep source code small - -static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) - float pixel_height, // height of font in pixels - unsigned char *pixels, int pw, int ph, // bitmap to be filled in - int first_char, int num_chars, // characters to bake - stbtt_bakedchar *chardata) -{ - float scale; - int x,y,bottom_y, i; - stbtt_fontinfo f; - f.userdata = NULL; - if (!stbtt_InitFont(&f, data, offset)) - return -1; - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - x=y=1; - bottom_y = 1; - - scale = stbtt_ScaleForPixelHeight(&f, pixel_height); - - for (i=0; i < num_chars; ++i) { - int advance, lsb, x0,y0,x1,y1,gw,gh; - int g = stbtt_FindGlyphIndex(&f, first_char + i); - stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); - stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); - gw = x1-x0; - gh = y1-y0; - if (x + gw + 1 >= pw) - y = bottom_y, x = 1; // advance to next row - if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row - return -i; - STBTT_assert(x+gw < pw); - STBTT_assert(y+gh < ph); - stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); - chardata[i].x0 = (stbtt_int16) x; - chardata[i].y0 = (stbtt_int16) y; - chardata[i].x1 = (stbtt_int16) (x + gw); - chardata[i].y1 = (stbtt_int16) (y + gh); - chardata[i].xadvance = scale * advance; - chardata[i].xoff = (float) x0; - chardata[i].yoff = (float) y0; - x = x + gw + 1; - if (y+gh+1 > bottom_y) - bottom_y = y+gh+1; - } - return bottom_y; -} - -STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) -{ - float d3d_bias = opengl_fillrule ? 0 : -0.5f; - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_bakedchar *b = chardata + char_index; - int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); - int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); - - q->x0 = round_x + d3d_bias; - q->y0 = round_y + d3d_bias; - q->x1 = round_x + b->x1 - b->x0 + d3d_bias; - q->y1 = round_y + b->y1 - b->y0 + d3d_bias; - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// rectangle packing replacement routines if you don't have stb_rect_pack.h -// - -#ifndef STB_RECT_PACK_VERSION - -typedef int stbrp_coord; - -//////////////////////////////////////////////////////////////////////////////////// -// // -// // -// COMPILER WARNING ?!?!? // -// // -// // -// if you get a compile warning due to these symbols being defined more than // -// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // -// // -//////////////////////////////////////////////////////////////////////////////////// - -typedef struct -{ - int width,height; - int x,y,bottom_y; -} stbrp_context; - -typedef struct -{ - unsigned char x; -} stbrp_node; - -struct stbrp_rect -{ - stbrp_coord x,y; - int id,w,h,was_packed; -}; - -static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) -{ - con->width = pw; - con->height = ph; - con->x = 0; - con->y = 0; - con->bottom_y = 0; - STBTT__NOTUSED(nodes); - STBTT__NOTUSED(num_nodes); -} - -static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) -{ - int i; - for (i=0; i < num_rects; ++i) { - if (con->x + rects[i].w > con->width) { - con->x = 0; - con->y = con->bottom_y; - } - if (con->y + rects[i].h > con->height) - break; - rects[i].x = con->x; - rects[i].y = con->y; - rects[i].was_packed = 1; - con->x += rects[i].w; - if (con->y + rects[i].h > con->bottom_y) - con->bottom_y = con->y + rects[i].h; - } - for ( ; i < num_rects; ++i) - rects[i].was_packed = 0; -} -#endif - -////////////////////////////////////////////////////////////////////////////// -// -// bitmap baking -// -// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If -// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. - -STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) -{ - stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); - int num_nodes = pw - padding; - stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); - - if (context == NULL || nodes == NULL) { - if (context != NULL) STBTT_free(context, alloc_context); - if (nodes != NULL) STBTT_free(nodes , alloc_context); - return 0; - } - - spc->user_allocator_context = alloc_context; - spc->width = pw; - spc->height = ph; - spc->pixels = pixels; - spc->pack_info = context; - spc->nodes = nodes; - spc->padding = padding; - spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; - spc->h_oversample = 1; - spc->v_oversample = 1; - spc->skip_missing = 0; - - stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); - - if (pixels) - STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels - - return 1; -} - -STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) -{ - STBTT_free(spc->nodes , spc->user_allocator_context); - STBTT_free(spc->pack_info, spc->user_allocator_context); -} - -STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) -{ - STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); - STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); - if (h_oversample <= STBTT_MAX_OVERSAMPLE) - spc->h_oversample = h_oversample; - if (v_oversample <= STBTT_MAX_OVERSAMPLE) - spc->v_oversample = v_oversample; -} - -STBTT_DEF void stbtt_PackSetSkipMissingCodepoints(stbtt_pack_context *spc, int skip) -{ - spc->skip_missing = skip; -} - -#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) - -static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_w = w - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < h; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_w; ++i) { - total += pixels[i] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; - pixels[i] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < w; ++i) { - STBTT_assert(pixels[i] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i] = (unsigned char) (total / kernel_width); - } - - pixels += stride_in_bytes; - } -} - -static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) -{ - unsigned char buffer[STBTT_MAX_OVERSAMPLE]; - int safe_h = h - kernel_width; - int j; - STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze - for (j=0; j < w; ++j) { - int i; - unsigned int total; - STBTT_memset(buffer, 0, kernel_width); - - total = 0; - - // make kernel_width a constant in common cases so compiler can optimize out the divide - switch (kernel_width) { - case 2: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 2); - } - break; - case 3: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 3); - } - break; - case 4: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 4); - } - break; - case 5: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / 5); - } - break; - default: - for (i=0; i <= safe_h; ++i) { - total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; - buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - break; - } - - for (; i < h; ++i) { - STBTT_assert(pixels[i*stride_in_bytes] == 0); - total -= buffer[i & STBTT__OVER_MASK]; - pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); - } - - pixels += 1; - } -} - -static float stbtt__oversample_shift(int oversample) -{ - if (!oversample) - return 0.0f; - - // The prefilter is a box filter of width "oversample", - // which shifts phase by (oversample - 1)/2 pixels in - // oversampled space. We want to shift in the opposite - // direction to counter this. - return (float)-(oversample - 1) / (2.0f * (float)oversample); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k; - int missing_glyph_added = 0; - - k=0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - ranges[i].h_oversample = (unsigned char) spc->h_oversample; - ranges[i].v_oversample = (unsigned char) spc->v_oversample; - for (j=0; j < ranges[i].num_chars; ++j) { - int x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - if (glyph == 0 && (spc->skip_missing || missing_glyph_added)) { - rects[k].w = rects[k].h = 0; - } else { - stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - &x0,&y0,&x1,&y1); - rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); - rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); - if (glyph == 0) - missing_glyph_added = 1; - } - ++k; - } - } - - return k; -} - -STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) -{ - stbtt_MakeGlyphBitmapSubpixel(info, - output, - out_w - (prefilter_x - 1), - out_h - (prefilter_y - 1), - out_stride, - scale_x, - scale_y, - shift_x, - shift_y, - glyph); - - if (prefilter_x > 1) - stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); - - if (prefilter_y > 1) - stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); - - *sub_x = stbtt__oversample_shift(prefilter_x); - *sub_y = stbtt__oversample_shift(prefilter_y); -} - -// rects array must be big enough to accommodate all characters in the given ranges -STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) -{ - int i,j,k, missing_glyph = -1, return_value = 1; - - // save current values - int old_h_over = spc->h_oversample; - int old_v_over = spc->v_oversample; - - k = 0; - for (i=0; i < num_ranges; ++i) { - float fh = ranges[i].font_size; - float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); - float recip_h,recip_v,sub_x,sub_y; - spc->h_oversample = ranges[i].h_oversample; - spc->v_oversample = ranges[i].v_oversample; - recip_h = 1.0f / spc->h_oversample; - recip_v = 1.0f / spc->v_oversample; - sub_x = stbtt__oversample_shift(spc->h_oversample); - sub_y = stbtt__oversample_shift(spc->v_oversample); - for (j=0; j < ranges[i].num_chars; ++j) { - stbrp_rect *r = &rects[k]; - if (r->was_packed && r->w != 0 && r->h != 0) { - stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; - int advance, lsb, x0,y0,x1,y1; - int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; - int glyph = stbtt_FindGlyphIndex(info, codepoint); - stbrp_coord pad = (stbrp_coord) spc->padding; - - // pad on left and top - r->x += pad; - r->y += pad; - r->w -= pad; - r->h -= pad; - stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); - stbtt_GetGlyphBitmapBox(info, glyph, - scale * spc->h_oversample, - scale * spc->v_oversample, - &x0,&y0,&x1,&y1); - stbtt_MakeGlyphBitmapSubpixel(info, - spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w - spc->h_oversample+1, - r->h - spc->v_oversample+1, - spc->stride_in_bytes, - scale * spc->h_oversample, - scale * spc->v_oversample, - 0,0, - glyph); - - if (spc->h_oversample > 1) - stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->h_oversample); - - if (spc->v_oversample > 1) - stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, - r->w, r->h, spc->stride_in_bytes, - spc->v_oversample); - - bc->x0 = (stbtt_int16) r->x; - bc->y0 = (stbtt_int16) r->y; - bc->x1 = (stbtt_int16) (r->x + r->w); - bc->y1 = (stbtt_int16) (r->y + r->h); - bc->xadvance = scale * advance; - bc->xoff = (float) x0 * recip_h + sub_x; - bc->yoff = (float) y0 * recip_v + sub_y; - bc->xoff2 = (x0 + r->w) * recip_h + sub_x; - bc->yoff2 = (y0 + r->h) * recip_v + sub_y; - - if (glyph == 0) - missing_glyph = j; - } else if (spc->skip_missing) { - return_value = 0; - } else if (r->was_packed && r->w == 0 && r->h == 0 && missing_glyph >= 0) { - ranges[i].chardata_for_range[j] = ranges[i].chardata_for_range[missing_glyph]; - } else { - return_value = 0; // if any fail, report failure - } - - ++k; - } - } - - // restore original values - spc->h_oversample = old_h_over; - spc->v_oversample = old_v_over; - - return return_value; -} - -STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) -{ - stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); -} - -STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) -{ - stbtt_fontinfo info; - int i,j,n, return_value = 1; - //stbrp_context *context = (stbrp_context *) spc->pack_info; - stbrp_rect *rects; - - // flag all characters as NOT packed - for (i=0; i < num_ranges; ++i) - for (j=0; j < ranges[i].num_chars; ++j) - ranges[i].chardata_for_range[j].x0 = - ranges[i].chardata_for_range[j].y0 = - ranges[i].chardata_for_range[j].x1 = - ranges[i].chardata_for_range[j].y1 = 0; - - n = 0; - for (i=0; i < num_ranges; ++i) - n += ranges[i].num_chars; - - rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); - if (rects == NULL) - return 0; - - info.userdata = spc->user_allocator_context; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); - - n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); - - stbtt_PackFontRangesPackRects(spc, rects, n); - - return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); - - STBTT_free(rects, spc->user_allocator_context); - return return_value; -} - -STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, - int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) -{ - stbtt_pack_range range; - range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; - range.array_of_unicode_codepoints = NULL; - range.num_chars = num_chars_in_range; - range.chardata_for_range = chardata_for_range; - range.font_size = font_size; - return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); -} - -STBTT_DEF void stbtt_GetScaledFontVMetrics(const unsigned char *fontdata, int index, float size, float *ascent, float *descent, float *lineGap) -{ - int i_ascent, i_descent, i_lineGap; - float scale; - stbtt_fontinfo info; - stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata, index)); - scale = size > 0 ? stbtt_ScaleForPixelHeight(&info, size) : stbtt_ScaleForMappingEmToPixels(&info, -size); - stbtt_GetFontVMetrics(&info, &i_ascent, &i_descent, &i_lineGap); - *ascent = (float) i_ascent * scale; - *descent = (float) i_descent * scale; - *lineGap = (float) i_lineGap * scale; -} - -STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) -{ - float ipw = 1.0f / pw, iph = 1.0f / ph; - const stbtt_packedchar *b = chardata + char_index; - - if (align_to_integer) { - float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); - float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); - q->x0 = x; - q->y0 = y; - q->x1 = x + b->xoff2 - b->xoff; - q->y1 = y + b->yoff2 - b->yoff; - } else { - q->x0 = *xpos + b->xoff; - q->y0 = *ypos + b->yoff; - q->x1 = *xpos + b->xoff2; - q->y1 = *ypos + b->yoff2; - } - - q->s0 = b->x0 * ipw; - q->t0 = b->y0 * iph; - q->s1 = b->x1 * ipw; - q->t1 = b->y1 * iph; - - *xpos += b->xadvance; -} - -////////////////////////////////////////////////////////////////////////////// -// -// sdf computation -// - -#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) -#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) - -static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) -{ - float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; - float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; - float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; - float roperp = orig[1]*ray[0] - orig[0]*ray[1]; - - float a = q0perp - 2*q1perp + q2perp; - float b = q1perp - q0perp; - float c = q0perp - roperp; - - float s0 = 0., s1 = 0.; - int num_s = 0; - - if (a != 0.0) { - float discr = b*b - a*c; - if (discr > 0.0) { - float rcpna = -1 / a; - float d = (float) STBTT_sqrt(discr); - s0 = (b+d) * rcpna; - s1 = (b-d) * rcpna; - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { - if (num_s == 0) s0 = s1; - ++num_s; - } - } - } else { - // 2*b*s + c = 0 - // s = -c / (2*b) - s0 = c / (-2 * b); - if (s0 >= 0.0 && s0 <= 1.0) - num_s = 1; - } - - if (num_s == 0) - return 0; - else { - float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); - float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; - - float q0d = q0[0]*rayn_x + q0[1]*rayn_y; - float q1d = q1[0]*rayn_x + q1[1]*rayn_y; - float q2d = q2[0]*rayn_x + q2[1]*rayn_y; - float rod = orig[0]*rayn_x + orig[1]*rayn_y; - - float q10d = q1d - q0d; - float q20d = q2d - q0d; - float q0rd = q0d - rod; - - hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; - hits[0][1] = a*s0+b; - - if (num_s > 1) { - hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; - hits[1][1] = a*s1+b; - return 2; - } else { - return 1; - } - } -} - -static int equal(float *a, float *b) -{ - return (a[0] == b[0] && a[1] == b[1]); -} - -static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) -{ - int i; - float orig[2], ray[2] = { 1, 0 }; - float y_frac; - int winding = 0; - - // make sure y never passes through a vertex of the shape - y_frac = (float) STBTT_fmod(y, 1.0f); - if (y_frac < 0.01f) - y += 0.01f; - else if (y_frac > 0.99f) - y -= 0.01f; - - orig[0] = x; - orig[1] = y; - - // test a ray from (-infinity,y) to (x,y) - for (i=0; i < nverts; ++i) { - if (verts[i].type == STBTT_vline) { - int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; - int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } - if (verts[i].type == STBTT_vcurve) { - int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; - int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; - int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; - int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); - int by = STBTT_max(y0,STBTT_max(y1,y2)); - if (y > ay && y < by && x > ax) { - float q0[2],q1[2],q2[2]; - float hits[2][2]; - q0[0] = (float)x0; - q0[1] = (float)y0; - q1[0] = (float)x1; - q1[1] = (float)y1; - q2[0] = (float)x2; - q2[1] = (float)y2; - if (equal(q0,q1) || equal(q1,q2)) { - x0 = (int)verts[i-1].x; - y0 = (int)verts[i-1].y; - x1 = (int)verts[i ].x; - y1 = (int)verts[i ].y; - if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { - float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; - if (x_inter < x) - winding += (y0 < y1) ? 1 : -1; - } - } else { - int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); - if (num_hits >= 1) - if (hits[0][0] < 0) - winding += (hits[0][1] < 0 ? -1 : 1); - if (num_hits >= 2) - if (hits[1][0] < 0) - winding += (hits[1][1] < 0 ? -1 : 1); - } - } - } - } - return winding; -} - -static float stbtt__cuberoot( float x ) -{ - if (x<0) - return -(float) STBTT_pow(-x,1.0f/3.0f); - else - return (float) STBTT_pow( x,1.0f/3.0f); -} - -// x^3 + a*x^2 + b*x + c = 0 -static int stbtt__solve_cubic(float a, float b, float c, float* r) -{ - float s = -a / 3; - float p = b - a*a / 3; - float q = a * (2*a*a - 9*b) / 27 + c; - float p3 = p*p*p; - float d = q*q + 4*p3 / 27; - if (d >= 0) { - float z = (float) STBTT_sqrt(d); - float u = (-q + z) / 2; - float v = (-q - z) / 2; - u = stbtt__cuberoot(u); - v = stbtt__cuberoot(v); - r[0] = s + u + v; - return 1; - } else { - float u = (float) STBTT_sqrt(-p/3); - float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative - float m = (float) STBTT_cos(v); - float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; - r[0] = s + u * 2 * m; - r[1] = s - u * (m + n); - r[2] = s - u * (m - n); - - //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? - //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); - //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); - return 3; - } -} - -STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - float scale_x = scale, scale_y = scale; - int ix0,iy0,ix1,iy1; - int w,h; - unsigned char *data; - - if (scale == 0) return NULL; - - stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); - - // if empty, return NULL - if (ix0 == ix1 || iy0 == iy1) - return NULL; - - ix0 -= padding; - iy0 -= padding; - ix1 += padding; - iy1 += padding; - - w = (ix1 - ix0); - h = (iy1 - iy0); - - if (width ) *width = w; - if (height) *height = h; - if (xoff ) *xoff = ix0; - if (yoff ) *yoff = iy0; - - // invert for y-downwards bitmaps - scale_y = -scale_y; - - { - int x,y,i,j; - float *precompute; - stbtt_vertex *verts; - int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); - data = (unsigned char *) STBTT_malloc(w * h, info->userdata); - precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); - - for (i=0,j=num_verts-1; i < num_verts; j=i++) { - if (verts[i].type == STBTT_vline) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; - float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); - precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; - float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; - float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float len2 = bx*bx + by*by; - if (len2 != 0.0f) - precompute[i] = 1.0f / (bx*bx + by*by); - else - precompute[i] = 0.0f; - } else - precompute[i] = 0.0f; - } - - for (y=iy0; y < iy1; ++y) { - for (x=ix0; x < ix1; ++x) { - float val; - float min_dist = 999999.0f; - float sx = (float) x + 0.5f; - float sy = (float) y + 0.5f; - float x_gspace = (sx / scale_x); - float y_gspace = (sy / scale_y); - - int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path - - for (i=0; i < num_verts; ++i) { - float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; - - if (verts[i].type == STBTT_vline && precompute[i] != 0.0f) { - float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; - - float dist,dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - // coarse culling against bbox - //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && - // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) - dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; - STBTT_assert(i != 0); - if (dist < min_dist) { - // check position along line - // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) - // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) - float dx = x1-x0, dy = y1-y0; - float px = x0-sx, py = y0-sy; - // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy - // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve - float t = -(px*dx + py*dy) / (dx*dx + dy*dy); - if (t >= 0.0f && t <= 1.0f) - min_dist = dist; - } - } else if (verts[i].type == STBTT_vcurve) { - float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; - float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; - float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); - float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); - float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); - float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); - // coarse culling against bbox to avoid computing cubic unnecessarily - if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { - int num=0; - float ax = x1-x0, ay = y1-y0; - float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; - float mx = x0 - sx, my = y0 - sy; - float res[3] = {0.f,0.f,0.f}; - float px,py,t,it,dist2; - float a_inv = precompute[i]; - if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula - float a = 3*(ax*bx + ay*by); - float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); - float c = mx*ax+my*ay; - if (a == 0.0) { // if a is 0, it's linear - if (b != 0.0) { - res[num++] = -c/b; - } - } else { - float discriminant = b*b - 4*a*c; - if (discriminant < 0) - num = 0; - else { - float root = (float) STBTT_sqrt(discriminant); - res[0] = (-b - root)/(2*a); - res[1] = (-b + root)/(2*a); - num = 2; // don't bother distinguishing 1-solution case, as code below will still work - } - } - } else { - float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point - float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; - float d = (mx*ax+my*ay) * a_inv; - num = stbtt__solve_cubic(b, c, d, res); - } - dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); - if (dist2 < min_dist*min_dist) - min_dist = (float) STBTT_sqrt(dist2); - - if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { - t = res[0], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { - t = res[1], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { - t = res[2], it = 1.0f - t; - px = it*it*x0 + 2*t*it*x1 + t*t*x2; - py = it*it*y0 + 2*t*it*y1 + t*t*y2; - dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); - if (dist2 < min_dist * min_dist) - min_dist = (float) STBTT_sqrt(dist2); - } - } - } - } - if (winding == 0) - min_dist = -min_dist; // if outside the shape, value is negative - val = onedge_value + pixel_dist_scale * min_dist; - if (val < 0) - val = 0; - else if (val > 255) - val = 255; - data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; - } - } - STBTT_free(precompute, info->userdata); - STBTT_free(verts, info->userdata); - } - return data; -} - -STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) -{ - return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); -} - -STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) -{ - STBTT_free(bitmap, userdata); -} - -////////////////////////////////////////////////////////////////////////////// -// -// font name matching -- recommended not to use this -// - -// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string -static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) -{ - stbtt_int32 i=0; - - // convert utf16 to utf8 and compare the results while converting - while (len2) { - stbtt_uint16 ch = s2[0]*256 + s2[1]; - if (ch < 0x80) { - if (i >= len1) return -1; - if (s1[i++] != ch) return -1; - } else if (ch < 0x800) { - if (i+1 >= len1) return -1; - if (s1[i++] != 0xc0 + (ch >> 6)) return -1; - if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; - } else if (ch >= 0xd800 && ch < 0xdc00) { - stbtt_uint32 c; - stbtt_uint16 ch2 = s2[2]*256 + s2[3]; - if (i+3 >= len1) return -1; - c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; - if (s1[i++] != 0xf0 + (c >> 18)) return -1; - if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; - s2 += 2; // plus another 2 below - len2 -= 2; - } else if (ch >= 0xdc00 && ch < 0xe000) { - return -1; - } else { - if (i+2 >= len1) return -1; - if (s1[i++] != 0xe0 + (ch >> 12)) return -1; - if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; - if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; - } - s2 += 2; - len2 -= 2; - } - return i; -} - -static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) -{ - return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); -} - -// returns results in whatever encoding you request... but note that 2-byte encodings -// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare -STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) -{ - stbtt_int32 i,count,stringOffset; - stbtt_uint8 *fc = font->data; - stbtt_uint32 offset = font->fontstart; - stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return NULL; - - count = ttUSHORT(fc+nm+2); - stringOffset = nm + ttUSHORT(fc+nm+4); - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) - && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { - *length = ttUSHORT(fc+loc+8); - return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); - } - } - return NULL; -} - -static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) -{ - stbtt_int32 i; - stbtt_int32 count = ttUSHORT(fc+nm+2); - stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); - - for (i=0; i < count; ++i) { - stbtt_uint32 loc = nm + 6 + 12 * i; - stbtt_int32 id = ttUSHORT(fc+loc+6); - if (id == target_id) { - // find the encoding - stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); - - // is this a Unicode encoding? - if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { - stbtt_int32 slen = ttUSHORT(fc+loc+8); - stbtt_int32 off = ttUSHORT(fc+loc+10); - - // check if there's a prefix match - stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); - if (matchlen >= 0) { - // check for target_id+1 immediately following, with same encoding & language - if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { - slen = ttUSHORT(fc+loc+12+8); - off = ttUSHORT(fc+loc+12+10); - if (slen == 0) { - if (matchlen == nlen) - return 1; - } else if (matchlen < nlen && name[matchlen] == ' ') { - ++matchlen; - if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) - return 1; - } - } else { - // if nothing immediately following - if (matchlen == nlen) - return 1; - } - } - } - - // @TODO handle other encodings - } - } - return 0; -} - -static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) -{ - stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); - stbtt_uint32 nm,hd; - if (!stbtt__isfont(fc+offset)) return 0; - - // check italics/bold/underline flags in macStyle... - if (flags) { - hd = stbtt__find_table(fc, offset, "head"); - if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; - } - - nm = stbtt__find_table(fc, offset, "name"); - if (!nm) return 0; - - if (flags) { - // if we checked the macStyle flags, then just check the family and ignore the subfamily - if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } else { - if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; - if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; - } - - return 0; -} - -static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) -{ - stbtt_int32 i; - for (i=0;;++i) { - stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); - if (off < 0) return off; - if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) - return off; - } -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wcast-qual" -#endif - -STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, - float pixel_height, unsigned char *pixels, int pw, int ph, - int first_char, int num_chars, stbtt_bakedchar *chardata) -{ - return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); -} - -STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) -{ - return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); -} - -STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) -{ - return stbtt_GetNumberOfFonts_internal((unsigned char *) data); -} - -STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) -{ - return stbtt_InitFont_internal(info, (unsigned char *) data, offset); -} - -STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) -{ - return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); -} - -STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) -{ - return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); -} - -#if defined(__GNUC__) || defined(__clang__) -#pragma GCC diagnostic pop -#endif - -#endif // STB_TRUETYPE_IMPLEMENTATION - - -// FULL VERSION HISTORY -// -// 1.25 (2021-07-11) many fixes -// 1.24 (2020-02-05) fix warning -// 1.23 (2020-02-02) query SVG data for glyphs; query whole kerning table (but only kern not GPOS) -// 1.22 (2019-08-11) minimize missing-glyph duplication; fix kerning if both 'GPOS' and 'kern' are defined -// 1.21 (2019-02-25) fix warning -// 1.20 (2019-02-07) PackFontRange skips missing codepoints; GetScaleFontVMetrics() -// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod -// 1.18 (2018-01-29) add missing function -// 1.17 (2017-07-23) make more arguments const; doc fix -// 1.16 (2017-07-12) SDF support -// 1.15 (2017-03-03) make more arguments const -// 1.14 (2017-01-16) num-fonts-in-TTC function -// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts -// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual -// 1.11 (2016-04-02) fix unused-variable warning -// 1.10 (2016-04-02) allow user-defined fabs() replacement -// fix memory leak if fontsize=0.0 -// fix warning from duplicate typedef -// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges -// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges -// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; -// allow PackFontRanges to pack and render in separate phases; -// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); -// fixed an assert() bug in the new rasterizer -// replace assert() with STBTT_assert() in new rasterizer -// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) -// also more precise AA rasterizer, except if shapes overlap -// remove need for STBTT_sort -// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC -// 1.04 (2015-04-15) typo in example -// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes -// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ -// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match -// non-oversampled; STBTT_POINT_SIZE for packed case only -// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling -// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) -// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID -// 0.8b (2014-07-07) fix a warning -// 0.8 (2014-05-25) fix a few more warnings -// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back -// 0.6c (2012-07-24) improve documentation -// 0.6b (2012-07-20) fix a few more warnings -// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, -// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty -// 0.5 (2011-12-09) bugfixes: -// subpixel glyph renderer computed wrong bounding box -// first vertex of shape can be off-curve (FreeSans) -// 0.4b (2011-12-03) fixed an error in the font baking example -// 0.4 (2011-12-01) kerning, subpixel rendering (tor) -// bugfixes for: -// codepoint-to-glyph conversion using table fmt=12 -// codepoint-to-glyph conversion using table fmt=4 -// stbtt_GetBakedQuad with non-square texture (Zer) -// updated Hello World! sample to use kerning and subpixel -// fixed some warnings -// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) -// userdata, malloc-from-userdata, non-zero fill (stb) -// 0.2 (2009-03-11) Fix unsigned/signed char warnings -// 0.1 (2009-03-09) First public release -// - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/text_rendering/util.c b/text_rendering/util.c deleted file mode 100644 index 2fbb03e..0000000 --- a/text_rendering/util.c +++ /dev/null @@ -1,126 +0,0 @@ -#define _POSIX_C_SOURCE 200809l - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "util.h" - - -const char *bit_rep[16] = { - [ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011", - [ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111", - [ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011", - [12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111", -}; - - - -void * emalloc(unsigned long int size) -{ - void *r = malloc(size); - if (!r) - err(EXIT_FAILURE, "malloc() of size %ld", size); - return r; -} - - -void * ecalloc(unsigned long int nmemb, unsigned long int size) -{ - void *r = calloc(nmemb, size); - if (!r) - err(EXIT_FAILURE, "calloc() of size %ld, nmemb %ld", size, nmemb); - return r; -} - -void * erealloc(void *ptr, unsigned long int size) -{ - void *r = realloc(ptr, size); - if (!r) - err(EXIT_FAILURE, "ralloc() of 0x%lx, to size %ld", (unsigned long)ptr, size); - return r; -} - - -void efree(void *ptr) -{ - if (ptr) - free(ptr); -} - - -void map_file(const unsigned char **str, int *size, const char *path) -{ - if (!path) { - errno = EINVAL; - err(EXIT_FAILURE, "NULL filename"); - } - FILE *fp = fopen(path, "r"); - if (!fp) - err(EXIT_FAILURE, "Cannot open file %s", path); - *size = lseek(fileno(fp), 0, SEEK_END); - if (*size == (off_t)-1) - err(EXIT_FAILURE, "lseek() failed"); - *str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0); - if (*str == (void*)-1) - err(EXIT_FAILURE, "mmap() failed"); - if (fclose(fp)) - err(EXIT_FAILURE, "Error closing file"); -} - - -void dump_file(const char *path, char **buf, int *buf_len) -{ - if (!path) { - errno = EINVAL; - err(EXIT_FAILURE, "NULL filename"); - } - if (!buf) { - errno = EINVAL; - err(EXIT_FAILURE, "No buffer specified"); - } - int m = 0; - if (!buf_len) - buf_len = &m; - FILE *fp = fopen(path, "r"); - if (!fp) - err(EXIT_FAILURE, "Cannot open file %s", path); - *buf_len = lseek(fileno(fp), 0, SEEK_END); - rewind(fp); - if (*buf_len == (off_t)-1) - err(EXIT_FAILURE, "lseek() failed"); - *buf = emalloc(*buf_len+1); - memset(*buf, 0, *buf_len+1); - int ret = fread(*buf, 1, *buf_len, fp); - if (ret != *buf_len) - err(EXIT_FAILURE, "fread() returned short %s", ferror(fp) ? "stream error" : feof(fp) ? "EOF reached" : "unknown error"); - if (fclose(fp)) - err(EXIT_FAILURE, "Error closing file"); -} - - -void print_byte(unsigned char byte) -{ - printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]); -} - - -static struct timespec clock_start, clock_stop; - -void stopwatch_start(void) -{ - clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_start); -} - - -double stopwatch_get(void) -{ - clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_stop); - return (clock_stop.tv_sec-clock_start.tv_sec)+(double)(clock_stop.tv_nsec-clock_start.tv_nsec)/(double)1000000000L; -} \ No newline at end of file diff --git a/text_rendering/util.h b/text_rendering/util.h deleted file mode 100644 index 6712805..0000000 --- a/text_rendering/util.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef _UTIL_H -#define _UTIL_H - -#include - - -void * emalloc(unsigned long int size); -void * ecalloc(unsigned long int nmemb, unsigned long int size); -void * erealloc(void *ptr, unsigned long int size); -void efree(void *ptr); - -void map_file(const unsigned char **str, int *size, const char *path); -void dump_file(const char *path, char **buf, int *buf_len); - -void print_byte(unsigned char byte); - -void stopwatch_start(void); -double stopwatch_get(void); - -#define TIME_SEC(f) \ -{ \ - stopwatch_start(); \ - f; \ - printf("\"%s\" took %f seconds", #f, stopwatch_get()); \ -} - -#define TIME_MS(f) \ -{ \ - stopwatch_start(); \ - f; \ - printf("\"%s\" took %f ms", #f, stopwatch_get()*1000.0f); \ -} - -#endif diff --git a/ugui.c b/ugui.c deleted file mode 100644 index 57e83c9..0000000 --- a/ugui.c +++ /dev/null @@ -1,1461 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "ugui.h" -#include "def_style.h" - - -#define SALT 0xbabb0cac -#define DEF_SCALE 1.0 -#define DEF_PPI 96.0 -#define STACK_STEP 64 -#define SLICE_STEP 128 - -#define RESIZEALL ( \ - UG_CNT_RESIZE_RIGHT | \ - UG_CNT_RESIZE_BOTTOM | \ - UG_CNT_RESIZE_LEFT | \ - UG_CNT_RESIZE_TOP \ - ) - -#define BTN_ANY ( \ - UG_BTN_LEFT | \ - UG_BTN_RIGHT | \ - UG_BTN_MIDDLE | \ - UG_BTN_4 | \ - UG_BTN_5 \ - ) - -#define CNT_STATE_ALL ( \ - CNT_STATE_NONE | \ - CNT_STATE_MOVING | \ - CNT_STATE_RESIZE_T | \ - CNT_STATE_RESIZE_B | \ - CNT_STATE_RESIZE_L | \ - CNT_STATE_RESIZE_R | \ - CNT_STATE_RESIZE_D \ - ) - -#define PPI_PPM(ppi, scale) (ppi * scale * 0.03937008) -#define PPI_PPD(ppi, scale) (PPI_PPM(ppi, scale) * 0.3528) -#define IS_VALID_UNIT(u) (u==UG_UNIT_PX||u==UG_UNIT_MM||u==UG_UNIT_PT) -#define UG_ERR(...) err(errno, "__FUNCTION__: " __VA_ARGS__) -#define BETWEEN(x, min, max) (x <= max && x >= min) -#define INTERSECTS(v, r) (BETWEEN(v.x, r.x, r.x+r.w) && BETWEEN(v.y, r.y, r.y+r.h)) -#define TEST(f, b) (f & (b)) -#define MAX(a, b) (a > b ? a : b) -#define R_MARGIN(r, b) (r.x + r.w - b) -#define L_MARGIN(r, b) (r.x + b) -#define T_MARGIN(r, b) (r.y + b) -#define B_MARGIN(r, b) (r.y + r.h - b) -// check if ra is outside of rb -#define OUTSIDE(ra, ba, rb, bb) (L_MARGIN(ra, ba) > R_MARGIN(rb, bb) || \ - T_MARGIN(ra, ba) > B_MARGIN(rb, bb) || \ - R_MARGIN(ra, ba) < L_MARGIN(rb, bb) || \ - B_MARGIN(ra, ba) < T_MARGIN(rb, bb)) - - -static ug_style_t style_cache = {0}; - - -/*=============================================================================* - * Common Functions * - *=============================================================================*/ - - -// grow a stack -#define GROW_STACK(S) \ -{ \ - S.items = realloc(S.items, (S.size+STACK_STEP)*sizeof(*(S.items))); \ - if(!S.items) \ - UG_ERR("Could not allocate stack #S: %s", strerror(errno)); \ - memset(&(S.items[S.size]), 0, STACK_STEP*sizeof(*(S.items))); \ - S.size += STACK_STEP; \ -} - - -#define GET_FROM_STACK(S, c) \ -{ \ - if (S.idx >= S.size) \ - GROW_STACK(S); \ - c = &(S.items[S.idx++]); \ -} - - -// delete all items that have the same id from the stack, also shrink it if the total -// number of items is less than a third of the capacity -#define DELETE_FROM_STACK(S, c) \ -{ \ - for (int i = 0; i < S.idx; i++) { \ - if (c->id != S.items[i].id) \ - continue; \ - memmove(&S.items[i], &S.items[i+1], (S.idx-i)*sizeof(S.items[0])); \ - memset(&S.items[S.idx--], 0, sizeof(S.items[0])); \ - } \ - if (S.size > STACK_STEP && S.idx*3 < S.size) { \ - S.items = realloc(S.items, (S.size-STACK_STEP)*sizeof(*(S.items))); \ - if(!S.items) \ - UG_ERR("Could not allocate stack #S: %s", strerror(errno)); \ - S.size -= STACK_STEP; \ - } \ -} \ - - -// FIXME: is it really necessary to clear (memset) the stack -#define RESET_STACK(S) \ -{ \ - memset(S.items, 0, S.idx*sizeof(*(S.items))); \ - S.idx = 0; \ -} - - -#define MOUSEDOWN(ctx, btn) (~(ctx->mouse.hold & btn) & (ctx->mouse.update & btn)) -#define MOUSEUP(ctx, btn) ((ctx->mouse.hold & btn) & (ctx->mouse.update & btn)) -#define MOUSEHELD(ctx, btn) (ctx->mouse.hold & btn) - - -// https://en.wikipedia.org/wiki/Jenkins_hash_function -static ug_id_t hash(const void *data, unsigned int size) -{ - if (!size) - return 0; - ug_id_t hash = SALT; - unsigned char *v = (unsigned char *)data; - - for (; size; size--) { - hash += v[size-1]; - hash += hash << 10; - hash ^= hash >> 6; - } - hash += hash << 3; - hash ^= hash >> 11; - hash += hash << 15; - - return hash; -} - - -int size_to_px(ug_ctx_t *ctx, ug_size_t s) -{ - switch (s.unit) { - case UG_UNIT_MM: return roundf(s.size.f * ctx->ppm); - case UG_UNIT_PT: return roundf(s.size.f * ctx->ppd); - case UG_UNIT_PX: return s.size.i; - default: return 0; - } -} - -ug_rect_t div_to_rect(ug_ctx_t *ctx, ug_div_t *div) -{ - ug_rect_t r; - r.x = size_to_px(ctx, div->x); - r.y = size_to_px(ctx, div->y); - r.w = size_to_px(ctx, div->w); - r.h = size_to_px(ctx, div->h); - return r; -} - - -// update the style cache with the correct sizes in pixels and colors -static void update_style_cache(ug_ctx_t *ctx) -{ - const ug_style_t *s = ctx->style; - // FIME: use the correct units and convert, for now assume default style - style_cache = *s; -} - - -void scale_rect(ug_ctx_t *ctx, ug_rect_t *rect) -{ - if (ctx->ppi != ctx->last_ppi) { - // if the scale has been updated than we need to scale the container - // as well - float scale = ctx->ppi / ctx->last_ppi; - rect->x = roundf(rect->x * scale); - rect->y = roundf(rect->y * scale); - rect->w = roundf(rect->w * scale); - rect->h = roundf(rect->h * scale); - } -} - - -int crop_rect(ug_rect_t *ra, ug_rect_t *rb) -{ - int cropped = 0; - if (L_MARGIN((*ra), 0) < L_MARGIN((*rb), 0)) { - ra->w -= L_MARGIN((*rb), 0) - L_MARGIN((*ra), 0); - ra->x = rb->x; - cropped = 1; - } - if (R_MARGIN((*ra), 0) > R_MARGIN((*rb), 0)) { - ra->w -= R_MARGIN((*ra), 0) - R_MARGIN((*rb), 0); - cropped = 1; - } - if (T_MARGIN((*ra), 0) < T_MARGIN((*rb), 0)) { - ra->h -= T_MARGIN((*rb), 0) - T_MARGIN((*ra), 0); - ra->y = rb->y; - cropped = 1; - } - if (B_MARGIN((*ra), 0) > B_MARGIN((*rb), 0)) { - ra->h -= B_MARGIN((*ra), 0)- B_MARGIN((*rb), 0); - cropped = 1; - } - return cropped; -} - - -/*=============================================================================* - * Command Operations * - *=============================================================================*/ - - -// TODO: when pushing onto the stack calculate the incremental hash of the whole -// stack instead of calculating it at the end, this saves us one last scroll -// trought the draw stack -static void push_rect_command(ug_ctx_t *ctx, const ug_rect_t *rect, ug_color_t color) -{ - ug_cmd_t *c; - GET_FROM_STACK(ctx->cmd_stack, c); - c->type = UG_CMD_RECT; - c->rect.x = rect->x; - c->rect.y = rect->y; - c->rect.w = rect->w; - c->rect.h = rect->h; - c->rect.color = color; -} - - -// pushes a text command to the render command stack, str is a pointer to a valid -// string offered by the user, it must be valid at least until rendering is done -static void push_text_command(ug_ctx_t *ctx, ug_vec2_t pos, int size, ug_color_t color, const char *str) -{ - ug_cmd_t *c; - GET_FROM_STACK(ctx->cmd_stack, c); - c->type = UG_CMD_TEXT; - c->text.x = pos.x; - c->text.y = pos.y; - c->text.size = size; - c->text.color = color; - c->text.str = str; -} - - -ug_cmd_t * ug_cmd_next(ug_ctx_t *ctx) -{ - if(!ctx) - return NULL; - if (ctx->cmd_it < ctx->cmd_stack.idx) - return &ctx->cmd_stack.items[ctx->cmd_it++]; - return NULL; -} - - -/*=============================================================================* - * Context Operations * - *=============================================================================*/ - - -// creates a new context, fills with default values, ctx is ready for ug_start() -ug_ctx_t * ug_ctx_new(void) -{ - ug_ctx_t *ctx = malloc(sizeof(ug_ctx_t)); - if (!ctx) - err(errno, "__FUNCTION__:" "Could not allocate context: %s", strerror(errno)); - memset(ctx, 0, sizeof(ug_ctx_t)); - - ctx->style = &default_style; - ctx->style_px = &style_cache; - // NOTE: this fixes a bug where for the first frame the container stack - // doesn't get sorted, but it is kind of a botch - ctx->last_active_cnt = 1; - ug_ctx_set_displayinfo(ctx, DEF_SCALE, DEF_PPI); - - // TODO: allocate stacks - return ctx; -} - - -void ug_ctx_free(ug_ctx_t *ctx) -{ - if (!ctx) { - warn("__FUNCTION__:" "Trying to free a null context"); - return; - } - - free(ctx->cmd_stack.items); - free(ctx->cnt_stack.items); - - free(ctx); - - // NOTE: do not free style since the default style is statically allocated, - // let the user take care of it instead -} - - -// TODO: add error codes -#define TEST_CTX(ctx) { if (!ctx) return -1; } -#define TEST_STR(s) { if (!s || !s[0]) return -1; } - - -int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi) -{ - TEST_CTX(ctx); - if (scale <= 0 || ppi < 20.0) - return -1; - - ctx->last_ppi = ctx->ppi; - ctx->last_ppm = ctx->ppd; - ctx->last_ppd = ctx->ppm; - - ctx->ppm = PPI_PPM(scale, ppi); - ctx->ppd = PPI_PPM(scale, ppi); - ctx->ppi = ppi; - - update_style_cache(ctx); - - return 0; -} - - -int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size) -{ - TEST_CTX(ctx); - if (size.w <= 0 || size.h <= 0) - return -1; - - ctx->size.w = size.w; - ctx->size.h = size.h; - - return 0; -} - - -int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style) -{ - TEST_CTX(ctx); - if (!style) - return -1; - // TODO: validate style - - ctx->style = style; - update_style_cache(ctx); - - return 0; -} - - -/*=============================================================================* - * Container Operations * - *=============================================================================*/ - -#define STATEIS(cnt, f) ((cnt->flags & CNT_STATE_ALL) == f) - -/* - * Container style: rca stands for absolute rectangle, it delimits the total - * drawable area, as such it does not include borders - * - * rca v Border Top v - * +-v--------------------------------------------------------+ - * | +------------------------------------------------------+ | - * | | Titlebar | | - * | +------------------------------------------------------+ | - * | +------------------------------------------------------+ | - * | |\.................................................... | | - * | | \ ^ Border Top ^ . | | - * | | .\_ rect(0,0) . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . < Border Left . | | - * | | . + Margin Border Right >. | | - * | | . + Margin . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | . . | | - * | | .................................................... | | - * | +------------------------------------------------------+ | - * +----------------------------------------------------------+ - * ^ Border Bottom ^ - */ - - -// search a container by id int the stack and get it's address -static ug_container_t *search_container(ug_ctx_t *ctx, ug_id_t id) -{ - ug_container_t *c = NULL; - for (int i = 0; i < ctx->cnt_stack.idx; i++) { - if (ctx->cnt_stack.items[i].id == id) { - c = &(ctx->cnt_stack.items[i]); - break; - } - } - return c; -} - - -// get a new or existing container handle -static ug_container_t *get_container(ug_ctx_t *ctx, ug_id_t id) -{ - ug_container_t *c = search_container(ctx, id); - // if the container was not already there allocate a new one - if (!c) { - GET_FROM_STACK(ctx->cnt_stack, c); - ctx->cnt_stack.sorted = 0; - } - - return c; -} - - -// sort the conatiner stack so that floating conatiners always stay on top and -// the active floating conatiner i son top of everything -static void sort_containers(ug_ctx_t *ctx) -{ - if (ctx->cnt_stack.sorted) - return; - int tot = ctx->cnt_stack.idx; - if (!tot) - return; - - for (int i = 0; i < tot; i++) { - ug_container_t *cnt = &ctx->cnt_stack.items[i]; - // move floating containers to the top - // FIXME: this is really shit - if (TEST(cnt->flags, UG_CNT_FLOATING)) { - int y = tot; - - ug_container_t *s = ctx->cnt_stack.items; - ug_container_t c = *cnt; - - if (ctx->active_cnt != c.id) - for (; y > 0 && TEST(s[y-1].flags, UG_CNT_FLOATING); y--); - - if (i >= y) - continue; - - memmove(&s[i], &s[i+1], (y - i) * sizeof(ug_container_t)); - s[y-1] = c; - } - } - ctx->cnt_stack.sorted = 1; -} - - -// update the container position in the context area -// TODO: can we generalize position_container and position_element into one or -// more similar functions -static int position_container(ug_ctx_t *ctx, ug_container_t *cnt) -{ - if (!TEST(cnt->flags, UG_CNT_FLOATING)) { - // if there is no space left propagate the error - if (ctx->origin.w <= 0 || ctx->origin.h <= 0) - return -1; - } - - ug_rect_t *rect, *rca; - rect = &(cnt->rect); - rca = &(cnt->rca); - scale_rect(ctx, rect); - - *rca = *rect; - - const ug_style_t *s = ctx->style_px; - int b = SZ_INT(s->border.size); - int hh = SZ_INT(s->title.height); - ug_rect_t c = { - .x = ctx->origin.x + b, - .y = ctx->origin.y + b, - .w = ctx->origin.w - 2*b, - .h = ctx->origin.h - 2*b - }; - - // handle relative sizes - if (rect->w == 0) rca->w = c.w; - if (rect->h == 0) rca->h = c.h; - else if (TEST(cnt->flags, UG_CNT_MOVABLE)) rca->h += hh + b; - - // handle borders, only draw borders on sides which can be used to resize - //if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) { - // rca->w += b; - //} - //if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) { - // rca->w += b; - //} - //if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) { - // rca->h += b; - //} - //if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) { - // rca->h += b; - //} - //if (TEST(cnt->flags, UG_CNT_MOVABLE)) { - // rca->h += b; - //} - - // if the container is not fixed than it can have positions outside of the - // main window, thus negative - if (!TEST(cnt->flags, UG_CNT_FLOATING)) { - // <0 -> relative to the right margin - if (rect->x < 0) rca->x = R_MARGIN(c, 0) - R_MARGIN((*rca), -1); - else rca->x = L_MARGIN(c, 0); - if (rect->y < 0) rca->y = B_MARGIN(c, 0) - B_MARGIN((*rca), -1); - else rca->y = T_MARGIN(c, 0); - - // if the container is fixed than update the available space in - // the context - if (rect->w) { - if (rect->x >= 0) - ctx->origin.x = R_MARGIN((*rca), -b); - ctx->origin.w -= rca->w; - } else if (rect->h) { - if (rect->y >= 0) - ctx->origin.y = B_MARGIN((*rca), -b); - ctx->origin.h -= rca->h; - } else { - // width and height zero means fill everything - ctx->origin.w = ctx->origin.h = 0; - } - } - - // set the correct element origin - cnt->c_orig.x = cnt->r_orig.x = cnt->space.x = L_MARGIN((*rca), 0); - cnt->c_orig.y = cnt->r_orig.y = cnt->space.y = T_MARGIN((*rca), 0); - if (TEST(cnt->flags, UG_CNT_MOVABLE)) { - cnt->c_orig.y += hh + SZ_INT(s->border.size); - cnt->r_orig.y += hh + SZ_INT(s->border.size); - cnt->space.y += hh + SZ_INT(s->border.size); - } - - return 0; -} - - -// TODO: make a set of fixed return values for the different states -// handle moving and resizing, return 1 if it had focus, 0 otherwise, negative -// return values represent errors -static int handle_container(ug_ctx_t *ctx, ug_container_t *cnt) -{ - // if we are not the currently active container than definately we are not - // being moved or resized - if (ctx->active_cnt != cnt->id) { - cnt->flags &= ~CNT_STATE_ALL; - return 0; - } - - // mouse pressed handle resize, for simplicity containers can only - // be resized from the bottom and right border - // TODO: change return value to indicate this case - if (!MOUSEHELD(ctx, UG_BTN_LEFT) || - !TEST(cnt->flags, (RESIZEALL | UG_CNT_MOVABLE))) - return 1; - - ug_rect_t *rect, *rca; - rect = &cnt->rect; - rca = &cnt->rca; - - const ug_style_t *s = ctx->style_px; - int b = SZ_INT(s->border.size); - int m = SZ_INT(s->margin); - int hh = SZ_INT(s->title.height); - - ug_vec2_t mpos = ctx->mouse.pos; - ug_rect_t clip; - - // handle movable windows - if (TEST(cnt->flags, UG_CNT_MOVABLE)) { - clip = (ug_rect_t){ - .x = L_MARGIN((*rca), 0), - .y = T_MARGIN((*rca), 0), - .w = rca->w, - .h = hh, - }; - if (STATEIS(cnt, CNT_STATE_MOVING) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) { - cnt->flags |= CNT_STATE_MOVING; - rect->x += ctx->mouse.delta.x; - rect->y += ctx->mouse.delta.y; - rca->x += ctx->mouse.delta.x; - rca->y += ctx->mouse.delta.y; - } - } - - // lower diagonal resize - if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM) && TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) { - clip = (ug_rect_t){ - .x = R_MARGIN((*rca), m), - .y = B_MARGIN((*rca), m), - .w = b + m, - .h = b + m, - }; - if (STATEIS(cnt, CNT_STATE_RESIZE_D) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) { - cnt->flags |= CNT_STATE_RESIZE_D; - rect->h = MAX(10, rect->h + ctx->mouse.delta.y); - rca->h = MAX(10, rca->h + ctx->mouse.delta.y); - rect->w = MAX(10, rect->w + ctx->mouse.delta.x); - rca->w = MAX(10, rca->w + ctx->mouse.delta.x); - } - } - - // right border resize - if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) { - clip = (ug_rect_t){ - .x = R_MARGIN((*rca), m), - .y = T_MARGIN((*rca), 0), - .w = b + m, - .h = rca->h, - }; - if (STATEIS(cnt, CNT_STATE_RESIZE_R) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) { - cnt->flags |= CNT_STATE_RESIZE_R; - rect->w = MAX(10, rect->w + ctx->mouse.delta.x); - rca->w = MAX(10, rca->w + ctx->mouse.delta.x); - } - } - - // left border resize - if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) { - clip = (ug_rect_t){ - .x = L_MARGIN((*rca), -b), - .y = T_MARGIN((*rca), 0), - .w = b + m, - .h = rca->h, - }; - if (STATEIS(cnt, CNT_STATE_RESIZE_L) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))){ - cnt->flags |= CNT_STATE_RESIZE_L; - - if (rect->w - ctx->mouse.delta.x >= 10) { - rect->w -= ctx->mouse.delta.x; - if (TEST(cnt->flags, UG_CNT_MOVABLE)) - rect->x += ctx->mouse.delta.x; - } - if (rca->w - ctx->mouse.delta.x >= 10) { - rca->w -= ctx->mouse.delta.x; - rca->x += ctx->mouse.delta.x; - } - } - } - - // bottom border resize - if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) { - clip = (ug_rect_t){ - .x = L_MARGIN((*rca), 0), - .y = B_MARGIN((*rca), m), - .w = rca->w, - .h = b + m, - }; - if (STATEIS(cnt, CNT_STATE_RESIZE_B) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) { - cnt->flags |= CNT_STATE_RESIZE_B; - rect->h = MAX(10, rect->h + ctx->mouse.delta.y); - rca->h = MAX(10, rca->h + ctx->mouse.delta.y); - } - } - - // top border resize - if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) { - clip = (ug_rect_t){ - .x = L_MARGIN((*rca), 0), - .y = T_MARGIN((*rca), -b), - .w = rca->w, - .h = b + m, - }; - if (STATEIS(cnt, CNT_STATE_RESIZE_T) || - (STATEIS(cnt, 0) && INTERSECTS(mpos, clip))) { - cnt->flags |= CNT_STATE_RESIZE_T; - - if (rect->h - ctx->mouse.delta.y >= 10) { - rect->h -= ctx->mouse.delta.y; - if (TEST(cnt->flags, UG_CNT_MOVABLE)) - rect->y += ctx->mouse.delta.y; - } - if (rca->h - ctx->mouse.delta.y >= 10) { - rca->h -= ctx->mouse.delta.y; - rca->y += ctx->mouse.delta.y; - } - } - } - - // if we were not resized but we are still active it means we are doing - // something to the contained elements, as such set state to none - if (STATEIS(cnt, 0)) - cnt->flags |= CNT_STATE_NONE; - - // TODO: what if I want to close a floating container? - // Maybe add a UG_CNT_CLOSABLE flag? - - // TODO: what about scrolling? how do we know if we need to draw - // a scroll bar? Maybe add that information inside the - // container structure - return 1; -} - -#define EXPAND(r, b) {r.x-=b,r.y-=b,r.w+=2*b,r.h+=2*b;} - -static void draw_container(ug_ctx_t *ctx, ug_container_t *cnt, const char *text) -{ - ug_rect_t rect; - const ug_style_t *s = ctx->style_px; - int b = SZ_INT(s->border.size); - int hh = SZ_INT(s->title.height); - int ts = SZ_INT(s->title.font_size); - - // push main body - rect = cnt->rca; - EXPAND(rect, b); - push_rect_command(ctx, &rect, s->color.bg); - - // push outline - if (TEST(cnt->flags, UG_CNT_RESIZE_LEFT)) { - rect = cnt->rca; - rect.x -= b; - rect.y -= b, - rect.h += 2*b; - rect.w = b; - push_rect_command(ctx, &rect, s->border.color); - } - if (TEST(cnt->flags, UG_CNT_RESIZE_RIGHT)) { - rect = cnt->rca; - rect.x += rect.w; - rect.y -= b; - rect.h += 2*b; - rect.w = b; - push_rect_command(ctx, &rect, s->border.color); - } - if (TEST(cnt->flags, UG_CNT_RESIZE_TOP)) { - rect = cnt->rca; - rect.y -= b; - rect.x -= b; - rect.h = b; - rect.w += 2*b; - push_rect_command(ctx, &rect, s->border.color); - } - if (TEST(cnt->flags, UG_CNT_RESIZE_BOTTOM)) { - rect = cnt->rca; - rect.x -= b; - rect.y += rect.h; - rect.h = b; - rect.w += 2*b; - push_rect_command(ctx, &rect, s->border.color); - } - // titlebar - if (TEST(cnt->flags, UG_CNT_MOVABLE)) { - // titlebar area - rect = cnt->rca; - rect.h = hh; - push_rect_command(ctx, &rect, s->title.color.bg); - // titlebar border - rect.y += hh; - rect.h = b; - push_rect_command(ctx, &rect, s->border.color); - if (text) { - // TODO: center text - push_text_command(ctx, - (ug_vec2_t){.x = rect.x, .y = rect.y}, - ts, s->title.color.fg, text); - } - } -} - - -// a floating container can be placed anywhere and can be resized, acts like a -// window inside another window -int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div) -{ - TEST_CTX(ctx); - TEST_STR(name); - // TODO: verify div - - ug_id_t id = hash(name, strlen(name)); - ug_container_t *cnt = get_container(ctx, id); - - // maybe the name address was changed so always overwrite it - cnt->name = name; - if (cnt->id) { - // nothing? maybe we can skip updating all dimensions and stuff - } else { - cnt->id = id; - cnt->rect = div_to_rect(ctx, &div); - cnt->flags = UG_CNT_FLOATING | RESIZEALL | UG_CNT_MOVABLE | - UG_CNT_SCROLL_X | UG_CNT_SCROLL_Y; - } - - // TODO: what about error codes and meaning? - if (position_container(ctx, cnt)) { - DELETE_FROM_STACK(ctx->cnt_stack, cnt); - return -1; - } - - // Select current conatiner - ctx->selected_cnt = id; - - return handle_container(ctx, cnt); -} - - -int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div) -{ - TEST_CTX(ctx); - TEST_STR(name); - // TODO: verify div - - ug_id_t id = hash(name, strlen(name)); - ug_container_t *cnt = get_container(ctx, id); - - // maybe the name address was changed so always overwrite it - cnt->name = name; - if (cnt->id) { - // nothing? maybe we can skip updating all dimensions and stuff - } else { - cnt->id = id; - cnt->rect = div_to_rect(ctx, &div); - cnt->flags = UG_CNT_FLOATING; - } - - // TODO: what about error codes and meaning? - if (position_container(ctx, cnt)) { - DELETE_FROM_STACK(ctx->cnt_stack, cnt); - return -1; - } - - // Select current conatiner - ctx->selected_cnt = id; - - return handle_container(ctx, cnt); -} - - -int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side) -{ - TEST_CTX(ctx); - TEST_STR(name); - - ug_id_t id = hash(name, strlen(name)); - ug_container_t *cnt = get_container(ctx, id); - - // maybe the name address was changed so always overwrite it - cnt->name = name; - if (cnt->id) { - // nothing? maybe we can skip updating all dimensions and stuff - } else { - cnt->id = id; - cnt->flags = UG_CNT_SCROLL_X | UG_CNT_SCROLL_Y; - ug_rect_t rect = {0}; - switch (side) { - case UG_SIDE_BOTTOM: - cnt->flags |= UG_CNT_RESIZE_TOP; - rect.y = -1; - rect.h = size_to_px(ctx, size); - break; - case UG_SIDE_TOP: - cnt->flags |= UG_CNT_RESIZE_BOTTOM; - rect.h = size_to_px(ctx, size); - break; - case UG_SIDE_RIGHT: - cnt->flags |= UG_CNT_RESIZE_LEFT; - rect.x = -1; - rect.w = size_to_px(ctx, size); - break; - case UG_SIDE_LEFT: - cnt->flags |= UG_CNT_RESIZE_RIGHT; - rect.w = size_to_px(ctx, size); - break; - default: return -1; - } - cnt->rect = rect; - } - - if (position_container(ctx, cnt)) { - DELETE_FROM_STACK(ctx->cnt_stack, cnt); - return -1; - } - - // Select current conatiner - ctx->selected_cnt = id; - - return handle_container(ctx, cnt); -} - -int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height) -{ - TEST_CTX(ctx); - TEST_STR(name); - - ug_id_t id = hash(name, strlen(name)); - ug_container_t *cnt = get_container(ctx, id); - - // maybe the name address was changed so always overwrite it - cnt->name = name; - if (cnt->id) { - // nothing? maybe we can skip updating all dimensions and stuff - } else { - cnt->id = id; - cnt->flags = 0; - ug_rect_t rect = { - .x = 0, .y = 0, - .w = 0, .h = size_to_px(ctx, height), - }; - cnt->rect = rect; - } - - if (position_container(ctx, cnt)) { - DELETE_FROM_STACK(ctx->cnt_stack, cnt); - return -1; - } - - // Select current conatiner - ctx->selected_cnt = id; - - return handle_container(ctx, cnt); -} - - -int ug_container_body(ug_ctx_t *ctx, const char *name) -{ - TEST_CTX(ctx); - TEST_STR(name); - - ug_id_t id = hash(name, strlen(name)); - ug_container_t *cnt = get_container(ctx, id); - - // maybe the name address was changed so always overwrite it - cnt->name = name; - if (cnt->id) { - // nothing? maybe we can skip updating all dimensions and stuff - } else { - cnt->id = id; - cnt->flags = 0; - cnt->rect = (ug_rect_t){0}; - } - - if (position_container(ctx, cnt)) { - DELETE_FROM_STACK(ctx->cnt_stack, cnt); - return -1; - } - - // Select current conatiner - ctx->selected_cnt = id; - - return handle_container(ctx, cnt); -} - - -// TODO: return an error indicating that no container exists with that name -int ug_container_remove(ug_ctx_t *ctx, const char *name) -{ - TEST_CTX(ctx); - - ug_id_t id = ctx->selected_cnt; - if (name) - id = hash(name, strlen(name)); - - ug_container_t *c = search_container(ctx, id); - if (c) { - c->flags |= CNT_STATE_DELETE; - return 0; - } else return -1; -} - - -ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name) -{ - ug_rect_t r = {0}; - if(!ctx) - return r; - - ug_id_t id = ctx->selected_cnt; - if (name) - id = hash(name, strlen(name)); - - ug_container_t *c = search_container(ctx, id); - if (c) - r = c->rca; - return r; -} - - -/*=============================================================================* - * Frame Handling * - *=============================================================================*/ - - -// At the beginning of a frame assume that all input has been passed to the context -// update the mouse delta and reset the command stack -int ug_frame_begin(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - // update mouse delta - ctx->mouse.delta.x = ctx->mouse.pos.x - ctx->mouse.last_pos.x; - ctx->mouse.delta.y = ctx->mouse.pos.y - ctx->mouse.last_pos.y; - - // clear command stack - RESET_STACK(ctx->cmd_stack); - - // update the origin - ctx->origin.x = 0; - ctx->origin.y = 0; - ctx->origin.w = ctx->size.w; - ctx->origin.h = ctx->size.h; - - // update hover index - printf("Container Stack: active %x\n", ctx->active_cnt); - ug_vec2_t v = ctx->mouse.pos; - for (int i = 0; i < ctx->cnt_stack.idx; i++) { - ug_container_t *c = &ctx->cnt_stack.items[i]; - - printf("[%d]: %x\n", i, c->id); - if (TEST(c->flags, CNT_STATE_DELETE)) { - DELETE_FROM_STACK(ctx->cnt_stack, c); - // FIXME: this should be fine since all elements in the - // stack are moved back and c now points to the - // container that would be next - } - - ug_rect_t r = c->rca; - EXPAND(r, SZ_INT(ctx->style_px->border.size)); - if (INTERSECTS(v, r)) { - ctx->hover_cnt = c->id; - // update element hover for the hovered container - for (int i = 0; i < c->elem_stack.idx; i++) { - ug_element_t *e = &c->elem_stack.items[i]; - if (INTERSECTS(ctx->mouse.pos, e->rca)) { - c->hover_elem = e->id; - break; - } - } - } - } - printf("\n"); - - if (ctx->last_active_cnt && ctx->active_cnt != ctx->last_active_cnt) - ctx->cnt_stack.sorted = 0; - - // update active container - if (MOUSEDOWN(ctx, UG_BTN_LEFT)) { - ctx->active_cnt = ctx->hover_cnt; - } else if (MOUSEUP(ctx, UG_BTN_LEFT)) { - ctx->last_active_cnt = ctx->active_cnt; - ctx->active_cnt = 0; - } - - return 0; -} - -// FIXME: reoreder code such that this prototype is not needed -void draw_elements(ug_ctx_t *, ug_container_t *); - -// At the end of a frame reset inputs -int ug_frame_end(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - // before drawing floating contaners need to be drawn on top of the others - sort_containers(ctx); - for (int i = 0; i < ctx->cnt_stack.idx; i++) { - ug_container_t *c = &ctx->cnt_stack.items[i]; - // draw containers - draw_container(ctx, c, c->name); - // TODO: add a debug flag - // debug draw space used by elements - push_rect_command(ctx, &c->space, (ug_color_t)RGBA_FORMAT(0x0000abf0)); - // draw elements - draw_elements(ctx, c); - // reset the hover element - c->hover_elem = 0; - // reset the layout to row - c->flags &= ~(CNT_LAYOUT_COLUMN); - // reset used space - c->space = (ug_rect_t){0}; - } - - ctx->input_text[0] = '\0'; - ctx->key.update = 0; - ctx->mouse.hold ^= ctx->mouse.update; - ctx->mouse.update = 0; - ctx->mouse.scroll_delta = (ug_vec2_t){0}; - ctx->mouse.last_pos = ctx->mouse.pos; - - // reset hover, it has to be calculated at frame beginning - // FIXME: reeeeeally? - //ctx->hover_cnt = 0; - - ctx->last_ppi = ctx->ppi; - ctx->last_ppm = ctx->ppm; - ctx->last_ppd = ctx->ppd; - - ctx->frame++; - - // deselect container - ctx->selected_cnt = 0; - - ctx->cmd_it = 0; - - return 0; -} - - -/*=============================================================================* - * Layouting * - *=============================================================================*/ - - -/* - * Container layout: - * A partition in the space taken up by the conglomerate of elements in - * between calls to new_row or new_column (no backing state, just an idea) - * +--------------------------------------------------------+ - * | .<- r_orig | - * | . | - * | previous element . | - * | . | - * |.............................. | - * |^ | - * |c_orig | - * | | - * | | - * | | - * | | - * | | - * +--------------------------------------------------------+ - * - * Element Style: - * - * rca margin - * +-v--------------+<--->+----------------+ - * | +------------+ | | +------------+ | - * | | | | | | | | - * | | | | | | | | - * | | | | | | | | - * | +------------+ | | +------------+ | - * +----------------+ +----------------+ - * <-> - * border -*/ - - -#define GET_SELECTED_CONTAINER(ctx, cp) \ -{ \ - cp = search_container(ctx, ctx->selected_cnt); \ - if (!cp) \ - return -1; \ -} - - -// set the layout to row -int ug_layout_row(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - cp->flags &= ~CNT_LAYOUT_COLUMN; - return 0; -} - - -// set the layout to column -int ug_layout_column(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - cp->flags |= CNT_LAYOUT_COLUMN; - return 0; -} - - -int ug_layout_next_row(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - - cp->c_orig.x = cp->r_orig.x = L_MARGIN(cp->space, 0); - cp->c_orig.y = cp->r_orig.y = B_MARGIN(cp->space, 0); - - return 0; -} - - -int ug_layout_next_column(ug_ctx_t *ctx) -{ - TEST_CTX(ctx); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - - cp->c_orig.x = cp->r_orig.x = R_MARGIN(cp->space, 0); - cp->c_orig.y = cp->r_orig.y = T_MARGIN(cp->space, 0); - - return 0; -} - - -/*=============================================================================* - * Elements * - *=============================================================================*/ - -// search an element by id in the container's element stack and get it's address -static ug_element_t *search_element(ug_container_t *cnt, ug_id_t id) -{ - ug_element_t *e = NULL; - for (int i = 0; i < cnt->elem_stack.idx; i++) { - if (cnt->elem_stack.items[i].id == id) { - e = &(cnt->elem_stack.items[i]); - break; - } - } - return e; -} - - -// get a new or existing container handle -static ug_element_t *get_element(ug_container_t *cnt, ug_id_t id) -{ - ug_element_t *e = search_element(cnt, id); - if (!e) { - GET_FROM_STACK(cnt->elem_stack, e); - cnt->elem_stack.sorted = 0; - } - return e; -} - - -// update the element's position in the container area -static int position_element(ug_ctx_t *ctx, ug_container_t *cnt, ug_element_t *elem) -{ - - // if there is no space then return - if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) { - if (cnt->space.w >= cnt->rca.w) - return -1; - } else { - if (cnt->space.h >= cnt->rca.h) - return -1; - } - - - ug_rect_t *rect, *rca; - rect = &(elem->rect); - rca = &(elem->rca); - scale_rect(ctx, rect); - - *rca = *rect; - - const ug_style_t *s = ctx->style_px; - int b = SZ_INT(s->border.size); - int m = SZ_INT(s->margin); - int cx, cy, cw, ch; - int eb = 0; - - switch (elem->type) { - case UG_ELEM_BUTTON: eb = SZ_INT(s->btn.border); break; - default: break; - } - - // FIXME: this does not work - cw = MAX(cnt->rca.w - cnt->space.w, 0); - ch = MAX(cnt->rca.h - cnt->space.h, 0); - if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) { - cx = cnt->c_orig.x; - cy = cnt->c_orig.y; - } else { - cx = cnt->r_orig.x; - cy = cnt->r_orig.y; - } - - // handle relative sizes - if (rect->w == 0) rca->w = cw - 2*eb; - if (rect->h == 0) rca->h = ch - 2*eb; - // for elements x and y are offsets - rca->x = cx + rect->x + eb + m; - rca->y = cy + rect->y + eb + m; - - // if the element was put outside of the container return, this can happen - // because of the element offset - if (OUTSIDE((*rca), 0, cnt->rca, b)) - return -1; - - elem->flags &= ~(ELEM_CLIPPED); - if (crop_rect(rca, &cnt->rca)) - elem->flags |= ELEM_CLIPPED; - - if (TEST(cnt->flags, CNT_LAYOUT_COLUMN)) { - cnt->c_orig.y = B_MARGIN((*rca), 0) + eb; - cnt->r_orig.x = R_MARGIN((*rca), 0) + eb; - cnt->r_orig.y = T_MARGIN((*rca), 0) + eb; - - } else { - cnt->r_orig.x = R_MARGIN((*rca), 0) + eb; - cnt->c_orig.x = L_MARGIN((*rca), 0) + eb; - cnt->c_orig.y = B_MARGIN((*rca), 0) + eb; - } - - // expand used space - if (R_MARGIN(cnt->space, 0) < R_MARGIN((*rca), -eb)) - cnt->space.w = R_MARGIN((*rca), -eb) - L_MARGIN(cnt->space, 0); - if (B_MARGIN(cnt->space, 0) < B_MARGIN((*rca), -eb)) - cnt->space.h = B_MARGIN((*rca), -eb) - T_MARGIN(cnt->space, 0); - - return 0; -} - - -// TODO: have a map of function pointers for each element type instead of this -// generic function -int handle_element(ug_ctx_t *ctx, ug_container_t *cnt, ug_element_t *elem) -{ - // if the contaniner is not the current hover then do nothing, think about - // floating cont. over bodies - if (ctx->hover_cnt != cnt->id) - return 0; - // if the element is not selected nor hovered then do nothing - if (cnt->hover_elem != elem->id && cnt->selected_elem != elem->id) - return 0; - - if (cnt->hover_elem == elem->id) { - if (MOUSEDOWN(ctx, BTN_ANY)) - cnt->selected_elem = elem->id; - } else { - if (cnt->selected_elem != elem->id) - return 0; - } - // TODO: handle different types of elements - - // TODO: return which button was held - return 1; -} - - -void draw_elements(ug_ctx_t *ctx, ug_container_t *cnt) -{ - const ug_style_t *s = ctx->style_px; - - for (int i = 0; i < cnt->elem_stack.idx; i++) { - ug_element_t *e = &(cnt->elem_stack.items[i]); - ug_color_t col = RGB_FORMAT(0), bcol = RGB_FORMAT(0); - ug_color_t txtcol = RGB_FORMAT(0); - ug_rect_t r = e->rca; - int eb = 0, ts = 0; - unsigned char draw_br = 0, draw_fg = 0, draw_txt = 0; - - switch (e->type) { - case UG_ELEM_BUTTON: - col = cnt->hover_elem == e->id ? s->btn.color.act : s->btn.color.bg; - bcol = cnt->selected_elem == e->id && ctx->hover_cnt == cnt->id ? s->btn.color.sel : s->btn.color.br; - txtcol = s->btn.color.fg; - ts = SZ_INT(s->btn.font_size); - eb = SZ_INT(ctx->style_px->btn.border); - draw_br = draw_fg = draw_txt = 1; - break; - case UG_ELEM_TXTBTN: - ts = SZ_INT(s->btn.font_size); - txtcol = s->color.fg; - draw_txt = 1; - default: break; - } - - if (draw_br) { - r = (ug_rect_t){ - .x = e->rca.x - eb, - .y = e->rca.y - eb, - .w = e->rca.w + 2*eb, - .h = e->rca.h + 2*eb, - }; - if (TEST(e->flags, ELEM_CLIPPED)) - crop_rect(&r, &cnt->rca); - push_rect_command(ctx, &r, bcol); - } - if (draw_fg) { - r = e->rca; - push_rect_command(ctx, &r, col); - } - if (draw_txt) { - push_text_command(ctx, - (ug_vec2_t){ - .x = e->rca.x+eb, - .y = e->rca.y+eb+e->rca.h/2 - }, - ts, - txtcol, - e->btn.txt); - } - } -} - - -int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim) -{ - TEST_CTX(ctx); - TEST_STR(name); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - - ug_id_t id = hash(name, strlen(name)); - ug_element_t *elem = get_element(cp, id); - - // FIXME: we don't always need to do everything - elem->id = id; - elem->type = UG_ELEM_BUTTON; - elem->rect = div_to_rect(ctx, &dim); - elem->name = name; - elem->btn.txt = txt; - - // FIXME: what about error codes? - if (position_element(ctx, cp, elem)) { - DELETE_FROM_STACK(cp->elem_stack, elem); - return -1; - } - - return handle_element(ctx, cp, elem); -} - - -// text-only button -int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim) -{ - TEST_CTX(ctx); - TEST_STR(name); - TEST_STR(txt); - - ug_container_t *cp; - GET_SELECTED_CONTAINER(ctx, cp); - - ug_id_t id = hash(name, strlen(name)); - ug_element_t *elem = get_element(cp, id); - - // FIXME: we don't always need to do everything - elem->id = id; - elem->type = UG_ELEM_TXTBTN; - elem->rect = div_to_rect(ctx, &dim); - elem->name = name; - elem->btn.txt = txt; - - // FIXME: what about error codes? - if (position_element(ctx, cp, elem)) { - DELETE_FROM_STACK(cp->elem_stack, elem); - return -1; - } - - return handle_element(ctx, cp, elem); -} diff --git a/ugui.h b/ugui.h deleted file mode 100644 index bb286a4..0000000 --- a/ugui.h +++ /dev/null @@ -1,285 +0,0 @@ -#ifndef _UG_HEADER -#define _UG_HEADER - -#define UG_STACK(T) struct { T *items; int idx, size, sorted; } -#define BIT(n) (1 << n) -#define RGBA_FORMAT(x) { .a=x&0xff, .b=(x>>8)&0xff, .g=(x>>16)&0xff, .r=(x>>24)&0xff } -#define RGB_FORMAT(x) { .a=0xff, .b=x&0xff, .g=(x>>8)&0xff, .r=(x>>16)&0xff } -#define SIZE_PX(x) { .size.i=x, .unit=UG_UNIT_PX } -#define SIZE_MM(x) { .size.f=x, .unit=UG_UNIT_MM } -#define SIZE_PT(x) { .size.f=x, .unit=UG_UNIT_PT } -#define SQUARE(x) .w = x, .h = x - -// basic types -typedef unsigned int ug_id_t; -typedef struct { union {int x, w;}; union {int y, h;}; } ug_vec2_t; -typedef struct { unsigned char a, b, g, r; } ug_color_t; -typedef struct { int x, y, w, h; } ug_rect_t; -typedef struct { union {int i; float f;} size; int unit; } ug_size_t; -// div has information about the phisical dimension -typedef struct { ug_size_t x, y, w, h;} ug_div_t; - -typedef enum { - UG_UNIT_PX = 0, - UG_UNIT_MM, - UG_UNIT_PT, -} ug_unit_t; - - -// element type -typedef struct { - ug_id_t id; - unsigned short int type; - unsigned short int flags; - ug_rect_t rect, rca; - const char *name; - union { - struct { - const char *txt; - } btn; - }; -} ug_element_t; - -enum { - UG_ELEM_BUTTON, // button - UG_ELEM_TXTBTN, // textual button, a button but without frame - UG_ELEM_CHECK, // checkbox - UG_ELEM_RADIO, // radio button - UG_ELEM_TOGGLE, // toggle button - UG_ELEM_LABEL, // simple text - UG_ELEM_UPDOWN, // text with two buttons up and down - UG_ELEM_TEXTINPUT, // text input box - UG_ELEM_TEXTBOX, // text surrounded by a box - UG_ELEM_IMG, // image, icon - UG_ELEM_SPACE, // takes up space -}; - -enum { - ELEM_CLIPPED = BIT(0), -}; - - -// container type, a container is an entity that contains layouts, a container has -// a haight a width and their maximum values, in essence a container is a rectangular -// area that can be resized and sits somewhere on the drawable region -// the z index of a container is determined by it's position on the stack -typedef struct { - ug_id_t id; - const char *name; - ug_rect_t rect; - // absolute position rect - ug_rect_t rca; - unsigned int flags; - // layouting and elements - // total space used by elements, x and y are the starting coordinates - // for elements - ug_rect_t space; - // origin for in-row and in-column elements - ug_vec2_t c_orig, r_orig; - UG_STACK(ug_element_t) elem_stack; - ug_id_t selected_elem, hover_elem; -} ug_container_t; - -// the container flags -enum { - UG_CNT_FLOATING = BIT(0), // is on top of everything else - UG_CNT_RESIZE_RIGHT = BIT(1), // can be resized from the right border - UG_CNT_RESIZE_BOTTOM = BIT(2), // can be resized from the bottom border - UG_CNT_RESIZE_LEFT = BIT(3), // can be resized from the left border - UG_CNT_RESIZE_TOP = BIT(4), // can be resized from the top border - UG_CNT_SCROLL_X = BIT(5), // can have horizontal scrolling - UG_CNT_SCROLL_Y = BIT(6), // can have vertical scrolling - UG_CNT_MOVABLE = BIT(7), // can be moved around - // container state - CNT_STATE_NONE = BIT(30), - CNT_STATE_MOVING = BIT(29), - CNT_STATE_RESIZE_T = BIT(28), - CNT_STATE_RESIZE_B = BIT(27), - CNT_STATE_RESIZE_L = BIT(26), - CNT_STATE_RESIZE_R = BIT(25), - CNT_STATE_RESIZE_D = BIT(24), - CNT_STATE_DELETE = BIT(23), // The container is marked for removal - // layouting - CNT_LAYOUT_COLUMN = BIT(22), -}; - -// style, defines default height, width, color, margins, borders, etc -// all dimensions should be taken as a reference when doing the layout since -// ultimately it's the layout that decides them. For example when deciding how to -// allocate space one can say that the default size of a region that allocates a -// slider has the style's default dimensions for a slider -typedef struct { - struct { ug_color_t bg, fg; } color; - ug_size_t margin; - struct { - ug_color_t color; - ug_size_t size; - } border; - struct { - struct { ug_color_t bg, fg; } color; - ug_size_t height, font_size; - } title; - struct { - struct { ug_color_t act, bg, fg, sel, br; } color; - ug_size_t font_size, border; - } btn; -} ug_style_t; - - -// render commands -struct ug_cmd_rect { int x, y, w, h; ug_color_t color; }; -struct ug_cmd_text { int x, y, size; ug_color_t color; const char *str; }; - -typedef struct { - unsigned int type; - union { - struct ug_cmd_rect rect; - struct ug_cmd_text text; - }; -} ug_cmd_t; - -typedef enum { - UG_CMD_NULL = 0, - UG_CMD_RECT, - UG_CMD_TEXT, -} ug_cmd_type_t; - -// window side -enum { - UG_SIDE_TOP = 0, - UG_SIDE_BOTTOM, - UG_SIDE_LEFT, - UG_SIDE_RIGHT, -}; - -// mouse buttons -enum { - UG_BTN_LEFT = BIT(0), - UG_BTN_MIDDLE = BIT(1), - UG_BTN_RIGHT = BIT(2), - UG_BTN_4 = BIT(3), - UG_BTN_5 = BIT(4), -}; - -// context -typedef struct { - // some style information - const ug_style_t *style; - // style_px is a style cache where all measurements are already in pixels - const ug_style_t *style_px; - // ppi: pixels per inch - // ppm: pixels per millimeter - // ppd: pixels per dot - float ppi, ppm, ppd; - float last_ppi, last_ppm, last_ppd; - // containers need to know how big the "main container" is so that all - // the relative positioning work - ug_vec2_t size; - ug_rect_t origin; - // which context and element we are hovering - ug_id_t hover_cnt; - // active is updated on mousedown and released on mouseup - // the id of the "active" element, active means different things for - // different elements, for exaple active for a button means to be pressed, - // and for a text box it means to be focused - ug_id_t active_cnt, last_active_cnt; - // id of the selected container, used for layout - // NOTE: since the stacks can be relocated with realloc it is better not - // to use a pointer here, even tough it would be better for efficiency - ug_id_t selected_cnt; - // count the frames for fun - unsigned long int frame; - // mouse data - struct { - ug_vec2_t pos; - ug_vec2_t last_pos; - ug_vec2_t delta; - ug_vec2_t scroll_delta; - // mouse.update: a mask of the mouse buttons that are being updated - unsigned char update; - // mouse.hold: a mask of the buttons that are being held - unsigned char hold; - } mouse; - // keyboard key pressed - struct { - unsigned char update; - unsigned char hold; - } key; - // input text buffer - char input_text[32]; - // stacks - UG_STACK(ug_container_t) cnt_stack; - UG_STACK(ug_cmd_t) cmd_stack; - // command stack iterator - int cmd_it; -} ug_ctx_t; - - -// context initialization -ug_ctx_t * ug_ctx_new(void); -void ug_ctx_free(ug_ctx_t *ctx); -// updates the context with user information -// ppi: pixels per inch, the scale used for all em/mm to pixel calculations -// scale: it is the scale between the physical pixels on the screen and the pixels -// on the buffer, if unsure set to 1.0 -int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi); -int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size); -int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style); - - -// define containers, name is used as a salt for the container id, all sizes are -// the default ones, resizing is done automagically with internal state - -// a floating container can be placed anywhere and can be resized, acts like a -// window inside another window -int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div); -// like a floating container but cannot be resized -int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div); -// a menu bar is a container of fixed height, cannot be resized and sits at the -// top of the window -int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height); -// a sidebar is a variable size container anchored to one side of the window -int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side); -// a body is a container that scales with the window, sits at it's center and cannot -// be resized, it also fills all the available space -int ug_container_body(ug_ctx_t *ctx, const char *name); -// mark a conatiner for removal, it will be freed at the next frame beginning if -// name is NULL then use the selected container -int ug_container_remove(ug_ctx_t *ctx, const char *name); -// get the drawable area of the container, if name is NULL then use the selected -// container -ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name); - -// layouting, the following functions define how different ui elements are placed -// inside the selected container. A new element is aligned respective to the -// previous element and/or to the container, particularly elements can be placed -// in a row or a column. -int ug_layout_row(ug_ctx_t *ctx); -int ug_layout_column(ug_ctx_t *ctx); -int ug_layout_next_row(ug_ctx_t *ctx); -int ug_layout_next_column(ug_ctx_t *ctx); - -// elements -int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim); -int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim); - -// Input functions -int ug_input_mousemove(ug_ctx_t *ctx, int x, int y); -int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask); -int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask); -int ug_input_scroll(ug_ctx_t *ctx, int x, int y); -// TODO: other input functions - -// Frame handling -int ug_frame_begin(ug_ctx_t *ctx); -int ug_frame_end(ug_ctx_t *ctx); - -// Commands -// get the next command, save iteration state inside 'iterator' -ug_cmd_t * ug_cmd_next(ug_ctx_t *ctx); - - -#undef UG_STACK -#undef BIT - -#endif From 4aefe8b42dfc53f179f9e233ba9ce10ed6781f84 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 27 Dec 2023 17:36:30 +0100 Subject: [PATCH 002/250] tree in a vector --- vectree | Bin 0 -> 20752 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100755 vectree diff --git a/vectree b/vectree new file mode 100755 index 0000000000000000000000000000000000000000..154e744d2bdb3ed04eed55376bd986de24ec5328 GIT binary patch literal 20752 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 Date: Fri, 29 Dec 2023 12:44:53 +0100 Subject: [PATCH 003/250] things --- .clang-format | 41 +++ .gitignore | 2 + ARCHITECTURE.md | 108 +++++++ Makefile | 14 + cache.c | 219 ++++++++++++++ compile_flags.txt | 5 + generic_hash.h => stuff/generic_hash.h | 0 generic_stack.h => stuff/generic_stack.h | 0 main.c => stuff/main.c | 0 stuff/vectree.h | 348 +++++++++++++++++++++++ ugui.c | 61 ++++ ugui.h | 63 ++++ vectree | Bin 20752 -> 0 bytes vectree.c | 335 ++++++++++++++++++++++ 14 files changed, 1196 insertions(+) create mode 100644 .clang-format create mode 100644 ARCHITECTURE.md create mode 100644 Makefile create mode 100644 cache.c create mode 100644 compile_flags.txt rename generic_hash.h => stuff/generic_hash.h (100%) rename generic_stack.h => stuff/generic_stack.h (100%) rename main.c => stuff/main.c (100%) create mode 100644 stuff/vectree.h create mode 100644 ugui.c create mode 100644 ugui.h delete mode 100755 vectree create mode 100644 vectree.c diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..b6d0dcb --- /dev/null +++ b/.clang-format @@ -0,0 +1,41 @@ +# linux kernel style formatting +BasedOnStyle: LLVM +IndentWidth: 8 +UseTab: AlignWithSpaces + +BreakBeforeBraces: Linux +AllowShortIfStatementsOnASingleLine: false +IndentCaseLabels: false +ColumnLimit: 85 + +InsertBraces: true +SortIncludes: Never +BinPackParameters: false +BinPackArguments: false +Cpp11BracedListStyle: true +SpaceBeforeCpp11BracedList: true +SeparateDefinitionBlocks: Always +AlignAfterOpenBracket: BlockIndent +InsertNewlineAtEOF: true + +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: true + PadOperators: false + +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + +AlignConsecutiveBitFields: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true + +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: true diff --git a/.gitignore b/.gitignore index 6320a1c..15fae14 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ microgui +ugui *.o test/test **/compile_commands.json **/.cache test +raylib/* diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..4ecdfd0 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,108 @@ +## High level overview + +Under the hood every element has an id, this id allows the library to store state +between frames. +Elements are also cached such that when the ui tree is rebuilt at the beginning of +every frame the element data structure doesn't have to be rebuilt. + +Elements are arranged in a tree, nodes are container elements that can contain other +elements, leafs are elements that cannot contain other elements. + +Every element has a size and a position, containers also have to keep track of their +layout information and some other state. + +Elements can push commands into the draw stack, which is a structure that contains +all the draw commands that the user application has to perform do display the ui +correctly, such commands include drawing lines, rectangles, sprites, text, etc. + + +```text + +-----------+ + | ug_init() | + +-----+-----+ + | + | + | + +---------v----------+ + |ug_input_keyboard() | + |ug_input_mouse() <----+ + |ug_input_clipboard()| | + | ... | | + +---------+----------+ | + | | + | | + +-------v--------+ | + |ug_frame_begin()| | + +-------+--------+ | + | | + | | + +---------v----------+ | + |ug_window_start() | | + +---->ug_container_start()| | + | |ug_div_start() | | + | | ... | | + | +---------+----------+ | + | | | + | | | +multiple +--------v---------+ | + times |ug_layout_row() | | + | |ug_layout_column()| | + | |ug_layout_float() | | + | | ... | | + | +--------+---------+ | + | | | + | | | + | +------v------+ | + | |ug_button() | | + | |ug_text_box()| | + | |ug_slider() | | + | | ... | | + | +------+------+ | + | | | + +--------------+ | + | | + +--------v---------+ | + |ug_window_end() | | + |ug_container_end()| | + |ug_div_end() | | + | ... | | + +--------+---------+ | + | | + | | + | | + +------v-------+ | + |ug_frame_end()| | + +------+-------+ | + | | + | | + | | + +------v-------+ | + |user draws the| | + | ui +-------+ + +------+-------+ + | + |quit + | + +------v-------+ + | ug_destroy() | + +--------------+ +``` + +### Notes + +How elements determine if they have focus or not + +```C +// in begin_{container} code +calculate focus +set has_focus property + + + +// in the element code +if(PARENT_HAS_FOCUS()) { + update stuff +} else { + fast path to return +} +``` diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8f92d90 --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/src +CC = gcc +LDFLAGS = -Lraylib/src -lm + +all: ugui + +raylib/src/libraylib.a: raylib/src/Makefile + cd raylib/src; $(MAKE) PLATFORM=PLATFORM_DESKTOP + +ugui: ugui.o vectree.o raylib/src/libraylib.a + +ugui.o: ugui.c ugui.h + +vectree.o: vectree.c ugui.h diff --git a/cache.c b/cache.c new file mode 100644 index 0000000..593bebc --- /dev/null +++ b/cache.c @@ -0,0 +1,219 @@ +// LRU cache: +/* + * The cache uses a pool (array) containing all the elements and a hash table + * associating the position in the pool with the element id + */ + +#include +#include + +#include "ugui.h" + +// To have less collisions TABLE_SIZE has to be larger than the cache size +#define CACHE_SIZE 265 +#define TABLE_SIZE (CACHE_SIZE * 1.5f) +#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3) +#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6) +#define CACHE_BRESET(b) \ + for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \ + ; +#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63) +#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63))) + +/* Hash Table Implementation ----------------------------------------------------- */ + +#define HASH_MAXSIZE 4096 + +// hash table (id -> index) +typedef struct { + uint32_t id; + uint32_t index; +} IdElem; + +typedef struct { + uint32_t items, size, exp; + IdElem bucket[]; +} IdTable; + +IdTable *table_create(uint32_t size) +{ + if (!size || size > HASH_MAXSIZE) { + return NULL; + } + /* round to the greater power of two */ + /* FIXME: check for intger overflow here */ + uint32_t exp = 32 - __builtin_clz(size - 1); + size = 1 << (exp); + /* FIXME: check for intger overflow here */ + IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size); + if (ht) { + ht->items = 0; + ht->size = size; + memset(ht->bucket, 0, sizeof(IdTable) * size); + } + return ht; +} + +void table_destroy(IdTable *ht) +{ + if (ht) { + free(ht); + } +} + +// Find and return the element by pointer +IdElem *table_search(IdTable *ht, uint32_t id) +{ + if (!ht) { + return NULL; + } + + // In this case id is the hash + uint32_t idx = id % ht->size; + + for (uint32_t x = 0, i; x < ht->size; x++) { + i = (idx + x) % ht->size; + if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) { + return &(ht->bucket[i]); + } + } + return NULL; +} + +// FIXME: this simply overrides the found item +IdElem *table_insert(IdTable *ht, IdElem entry) +{ + IdElem *r = table_search(ht, entry.id); + if (r != NULL) { + if (r->id != 0) { + ht->items++; + } + *r = entry; + } + return r; +} + +IdElem *table_remove(IdTable *ht, uint32_t id) +{ + if (!ht) { + return NULL; + } + IdElem *r = table_search(ht, id); + if (r) { + r->id = 0; + } + return r; +} + +/* Cache Implementation ---------------------------------------------------------- */ + +// Every CACHE_CYCLES operations mark not-present the unused elements +#define CACHE_CYCLE(c) \ + do { \ + if (++(c->cycles) > CACHE_NCYCLES) { \ + for (int i = 0; i < CACHE_BSIZE; i++) { \ + c->present[i] &= c->used[i]; \ + c->used[i] = 0; \ + } \ + c->cycles = 0; \ + } \ + } while (0) + +typedef struct { + IdTable *table; + UgElem *array; + uint64_t *present, *used; + int cycles; +} UgElemCache; + +/* FIXME: check for allocation errors */ +UgElemCache ug_cache_init(void) +{ + IdTable *t = table_create(TABLE_SIZE); + UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE); + uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE); + uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE); + CACHE_BRESET(p); + CACHE_BRESET(u); + return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0}; +} + +void ug_cache_free(UgElemCache *cache) +{ + if (cache) { + table_destroy(cache->table); + free(cache->array); + free(cache->present); + free(cache->used); + } +} + +const UgElem *ug_cache_search(UgElemCache *cache, uint32_t id) +{ + if (!cache) { + return NULL; + } + IdElem *r; + r = table_search(cache->table, id); + /* MISS */ + if (!r || id != r->id) { + return NULL; + } + /* MISS, the data is not valid (not present) */ + if (!CACHE_BTEST(cache->present, r->index)) { + return NULL; + } + /* HIT, set as recently used */ + CACHE_BSET(cache->used, r->index); + return (&cache->array[r->index]); +} + +/* Look for a free spot in the present bitmap and return its index */ +/* If there is no free space left then just return the first position */ +int ug_cache_get_free_spot(UgElemCache *cache) +{ + if (!cache) { + return -1; + } + int x = 0; + for (int b = 0; b < CACHE_BSIZE; b++) { + if (cache->present[b] == 0) { + x = 64; + } else { + x = __builtin_clzll(cache->present[b]); + } + x = 64 - x; + if (!CACHE_BTEST(cache->present, x + 64 * b)) { + return x + 64 * b; + } + } + return 0; +} + +const UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) +{ + if (!cache) { + return NULL; + } + UgElem *spot = NULL; + + /* Set used and present */ + CACHE_BSET(cache->present, index); + CACHE_BSET(cache->used, index); + CACHE_CYCLE(cache); + spot = &(cache->array[index]); + *spot = *g; + IdElem e = {.id = g->id, .index = index}; + if (!table_insert(cache->table, e)) { + return NULL; + } + return spot; +} + +// Insert an element in the cache +const UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, int32_t *index) +{ + *index = ug_cache_get_free_spot(cache); + return ug_cache_insert_at(cache, g, *index); +} + diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..13a23a6 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,5 @@ +-Iraylib/src +-Wall +-Wextra +-pedantic +-std=c11 diff --git a/generic_hash.h b/stuff/generic_hash.h similarity index 100% rename from generic_hash.h rename to stuff/generic_hash.h diff --git a/generic_stack.h b/stuff/generic_stack.h similarity index 100% rename from generic_stack.h rename to stuff/generic_stack.h diff --git a/main.c b/stuff/main.c similarity index 100% rename from main.c rename to stuff/main.c diff --git a/stuff/vectree.h b/stuff/vectree.h new file mode 100644 index 0000000..949bee1 --- /dev/null +++ b/stuff/vectree.h @@ -0,0 +1,348 @@ +#ifndef _VECTREE_H +#define _VECTREE_H + +#ifdef VTREE_DTYPE + +typedef struct { + int size, elements; + VTREE_DTYPE *vector; + int *refs; +} Vtree; + +int vtree_init(Vtree *tree, unsigned int size); +int vtree_pack(Vtree *tree); +int vtree_resize(Vtree *tree, unsigned int newsize); +int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent); +int vtree_prune(Vtree *tree, int ref); +int vtree_subtree_size(Vtree *tree, int ref); +int vtree_children_it(Vtree *tree, int parent, int *cursor); +int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor); +int vtree_destroy(Vtree *tree); + +#ifdef VTREE_IMPL + +#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) +#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) + +int vtree_init(Vtree *tree, unsigned int size) +{ + if (tree == NULL) { + return -1; + } + + tree->vector = malloc(sizeof(VTREE_DTYPE) * size); + if (tree->vector == NULL) { + return -1; + } + + tree->refs = malloc(sizeof(int) * size); + if (tree->refs == NULL) { + free(tree->vector); + return -1; + } + + // set all refs to -1, meaning invalid (free) element + for (unsigned int i = 0; i < size; i++) { + tree->refs[i] = -1; + } + + // fill vector with zeroes + memset(tree->vector, 0, size * sizeof(VTREE_DTYPE)); + + tree->size = size; + tree->elements = 0; + + return 0; +} + +int vtree_destroy(Vtree *tree) +{ + if (tree == NULL) { + return -1; + } + + free(tree->vector); + free(tree->refs); + + return 0; +} + +int vtree_pack(Vtree *tree) +{ + if (tree == NULL) { + return -1; + } + + // TODO: add a PACKED flag to skip this + + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + continue; + } + + // find a item that can be packed + if (free_spot >= 0 && tree->refs[i] >= 0) { + int old_ref = i; + + // move the item + tree->vector[free_spot] = tree->vector[i]; + tree->refs[free_spot] = tree->refs[i]; + + tree->vector[i] = (VTREE_DTYPE){0}; + tree->refs[i] = -1; + + // and move all references + for (int x = 0; x < tree->size; x++) { + if (tree->refs[x] == old_ref) { + tree->refs[x] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } + + return 0; +} + +int vtree_resize(Vtree *tree, unsigned int newsize) +{ + if (tree == NULL) { + return -1; + } + + // return error when shrinking with too many elements + if ((int)newsize < tree->elements) { + return -1; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree->size) { + //if (vtree_pack(tree) < 0) { + // return -1; + //} + // TODO: allow shrinking, since packing destroys all references + return -1; + } + + VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE)); + if (newvec == NULL) { + return -1; + } + + int *newrefs = realloc(tree->refs, newsize * sizeof(int)); + if (newrefs == NULL) { + return -1; + } + + tree->vector = newvec; + tree->refs = newrefs; + if ((int)newsize > tree->size) { + for (int i = tree->size; i < (int)newsize; i++) { + tree->vector[i] = (VTREE_DTYPE){0}; + tree->refs[i] = -1; + } + } + + tree->size = newsize; + + return 0; +} + +// add an element to the tree, return it's ref +int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent) +{ + if (tree == NULL) { + return -1; + } + + // invalid parent + if (!IS_VALID_REF(tree, parent)) { + return -1; + } + + // no space left + if (tree->elements >= tree->size) { + return -1; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { + return -1; + } + + // get the first free spot + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return -1; + } + + // finally add the element + tree->vector[free_spot] = elem; + tree->refs[free_spot] = parent; + tree->elements++; + + return free_spot; +} + +// prune the tree starting from the ref +// returns the number of pruned elements +int vtree_prune(Vtree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + tree->vector[ref] = (VTREE_DTYPE){0}; + tree->refs[ref] = -1; + + int count = 1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + count += vtree_prune(tree, i); + } + } + + return count; +} + +// find the size of the subtree starting from ref +int vtree_subtree_size(Vtree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + int count = 1; + for (int i = 0; i < tree->size; i++) { + // only root has the reference to itself + if (tree->refs[i] == ref && ref != i) { + count += vtree_subtree_size(tree, i); + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +int vtree_children_it(Vtree *tree, int parent, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + // if the cursor is out of bounds then we are done for sure + if (!IS_VALID_REF(tree, *cursor)) { + return -1; + } + + // same for the parent, if it's invalid it can't have children + if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { + return -1; + } + + // find the first child, update the cursor and return the ref + for (int i = *cursor; i < tree->size; i++) { + if (tree->refs[i] == parent) { + *cursor = i + 1; + return i; + } + } + + // if no children are found return -1 + *cursor = -1; + return -1; +} + +/* iterates trough every leaf of the subtree in the following manner + * node [x], x: visit order + * [0] + * / | \ + * / [2] [3] + * [1] | + * / \ [6] + * [4] [5] + */ +int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor) +{ + if (tree == NULL || queue_p == NULL || cursor == NULL) { + return -1; + } + + int *queue = *queue_p; + + // TODO: this could also be done when adding or removing elements + // first call, create a ref array ordered like we desire + if (queue == NULL) { + *cursor = 0; + // create a queue of invalid refs, size is the worst case + queue = malloc(sizeof(int) * tree->size); + if (queue == NULL) { + return -1; + } + for (int i = 0; i < tree->size; i++) { + queue[i] = -1; + } + *queue_p = queue; + + // iterate through the queue appending found children + int pos = 0, off = 0; + do { + //printf ("ref=%d\n", ref); + + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + queue[pos++] = i; + } + } + + for (;ref == queue[off] && off < tree->size; off++); + ref = queue[off]; + + } while (IS_VALID_REF(tree, ref)); + } + + //PRINT_ARR(queue, tree->size); + //return -1; + + // on successive calls just iterate through the queue until we find an + // invalid ref + int ret = queue[(*cursor)++]; + if (!IS_VALID_REF(tree, ret)) { + free(queue); + } + + return ret; +} +#endif // VTREE_IMPL + +#endif // VTREE_DTYPE + +#endif + diff --git a/ugui.c b/ugui.c new file mode 100644 index 0000000..a22e092 --- /dev/null +++ b/ugui.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include + +#include "raylib.h" +#include "ugui.h" + +int main(void) +{ + UgCtx ctx; + ug_init(&ctx); + + InitWindow(800, 450, "Ugui Test"); + + // Main loop + while (!WindowShouldClose()) { + ug_begin_frame(&ctx); + + // drawing + BeginDrawing(); + ClearBackground(BLACK); + DrawText( + "Congrats! You created your first window!", + 190, + 200, + 20, + LIGHTGRAY + ); + EndDrawing(); + + ug_end_frame(&ctx); + } + + CloseWindow(); + + ug_destroy(&ctx); + return 0; +} + +int ug_init(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + ug_tree_init(&ctx->tree, 10); + + return 0; +} + +int ug_destroy(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + ug_tree_destroy(&ctx->tree); + + return 0; +} diff --git a/ugui.h b/ugui.h new file mode 100644 index 0000000..08dabb6 --- /dev/null +++ b/ugui.h @@ -0,0 +1,63 @@ +#ifndef _UGUI_H +#define _UGUI_H + +#include + +typedef struct { + int32_t x, y, w, h; +} UgRect; + +typedef struct { + int32_t x, y; +} UgPoint; + +typedef struct { + uint8_t r, g, b, a; +} UgColor; + +typedef enum { + ETYPE_NONE = 0, + ETYPE_BUTTON, + ETYPE_TEXT, + ETYPE_SCROLL, + ETYPE_SLIDER, +} UgElemType; + +typedef struct { + uint64_t id; + UgRect rec; + union { + uint32_t type_int; + UgElemType type; + }; +} UgElem; + +// TODO: add a packed flag +// TODO: add a fill index to skip some searching for free spots + +typedef struct { + int size, elements; + uint32_t *vector; // vector of element ids + int *refs, *ordered_refs; +} UgTree; + +typedef struct { + UgTree tree; +} UgCtx; + +// tree implementation +int ug_tree_init(UgTree *tree, unsigned int size); +int ug_tree_pack(UgTree *tree); +int ug_tree_resize(UgTree *tree, unsigned int newsize); +int ug_tree_add(UgTree *tree, uint32_t elem, int parent); +int ug_tree_prune(UgTree *tree, int ref); +int ug_tree_subtree_size(UgTree *tree, int ref); +int ug_tree_children_it(UgTree *tree, int parent, int *cursor); +int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); +int ug_tree_destroy(UgTree *tree); + +int ug_init(UgCtx *ctx); +int ug_destroy(UgCtx *ctx); + +#endif // _UGUI_H + diff --git a/vectree b/vectree deleted file mode 100755 index 154e744d2bdb3ed04eed55376bd986de24ec5328..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20752 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 +#include + +#include "ugui.h" + +#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) +#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) + +int ug_tree_init(UgTree *tree, unsigned int size) +{ + if (tree == NULL) { + return -1; + } + + tree->vector = malloc(sizeof(UgElem) * size); + if (tree->vector == NULL) { + return -1; + } + + tree->refs = malloc(sizeof(int) * size); + if (tree->refs == NULL) { + free(tree->vector); + return -1; + } + + // ordered refs are used in the iterators + tree->ordered_refs = malloc(sizeof(int) * size); + if (tree->ordered_refs == NULL) { + free(tree->vector); + free(tree->refs); + return -1; + } + + // set all refs to -1, meaning invalid (free) element + for (unsigned int i = 0; i < size; i++) { + tree->refs[i] = -1; + } + + // fill vector with zeroes + memset(tree->vector, 0, size * sizeof(UgElem)); + + tree->size = size; + tree->elements = 0; + + return 0; +} + +int ug_tree_destroy(UgTree *tree) +{ + if (tree == NULL) { + return -1; + } + + free(tree->vector); + free(tree->refs); + + return 0; +} + +int ug_tree_pack(UgTree *tree) +{ + if (tree == NULL) { + return -1; + } + + // TODO: add a PACKED flag to skip this + + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + continue; + } + + // find a item that can be packed + if (free_spot >= 0 && tree->refs[i] >= 0) { + int old_ref = i; + + // move the item + tree->vector[free_spot] = tree->vector[i]; + tree->refs[free_spot] = tree->refs[i]; + + tree->vector[i] = 0; + tree->refs[i] = -1; + + // and move all references + for (int x = 0; x < tree->size; x++) { + if (tree->refs[x] == old_ref) { + tree->refs[x] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } + + return 0; +} + +int ug_tree_resize(UgTree *tree, unsigned int newsize) +{ + if (tree == NULL) { + return -1; + } + + // return error when shrinking with too many elements + if ((int)newsize < tree->elements) { + return -1; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree->size) { + // if (ug_tree_pack(tree) < 0) { + // return -1; + // } + // TODO: allow shrinking, since packing destroys all references + return -1; + } + + uint32_t *newvec = realloc(tree->vector, newsize * sizeof(uint32_t)); + if (newvec == NULL) { + return -1; + } + + int *newrefs = realloc(tree->refs, newsize * sizeof(int)); + if (newrefs == NULL) { + return -1; + } + + int *neworrefs = realloc(tree->ordered_refs, newsize * sizeof(int)); + if (neworrefs == NULL) { + return -1; + } + + tree->vector = newvec; + tree->refs = newrefs; + tree->ordered_refs = neworrefs; + if ((int)newsize > tree->size) { + for (int i = tree->size; i < (int)newsize; i++) { + tree->vector[i] = 0; + tree->refs[i] = -1; + } + } + + tree->size = newsize; + + return 0; +} + +// add an element to the tree, return it's ref +int ug_tree_add(UgTree *tree, uint32_t elem, int parent) +{ + if (tree == NULL) { + return -1; + } + + // invalid parent + if (!IS_VALID_REF(tree, parent)) { + return -1; + } + + // no space left + if (tree->elements >= tree->size) { + return -1; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { + return -1; + } + + // get the first free spot + int free_spot = -1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return -1; + } + + // finally add the element + tree->vector[free_spot] = elem; + tree->refs[free_spot] = parent; + tree->elements++; + + return free_spot; +} + +// prune the tree starting from the ref +// returns the number of pruned elements +int ug_tree_prune(UgTree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + tree->vector[ref] = 0; + tree->refs[ref] = -1; + + int count = 1; + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + count += ug_tree_prune(tree, i); + } + } + + return count; +} + +// find the size of the subtree starting from ref +int ug_tree_subtree_size(UgTree *tree, int ref) +{ + if (tree == NULL) { + return -1; + } + + if (!IS_VALID_REF(tree, ref)) { + return -1; + } + + if (!REF_IS_PRESENT(tree, ref)) { + return 0; + } + + int count = 1; + for (int i = 0; i < tree->size; i++) { + // only root has the reference to itself + if (tree->refs[i] == ref && ref != i) { + count += ug_tree_subtree_size(tree, i); + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +int ug_tree_children_it(UgTree *tree, int parent, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + // if the cursor is out of bounds then we are done for sure + if (!IS_VALID_REF(tree, *cursor)) { + return -1; + } + + // same for the parent, if it's invalid it can't have children + if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { + return -1; + } + + // find the first child, update the cursor and return the ref + for (int i = *cursor; i < tree->size; i++) { + if (tree->refs[i] == parent) { + *cursor = i + 1; + return i; + } + } + + // if no children are found return -1 + *cursor = -1; + return -1; +} + +/* iterates trough every leaf of the subtree in the following manner + * node [x], x: visit order + * [0] + * / | \ + * / [2] [3] + * [1] | + * / \ [6] + * [4] [5] + */ +int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) +{ + if (tree == NULL || cursor == NULL) { + return -1; + } + + int *queue = tree->ordered_refs; + + // TODO: this could also be done when adding or removing elements + // first call, create a ref array ordered like we desire + if (queue == NULL) { + *cursor = 0; + for (int i = 0; i < tree->size; i++) { + queue[i] = -1; + } + + // iterate through the queue appending found children + int pos = 0, off = 0; + do { + // printf ("ref=%d\n", ref); + + for (int i = 0; i < tree->size; i++) { + if (tree->refs[i] == ref) { + queue[pos++] = i; + } + } + + for (; ref == queue[off] && off < tree->size; off++) + ; + ref = queue[off]; + + } while (IS_VALID_REF(tree, ref)); + } + + // PRINT_ARR(queue, tree->size); + // return -1; + + // on successive calls just iterate through the queue until we find an + // invalid ref, if the user set the cursor to -1 it means it has found what + // he needed, so free + if (*cursor < 0) { + return -1; + } else if (IS_VALID_REF(tree, *cursor)) { + return queue[(*cursor)++]; + } + + return -1; +} From 156c3b3959a04d2e16ef46bcd62df903ad3dee0c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 1 Jan 2024 20:43:19 +0100 Subject: [PATCH 004/250] less than bare minimum --- Makefile | 4 +- cache.c | 23 ++--- ugui.c | 287 +++++++++++++++++++++++++++++++++++++++++++++++++++--- ugui.h | 34 +++++-- vectree.c | 17 +++- 5 files changed, 324 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 8f92d90..7eb7617 100644 --- a/Makefile +++ b/Makefile @@ -7,8 +7,10 @@ all: ugui raylib/src/libraylib.a: raylib/src/Makefile cd raylib/src; $(MAKE) PLATFORM=PLATFORM_DESKTOP -ugui: ugui.o vectree.o raylib/src/libraylib.a +ugui: ugui.o vectree.o cache.o raylib/src/libraylib.a ugui.o: ugui.c ugui.h vectree.o: vectree.c ugui.h + +cache.o: cache.c ugui.h diff --git a/cache.c b/cache.c index 593bebc..1ed0d35 100644 --- a/cache.c +++ b/cache.c @@ -24,13 +24,13 @@ #define HASH_MAXSIZE 4096 -// hash table (id -> index) +// hash table (id -> cache index) typedef struct { - uint32_t id; + UgId id; uint32_t index; } IdElem; -typedef struct { +typedef struct _IdTable { uint32_t items, size, exp; IdElem bucket[]; } IdTable; @@ -62,7 +62,7 @@ void table_destroy(IdTable *ht) } // Find and return the element by pointer -IdElem *table_search(IdTable *ht, uint32_t id) +IdElem *table_search(IdTable *ht, UgId id) { if (!ht) { return NULL; @@ -93,7 +93,7 @@ IdElem *table_insert(IdTable *ht, IdElem entry) return r; } -IdElem *table_remove(IdTable *ht, uint32_t id) +IdElem *table_remove(IdTable *ht, UgId id) { if (!ht) { return NULL; @@ -119,13 +119,6 @@ IdElem *table_remove(IdTable *ht, uint32_t id) } \ } while (0) -typedef struct { - IdTable *table; - UgElem *array; - uint64_t *present, *used; - int cycles; -} UgElemCache; - /* FIXME: check for allocation errors */ UgElemCache ug_cache_init(void) { @@ -148,7 +141,7 @@ void ug_cache_free(UgElemCache *cache) } } -const UgElem *ug_cache_search(UgElemCache *cache, uint32_t id) +UgElem *ug_cache_search(UgElemCache *cache, UgId id) { if (!cache) { return NULL; @@ -190,7 +183,7 @@ int ug_cache_get_free_spot(UgElemCache *cache) return 0; } -const UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) +UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) { if (!cache) { return NULL; @@ -211,7 +204,7 @@ const UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t i } // Insert an element in the cache -const UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, int32_t *index) +UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index) { *index = ug_cache_get_free_spot(cache); return ug_cache_insert_at(cache, g, *index); diff --git a/ugui.c b/ugui.c index a22e092..8ee7c41 100644 --- a/ugui.c +++ b/ugui.c @@ -1,3 +1,5 @@ +#define _DEFAULT_SOURCE + #include #include #include @@ -6,30 +8,185 @@ #include "raylib.h" #include "ugui.h" +typedef struct _UgCmd { + enum { + CMD_RECT = 0, + } type; + + union { + struct { + int32_t x, y, w, h; + uint8_t color[4]; // rgba, [0] = r + } rect; + }; +} UgCmd; + +typedef struct _UgStack { + int size, count; + UgCmd *stack; +} UgStack; + +int ug_stack_init(UgStack *stack, uint32_t size); +int ug_stack_free(UgStack *stack); +int ug_stack_push(UgStack *stack, UgCmd *cmd); +int ug_stack_pop(UgStack *stack, UgCmd *cmd); + +int ug_stack_init(UgStack *stack, uint32_t size) +{ + if (stack == NULL) { + return -1; + } + + stack->size = size; + stack->count = 0; + stack->stack = calloc(size, sizeof(UgCmd)); + + if (stack->stack == NULL) { + return -1; + } + + return 0; +} + +int ug_stack_free(UgStack *stack) +{ + if (stack != NULL && stack->stack != NULL) { + free(stack->stack); + stack->size = 0; + stack->count = 0; + } + return 0; +} + +#define STACK_STEP 10 + +int ug_stack_push(UgStack *stack, UgCmd *cmd) +{ + if (stack == NULL || cmd == NULL) { + return -1; + } + + if (stack->count >= stack->size) { + // UgCmd *tmp = reallocarray(stack->stack, stack->size + + // STACK_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; + // } + // stack->stack = tmp; + // stack->size += STACK_STEP; + return -1; + } + + stack->stack[stack->count++] = *cmd; + + return 0; +} + +int ug_stack_pop(UgStack *stack, UgCmd *cmd) +{ + if (stack == NULL || cmd == NULL) { + return -1; + } + + if (stack->count <= 0) { + // UgCmd *tmp = reallocarray(stack->stack, stack->size + + // STACK_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; + // } + // stack->stack = tmp; + // stack->size += STACK_STEP; + return -1; + } + + *cmd = stack->stack[--stack->count]; + + return 0; +} + +struct _UgCtx { + enum { + row = 0, + column, + floating, + } layout; + + UgTree tree; + UgElemCache cache; + UgStack stack; + + struct { + int width, height; + } size; + + int div_using; // tree node indicating the current active div +}; + +int ug_div_begin(UgCtx *ctx, const char *name, UgRect div); +int ug_div_end(UgCtx *ctx); +int ug_input_window_size(UgCtx *ctx, int width, int height); +UgId djb2(const char *str); + int main(void) { UgCtx ctx; ug_init(&ctx); - InitWindow(800, 450, "Ugui Test"); + int width = 800, height = 450; + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(width, height, "Ugui Test"); + ug_input_window_size(&ctx, width, height); // Main loop while (!WindowShouldClose()) { - ug_begin_frame(&ctx); + + // UI handling + ug_frame_begin(&ctx); + + if (IsWindowResized()) { + width = GetScreenWidth(); + height = GetScreenHeight(); + ug_input_window_size(&ctx, width, height); + } + +// main div, fill the whole window +#define DIV_FILL (UgRect) {.x = 0, .y = 0, .w = -1, .h = -1} + ug_div_begin(&ctx, "main", DIV_FILL); + ug_div_end(&ctx); + + ug_frame_end(&ctx); // drawing BeginDrawing(); ClearBackground(BLACK); - DrawText( - "Congrats! You created your first window!", - 190, - 200, - 20, - LIGHTGRAY - ); + + Rectangle r; + Color c; + for (UgCmd cmd; ug_stack_pop(&ctx.stack, &cmd) >= 0;) { + switch (cmd.type) { + case CMD_RECT: + printf( + "rect x=%d y=%d w=%d h=%d\n", + cmd.rect.x, + cmd.rect.y, + cmd.rect.w, + cmd.rect.h + ); + c = (Color) { + .r = cmd.rect.color[0], + .g = cmd.rect.color[1], + .b = cmd.rect.color[2], + .a = cmd.rect.color[3], + }; + DrawRectangle( + cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h, c + ); + break; + default: + printf("Unknown cmd type: %d\n", cmd.type); + break; + } + } + EndDrawing(); - ug_end_frame(&ctx); + WaitTime(0.2); } CloseWindow(); @@ -38,13 +195,21 @@ int main(void) return 0; } +#define MAX_ELEMS 128 +#define MAX_CMDS 256 + int ug_init(UgCtx *ctx) { if (ctx == NULL) { return -1; } - ug_tree_init(&ctx->tree, 10); + ug_tree_init(&ctx->tree, MAX_ELEMS); + ug_stack_init(&ctx->stack, MAX_CMDS); + + ctx->cache = ug_cache_init(); + ctx->layout = row; + ctx->div_using = 0; return 0; } @@ -56,6 +221,106 @@ int ug_destroy(UgCtx *ctx) } ug_tree_destroy(&ctx->tree); + ug_cache_free(&ctx->cache); return 0; } + +int ug_frame_begin(UgCtx *ctx) { return 0; } + +int ug_frame_end(UgCtx *ctx) { return 0; } + +int ug_input_window_size(UgCtx *ctx, int width, int height) +{ + if (ctx == NULL) { + return -1; + } + + if (width <= 0 || height <= 0) { + return -1; + } + + if (width >= 0) { + ctx->size.width = width; + } + if (height >= 0) { + ctx->size.height = height; + } + + return 0; +} + +UgId djb2(const char *str) +{ + uint64_t hash = 5381; + uint32_t c; + + while ((c = (str++)[0]) != 0) { + hash = ((hash << 5) + hash) + c; + } /* hash * 33 + c */ + + return hash; +} + +int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) +{ + if (ctx == NULL || name == NULL) { + return -1; + } + + UgId id = djb2(name); + + // add the element if it does not exist + UgElem *c_elem = ug_cache_search(&ctx->cache, id); + if (c_elem == NULL) { + UgElem elem = { + .id = id, + .type = ETYPE_DIV, + .rec = (UgRect) { + .x = div.x, + .y = div.y, + .w = div.w >= 0 ? div.w : ctx->size.width, + .h = div.h >= 0 ? div.h : ctx->size.height, + }}; + uint32_t c_idx; + c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); + } + + // FIXME: why save the id in the tree and not something more direct like + // the element pointer or the index into the cache vector? + int div_node = ug_tree_add(&ctx->tree, c_elem->id, ctx->div_using); + if (div_node < 0) { + // do something + printf("Error adding to tree\n"); + } + ctx->div_using = div_node; + +// printf( +// "elem.rect: x=%d x=%d w=%d h=%d\n", +// c_elem->rec.x, +// c_elem->rec.y, +// c_elem->rec.w, +// c_elem->rec.h +// ); + UgCmd cmd = { + .type = CMD_RECT, + .rect = + { + .x = c_elem->rec.x, + .y = c_elem->rec.y, + .w = c_elem->rec.w, + .h = c_elem->rec.h, + .color = {0xff, 0x00, 0x00, 0xff}, // red + }, + }; + ug_stack_push(&ctx->stack, &cmd); + + return 0; +} + +int ug_div_end(UgCtx *ctx) +{ + // the div_using returns to the parent of the current one + ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using); + return 0; +} diff --git a/ugui.h b/ugui.h index 08dabb6..ecb2fe8 100644 --- a/ugui.h +++ b/ugui.h @@ -15,16 +15,15 @@ typedef struct { uint8_t r, g, b, a; } UgColor; +typedef uint64_t UgId; + typedef enum { ETYPE_NONE = 0, - ETYPE_BUTTON, - ETYPE_TEXT, - ETYPE_SCROLL, - ETYPE_SLIDER, + ETYPE_DIV, } UgElemType; typedef struct { - uint64_t id; + UgId id; UgRect rec; union { uint32_t type_int; @@ -37,27 +36,42 @@ typedef struct { typedef struct { int size, elements; - uint32_t *vector; // vector of element ids + UgId *vector; // vector of element ids int *refs, *ordered_refs; } UgTree; typedef struct { - UgTree tree; -} UgCtx; + struct _IdTable *table; + UgElem *array; + uint64_t *present, *used; + int cycles; +} UgElemCache; + +typedef struct _UgCtx UgCtx; // tree implementation -int ug_tree_init(UgTree *tree, unsigned int size); +int ug_tree_init(UgTree *tree, unsigned int size); int ug_tree_pack(UgTree *tree); int ug_tree_resize(UgTree *tree, unsigned int newsize); -int ug_tree_add(UgTree *tree, uint32_t elem, int parent); +int ug_tree_add(UgTree *tree, UgId elem, int parent); int ug_tree_prune(UgTree *tree, int ref); int ug_tree_subtree_size(UgTree *tree, int ref); int ug_tree_children_it(UgTree *tree, int parent, int *cursor); int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); +int ug_tree_parentof(UgTree *tree, int node); int ug_tree_destroy(UgTree *tree); +// cache implementation +UgElemCache ug_cache_init(void); +void ug_cache_free(UgElemCache *cache); +UgElem *ug_cache_search(UgElemCache *cache, UgId id); +UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index); + + int ug_init(UgCtx *ctx); int ug_destroy(UgCtx *ctx); +int ug_frame_begin(UgCtx *ctx); +int ug_frame_end(UgCtx *ctx); #endif // _UGUI_H diff --git a/vectree.c b/vectree.c index e9e80f8..8c3af8d 100644 --- a/vectree.c +++ b/vectree.c @@ -12,7 +12,7 @@ int ug_tree_init(UgTree *tree, unsigned int size) return -1; } - tree->vector = malloc(sizeof(UgElem) * size); + tree->vector = malloc(sizeof(UgId) * size); if (tree->vector == NULL) { return -1; } @@ -37,7 +37,7 @@ int ug_tree_init(UgTree *tree, unsigned int size) } // fill vector with zeroes - memset(tree->vector, 0, size * sizeof(UgElem)); + memset(tree->vector, 0, size * sizeof(UgId)); tree->size = size; tree->elements = 0; @@ -118,7 +118,7 @@ int ug_tree_resize(UgTree *tree, unsigned int newsize) return -1; } - uint32_t *newvec = realloc(tree->vector, newsize * sizeof(uint32_t)); + UgId *newvec = realloc(tree->vector, newsize * sizeof(UgId)); if (newvec == NULL) { return -1; } @@ -149,7 +149,7 @@ int ug_tree_resize(UgTree *tree, unsigned int newsize) } // add an element to the tree, return it's ref -int ug_tree_add(UgTree *tree, uint32_t elem, int parent) +int ug_tree_add(UgTree *tree, UgId elem, int parent) { if (tree == NULL) { return -1; @@ -333,3 +333,12 @@ int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) return -1; } + +int ug_tree_parentof(UgTree *tree, int node) +{ + if (tree == NULL || !IS_VALID_REF(tree, node) || + !REF_IS_PRESENT(tree, node)) { + return -1; + } + return tree->refs[node]; +} From 99df8ad38d646137bcf871d93ef52f845491bd5a Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 1 Jan 2024 22:49:03 +0100 Subject: [PATCH 005/250] fixed tree_prune and level_order_it --- vectree.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/vectree.c b/vectree.c index 8c3af8d..5bd7bb1 100644 --- a/vectree.c +++ b/vectree.c @@ -24,7 +24,7 @@ int ug_tree_init(UgTree *tree, unsigned int size) } // ordered refs are used in the iterators - tree->ordered_refs = malloc(sizeof(int) * size); + tree->ordered_refs = malloc(sizeof(int) * (size + 1)); if (tree->ordered_refs == NULL) { free(tree->vector); free(tree->refs); @@ -128,7 +128,7 @@ int ug_tree_resize(UgTree *tree, unsigned int newsize) return -1; } - int *neworrefs = realloc(tree->ordered_refs, newsize * sizeof(int)); + int *neworrefs = realloc(tree->ordered_refs, (newsize + 1)* sizeof(int)); if (neworrefs == NULL) { return -1; } @@ -209,9 +209,10 @@ int ug_tree_prune(UgTree *tree, int ref) tree->vector[ref] = 0; tree->refs[ref] = -1; + tree->elements--; int count = 1; - for (int i = 0; i < tree->size; i++) { + for (int i = 0; tree->elements > 0 && i < tree->size; i++) { if (tree->refs[i] == ref) { count += ug_tree_prune(tree, i); } @@ -295,7 +296,7 @@ int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) // TODO: this could also be done when adding or removing elements // first call, create a ref array ordered like we desire - if (queue == NULL) { + if (*cursor == -1) { *cursor = 0; for (int i = 0; i < tree->size; i++) { queue[i] = -1; @@ -317,6 +318,8 @@ int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) ref = queue[off]; } while (IS_VALID_REF(tree, ref)); + // This line is why tree->ordered_refs has to be size+1 + queue[off+1] = -1; } // PRINT_ARR(queue, tree->size); From 7909306d7af5a5695c9b406fab6d26193f091843 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 2 Jan 2024 01:34:57 +0100 Subject: [PATCH 006/250] layout ideas --- ARCHITECTURE.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 4ecdfd0..c626495 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -88,6 +88,88 @@ multiple +--------v---------+ | +--------------+ ``` +### Layouting + +```text + parent div +[0,0]->+------------------------------------------------+ + | | + | | + | | + | | + | | + | | + | | + | space_r | + | space_c | begin div1 + | | ----+ + | | | + | | | + | | | + | | | + | | | + | | | + | | | + +------------------------------------------------+ | + | + | + parent div | +[0,0]->+----------------------+-------------------------+ | + | | | | + | | | | + | | | <---+ + | div 1 | | end div1 + | | | + | | | + | | | + | | | + +----------------------+ space_r | + | | | + | | | begin div2 + | | | ----+ + | | | | + | space_c | | | + | | | | + | | | | + | | | | + +----------------------+-------------------------+ | + | + | + parent div | +[0,0]->+----------------------+--------+----------------+ | + | | | | | + | | div 2 | | | + | | | | | + | div 1 +--------+ | | + | |xxxxxxxx| | | + | |xxxxxxxx| | <---+ + | |xxxxxxxx| | end div2 + | |xxxxxxxx| | + +----------------------+--------+ space_r | + | | | + | | | + | | | + | | | + | space_c | | + | | | + | | | + | | | + +-------------------------------+----------------+ + + +-+ + |x|: Lost Space + +-+ +``` + +TODO: handle when the content overflows the div +- Use a different concept, like a view or relative space, for example the child +div could have position `[0,0]` but in reality it is relative to the origin of the +parent div +- each div could have a view and a total area of the content, when drawing everything +is clipped to the view and scrollbars are shown +- individual elements accept dimensions and the x/y coordinates could be interpreted +as offset if the layout is row/column or absolute coordinates if the leayout is floating + ### Notes How elements determine if they have focus or not From 28b5ee16fb3bde66ae7a7456a22a9220ad911cd8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 9 Jan 2024 16:00:03 +0100 Subject: [PATCH 007/250] mar 9 gen 2024, 16:00:03, CET --- ARCHITECTURE.md | 138 +++++++++--------- ugui.c | 376 ++++++++++++++++++++++++++++++++++++++++-------- ugui.h | 46 ++++-- 3 files changed, 418 insertions(+), 142 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index c626495..b533650 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,8 +1,8 @@ ## High level overview Under the hood every element has an id, this id allows the library to store state -between frames. -Elements are also cached such that when the ui tree is rebuilt at the beginning of +between frames. +Elements are also cached such that when the ui tree is rebuilt at the beginning of every frame the element data structure doesn't have to be rebuilt. Elements are arranged in a tree, nodes are container elements that can contain other @@ -11,7 +11,7 @@ elements, leafs are elements that cannot contain other elements. Every element has a size and a position, containers also have to keep track of their layout information and some other state. -Elements can push commands into the draw stack, which is a structure that contains +Elements can push commands into the draw stack, which is a structure that contains all the draw commands that the user application has to perform do display the ui correctly, such commands include drawing lines, rectangles, sprites, text, etc. @@ -90,75 +90,59 @@ multiple +--------v---------+ | ### Layouting -```text - parent div -[0,0]->+------------------------------------------------+ - | | - | | - | | - | | - | | - | | - | | - | space_r | - | space_c | begin div1 - | | ----+ - | | | - | | | - | | | - | | | - | | | - | | | - | | | - +------------------------------------------------+ | - | - | - parent div | -[0,0]->+----------------------+-------------------------+ | - | | | | - | | | | - | | | <---+ - | div 1 | | end div1 - | | | - | | | - | | | - | | | - +----------------------+ space_r | - | | | - | | | begin div2 - | | | ----+ - | | | | - | space_c | | | - | | | | - | | | | - | | | | - +----------------------+-------------------------+ | - | - | - parent div | -[0,0]->+----------------------+--------+----------------+ | - | | | | | - | | div 2 | | | - | | | | | - | div 1 +--------+ | | - | |xxxxxxxx| | | - | |xxxxxxxx| | <---+ - | |xxxxxxxx| | end div2 - | |xxxxxxxx| | - +----------------------+--------+ space_r | - | | | - | | | - | | | - | | | - | space_c | | - | | | - | | | - | | | - +-------------------------------+----------------+ +Layouting happens in a dynamic grid, when a new element is inserted in a non-floating +manner it reserves a space in the grid, new elements are placed following this grid. - +-+ - |x|: Lost Space - +-+ +Every div has two points of origin, one for the row layout and one for the column +layout, named origin_r and origin_c respectively + +origin_r is used when the row layout is used and it is used to position the child +elements one next to the other, as such it always points to the top-right edge +of the last row element + +```text +Layout: row +#: lost space + Parent div +x---------------------------------+ +|[origin_c] | +|[origin_r] | +| | +| | +| | +| | +| | +| | +| | ++---------------------------------+ + + Parent div ++-----x---------------------------+ +| |[origin_r] | +| E1 | | +| | | +x-----+---------------------------+ +|[origin_c] | +| | | +| | | +| | | +| | | +| | | ++-----+---------------------------+ + + Parent div ++-----+----------+-----x----------+ +| | E2 | |[origin_r]| +| E1 +----------+ | | +| |##########| E3 | | ++-----+##########| | | +|################| | | ++----------------x-----+----------+ +| [origin_c] | +| | | +| | | +| | | ++----------------+----------------+ ``` TODO: handle when the content overflows the div @@ -170,6 +154,10 @@ is clipped to the view and scrollbars are shown - individual elements accept dimensions and the x/y coordinates could be interpreted as offset if the layout is row/column or absolute coordinates if the leayout is floating +A div can be marked resizeable or fixed, and static or dynamic. The difference being +that resizeable adds a resize handle to the div and dynamic lets the content overflow +causing scrollbars to be drawn + ### Notes How elements determine if they have focus or not @@ -185,6 +173,12 @@ set has_focus property if(PARENT_HAS_FOCUS()) { update stuff } else { - fast path to return + fast path to return } ``` + +How to get ids: + 1. use a name for each element + 2. supply an id for each element + 3. use a macro and the line position as id and then hash it + 4. use a macro, get the code line and hash it diff --git a/ugui.c b/ugui.c index 8ee7c41..ec77b05 100644 --- a/ugui.c +++ b/ugui.c @@ -8,6 +8,20 @@ #include "raylib.h" #include "ugui.h" +#define RGBA(u) \ + (UgColor) \ + { \ + .r = (u >> 24) & 0xff, .g = (u >> 16) & 0xff, .b = (u >> 8) & 0xff, \ + .a = (u >> 0) & 0xff \ + } +#define DIV_FILL \ + (UgRect) { .x = 0, .y = 0, .w = 0, .h = 0 } + +#define STACK_STEP 10 +#define MAX_ELEMS 128 +#define MAX_CMDS 256 +#define ROOT_ID 1 + typedef struct _UgCmd { enum { CMD_RECT = 0, @@ -15,15 +29,15 @@ typedef struct _UgCmd { union { struct { - int32_t x, y, w, h; - uint8_t color[4]; // rgba, [0] = r + UgRect rect; + UgColor color; } rect; }; } UgCmd; typedef struct _UgStack { - int size, count; - UgCmd *stack; + int size, count; + UgCmd *stack; } UgStack; int ug_stack_init(UgStack *stack, uint32_t size); @@ -58,8 +72,6 @@ int ug_stack_free(UgStack *stack) return 0; } -#define STACK_STEP 10 - int ug_stack_push(UgStack *stack, UgCmd *cmd) { if (stack == NULL || cmd == NULL) { @@ -100,6 +112,10 @@ int ug_stack_pop(UgStack *stack, UgCmd *cmd) return 0; } +enum InputEventFlags { + INPUT_CTX_SIZE = 1 << 0, // window size was changed +}; + struct _UgCtx { enum { row = 0, @@ -115,14 +131,29 @@ struct _UgCtx { int width, height; } size; + // input structure, it describes the events received between frames + struct { + uint32_t flags; + } input; + int div_using; // tree node indicating the current active div }; -int ug_div_begin(UgCtx *ctx, const char *name, UgRect div); +// layouting +int ug_layout_row(void); +int ug_layout_column(void); +int ug_layout_floating(void); +int ug_layout_next_row(void); +int ug_layout_next_column(void); + +int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); int ug_div_end(UgCtx *ctx); int ug_input_window_size(UgCtx *ctx, int width, int height); UgId djb2(const char *str); +int ug_button(UgCtx *ctx, const char *label, UgRect size); + + int main(void) { UgCtx ctx; @@ -136,18 +167,21 @@ int main(void) // Main loop while (!WindowShouldClose()) { - // UI handling - ug_frame_begin(&ctx); - + // Input handling if (IsWindowResized()) { width = GetScreenWidth(); height = GetScreenHeight(); ug_input_window_size(&ctx, width, height); } -// main div, fill the whole window -#define DIV_FILL (UgRect) {.x = 0, .y = 0, .w = -1, .h = -1} + // UI handling + ug_frame_begin(&ctx); + + // main div, fill the whole window ug_div_begin(&ctx, "main", DIV_FILL); + + ug_button(&ctx, "button0", (UgRect){.w = 100, .h = 16}); + ug_div_end(&ctx); ug_frame_end(&ctx); @@ -156,26 +190,30 @@ int main(void) BeginDrawing(); ClearBackground(BLACK); - Rectangle r; - Color c; + Color c; for (UgCmd cmd; ug_stack_pop(&ctx.stack, &cmd) >= 0;) { + printf("bruh\n"); switch (cmd.type) { case CMD_RECT: - printf( - "rect x=%d y=%d w=%d h=%d\n", - cmd.rect.x, - cmd.rect.y, - cmd.rect.w, - cmd.rect.h - ); + //printf( + // "rect x=%d y=%d w=%d h=%d\n", + // cmd.rect.rect.x, + // cmd.rect.rect.y, + // cmd.rect.rect.w, + // cmd.rect.rect.h + //); c = (Color) { - .r = cmd.rect.color[0], - .g = cmd.rect.color[1], - .b = cmd.rect.color[2], - .a = cmd.rect.color[3], + .r = cmd.rect.color.r, + .g = cmd.rect.color.g, + .b = cmd.rect.color.b, + .a = cmd.rect.color.a, }; DrawRectangle( - cmd.rect.x, cmd.rect.y, cmd.rect.w, cmd.rect.h, c + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h, + c ); break; default: @@ -195,9 +233,6 @@ int main(void) return 0; } -#define MAX_ELEMS 128 -#define MAX_CMDS 256 - int ug_init(UgCtx *ctx) { if (ctx == NULL) { @@ -226,9 +261,80 @@ int ug_destroy(UgCtx *ctx) return 0; } -int ug_frame_begin(UgCtx *ctx) { return 0; } +void print_tree(UgCtx *ctx) +{ + printf("ctx->tree: ["); + for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) { + printf("%d, ", x); + } + printf("-1]\n"); +} -int ug_frame_end(UgCtx *ctx) { return 0; } +int ug_frame_begin(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + // 1. Create the root div element + UgRect space = { + .x = 0, + .y = 0, + .w = ctx->size.width, + .h = ctx->size.height, + }; + UgElem root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .flags = 0, + .rect = space, + .div = + { + .layout = DIV_LAYOUT_ROW, + .origin_r = {0}, + .origin_c = {0}, + .color_bg = {0}, + }, + }; + // The root should have the updated flag only if the size of the window + // was changed between frames, this propagates an element size recalculation + // down the element tree + if (ctx->input.flags & INPUT_CTX_SIZE) { + root.flags |= ELEM_UPDATED; + } + + // FIXME: check errors + uint32_t cache_idx; + ug_cache_insert(&ctx->cache, &root, &cache_idx); + ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0); + + if (ctx->div_using < 0) { + printf("why?\n"); + return -1; + } + + //print_tree(ctx); + + // The root element does not push anything to the stack + // TODO: add a background color taken from a theme or config + + return 0; +} + +int ug_frame_end(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + // 1. clear the tree + ug_tree_prune(&ctx->tree, 0); + + // 2. clear input fields + ctx->input.flags = 0; + + return 0; +} int ug_input_window_size(UgCtx *ctx, int width, int height) { @@ -262,28 +368,34 @@ UgId djb2(const char *str) return hash; } -int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) +int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) { - if (ctx == NULL || name == NULL) { + if (ctx == NULL || label == NULL) { return -1; } - UgId id = djb2(name); + UgId id = djb2(label); + + // TODO: do layouting if the element is new or the parent has updated + int is_new_elem = 0; // add the element if it does not exist UgElem *c_elem = ug_cache_search(&ctx->cache, id); if (c_elem == NULL) { - UgElem elem = { - .id = id, - .type = ETYPE_DIV, - .rec = (UgRect) { - .x = div.x, - .y = div.y, - .w = div.w >= 0 ? div.w : ctx->size.width, - .h = div.h >= 0 ? div.h : ctx->size.height, - }}; + UgElem elem = {0}; uint32_t c_idx; - c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); + c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); + is_new_elem = 1; + } + + // take a reference to the parent + // FIXME: if the tree held pointers to the elements then no more + // redundant cache search + UgId parent_id = ctx->tree.vector[ctx->div_using]; + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; } // FIXME: why save the id in the tree and not something more direct like @@ -295,22 +407,78 @@ int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) } ctx->div_using = div_node; -// printf( -// "elem.rect: x=%d x=%d w=%d h=%d\n", -// c_elem->rec.x, -// c_elem->rec.y, -// c_elem->rec.w, -// c_elem->rec.h -// ); + //print_tree(ctx); + + // layouting + // TODO: do layout + if (is_new_elem || parent->flags & ELEM_UPDATED) { + c_elem->id = id; + c_elem->type = ETYPE_DIV; + + // 1. Select the right origin offset + switch (parent->div.layout) { + case DIV_LAYOUT_ROW: + c_elem->rect = (UgRect) { + .x = parent->div.origin_r.x + div.x, + .y = parent->div.origin_r.y + div.y, + }; + break; + case DIV_LAYOUT_COLUMN: + c_elem->rect = (UgRect) { + .x = parent->div.origin_c.x + div.x, + .y = parent->div.origin_c.y + div.y, + }; + break; + case DIV_LAYOUT_FLOATING: + c_elem->rect = (UgRect) { + .x = div.x, + .y = div.y, + }; + break; + default: + // Error + break; + } + + // 2. Calculate width & height + // TODO: what about negative values? + c_elem->rect.w = div.w != 0 ? div.w : parent->rect.w; + c_elem->rect.h = div.h != 0 ? div.h : parent->rect.h; + + // 3. Mark the element as updated + c_elem->flags |= ELEM_UPDATED; + + // 4. Set div information + c_elem->div.layout = parent->div.layout; + c_elem->div.origin_c = (UgPoint) { + .x = c_elem->rect.x, + .y = c_elem->rect.y, + }; + c_elem->div.origin_r = c_elem->div.origin_c; + c_elem->div.color_bg = RGBA(0xff0000ff); + + // 4. Update the origins of the parent + parent->div.origin_r = (UgPoint) { + .x = c_elem->rect.x + c_elem->rect.w, + .y = c_elem->rect.y, + }; + parent->div.origin_c = (UgPoint) { + .x = c_elem->rect.x, + .y = c_elem->rect.y + c_elem->rect.h, + }; + } else { + // TODO: check active + // TODO: check resizeable + // TODO: check scrollbars + } + + // Add the background to the draw stack UgCmd cmd = { .type = CMD_RECT, .rect = { - .x = c_elem->rec.x, - .y = c_elem->rec.y, - .w = c_elem->rec.w, - .h = c_elem->rec.h, - .color = {0xff, 0x00, 0x00, 0xff}, // red + .rect = c_elem->rect, + .color = c_elem->div.color_bg, }, }; ug_stack_push(&ctx->stack, &cmd); @@ -324,3 +492,99 @@ int ug_div_end(UgCtx *ctx) ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using); return 0; } + +int ug_button(UgCtx *ctx, const char *label, UgRect size) +{ + if (ctx == NULL || label == NULL) { + return -1; + } + + UgId id = djb2(label); + + // TODO: do layouting if the element is new or the parent has updated + int is_new_elem = 0; + + // add the element if it does not exist + UgElem *c_elem = ug_cache_search(&ctx->cache, id); + if (c_elem == NULL) { + UgElem elem = {0}; + uint32_t c_idx; + c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); + is_new_elem = 1; + } + + // take a reference to the parent + // FIXME: if the tree held pointers to the elements then no more + // redundant cache search + UgId parent_id = ctx->tree.vector[ctx->div_using]; + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + // if the element is new or the parent was updated then redo layout + if (is_new_elem || parent->flags & ELEM_UPDATED) { + c_elem->id = id; + c_elem->type = ETYPE_BUTTON; + + // 1. Select the right origin offset + switch (parent->div.layout) { + case DIV_LAYOUT_ROW: + c_elem->rect = (UgRect) { + .x = parent->div.origin_r.x + size.x, + .y = parent->div.origin_r.y + size.y, + }; + break; + case DIV_LAYOUT_COLUMN: + c_elem->rect = (UgRect) { + .x = parent->div.origin_c.x + size.x, + .y = parent->div.origin_c.y + size.y, + }; + break; + case DIV_LAYOUT_FLOATING: + c_elem->rect = (UgRect) { + .x = size.x, + .y = size.y, + }; + break; + default: + // Error + break; + } + + // 2. Calculate width & height + // TODO: what about negative values? + c_elem->rect.w = size.w != 0 ? size.w : parent->rect.w; + c_elem->rect.h = size.h != 0 ? size.h : parent->rect.h; + + /// Not a div, so not needed + // 3. Mark the element as updated + // 4. Set div information + + // 4. Update the origins of the parent + parent->div.origin_r = (UgPoint) { + .x = c_elem->rect.x + c_elem->rect.w, + .y = c_elem->rect.y, + }; + parent->div.origin_c = (UgPoint) { + .x = c_elem->rect.x, + .y = c_elem->rect.y + c_elem->rect.h, + }; + } else { + // TODO: Check for interactions + } + + // Draw the button + UgCmd cmd = { + .type = CMD_RECT, + .rect = + { + .rect = c_elem->rect, + .color = RGBA(0x0000ffff), + }, + }; + ug_stack_push(&ctx->stack, &cmd); + + return 0; +} diff --git a/ugui.h b/ugui.h index ecb2fe8..36739d2 100644 --- a/ugui.h +++ b/ugui.h @@ -20,31 +20,51 @@ typedef uint64_t UgId; typedef enum { ETYPE_NONE = 0, ETYPE_DIV, + ETYPE_BUTTON, } UgElemType; +enum UgElemFlags { + ELEM_UPDATED = 1 << 0, +}; + typedef struct { - UgId id; - UgRect rec; + UgId id; + uint32_t flags; + UgRect rect; + union { - uint32_t type_int; + uint32_t type_int; UgElemType type; }; + + // type-specific fields + union { + struct { + enum { + DIV_LAYOUT_ROW = 0, + DIV_LAYOUT_COLUMN, + DIV_LAYOUT_FLOATING, + } layout; + UgPoint origin_r, origin_c; + UgColor color_bg; + } div; // Div + }; } UgElem; // TODO: add a packed flag // TODO: add a fill index to skip some searching for free spots typedef struct { - int size, elements; + int size, elements; UgId *vector; // vector of element ids - int *refs, *ordered_refs; + int *refs, *ordered_refs; } UgTree; typedef struct { - struct _IdTable *table; - UgElem *array; - uint64_t *present, *used; - int cycles; + struct _IdTable *table; + UgElem *array; + uint64_t *present, *used; + int cycles; } UgElemCache; typedef struct _UgCtx UgCtx; @@ -63,10 +83,9 @@ int ug_tree_destroy(UgTree *tree); // cache implementation UgElemCache ug_cache_init(void); -void ug_cache_free(UgElemCache *cache); -UgElem *ug_cache_search(UgElemCache *cache, UgId id); -UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index); - +void ug_cache_free(UgElemCache *cache); +UgElem *ug_cache_search(UgElemCache *cache, UgId id); +UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index); int ug_init(UgCtx *ctx); int ug_destroy(UgCtx *ctx); @@ -74,4 +93,3 @@ int ug_frame_begin(UgCtx *ctx); int ug_frame_end(UgCtx *ctx); #endif // _UGUI_H - From d4c97e1f4f7c05d9fa52d41e40c1cc232aea99c7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 9 Jan 2024 16:05:51 +0100 Subject: [PATCH 008/250] fifo --- Makefile | 9 ++-- compile_flags.txt | 2 +- get_raylib.sh | 8 ++++ ugui.c | 105 +++++++++++++++++++++++++--------------------- 4 files changed, 70 insertions(+), 54 deletions(-) create mode 100755 get_raylib.sh diff --git a/Makefile b/Makefile index 7eb7617..31a689a 100644 --- a/Makefile +++ b/Makefile @@ -1,13 +1,10 @@ -CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/src +CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/include CC = gcc -LDFLAGS = -Lraylib/src -lm +LDFLAGS = -Lraylib/lib -lm all: ugui -raylib/src/libraylib.a: raylib/src/Makefile - cd raylib/src; $(MAKE) PLATFORM=PLATFORM_DESKTOP - -ugui: ugui.o vectree.o cache.o raylib/src/libraylib.a +ugui: ugui.o vectree.o cache.o raylib/lib/libraylib.a ugui.o: ugui.c ugui.h diff --git a/compile_flags.txt b/compile_flags.txt index 13a23a6..3e90386 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,4 +1,4 @@ --Iraylib/src +-Iraylib/include -Wall -Wextra -pedantic diff --git a/get_raylib.sh b/get_raylib.sh new file mode 100755 index 0000000..cbe25df --- /dev/null +++ b/get_raylib.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +mkdir -p raylib +wget https://github.com/raysan5/raylib/releases/download/5.0/raylib-5.0_linux_amd64.tar.gz +tar -xvf raylib-5.0_linux_amd64.tar.gz +mv ./raylib-5.0_linux_amd64/* ./raylib/ + +rm -rf raylib-5.0_linux_amd64 raylib-5.0_linux_amd64.tar.gz diff --git a/ugui.c b/ugui.c index 8ee7c41..46959dc 100644 --- a/ugui.c +++ b/ugui.c @@ -21,81 +21,91 @@ typedef struct _UgCmd { }; } UgCmd; -typedef struct _UgStack { - int size, count; - UgCmd *stack; -} UgStack; +typedef struct _UgFifo { + int size, in, out, count; + UgCmd *vec; +} UgFifo; -int ug_stack_init(UgStack *stack, uint32_t size); -int ug_stack_free(UgStack *stack); -int ug_stack_push(UgStack *stack, UgCmd *cmd); -int ug_stack_pop(UgStack *stack, UgCmd *cmd); +int ug_fifo_init(UgFifo *fifo, uint32_t size); +int ug_fifo_free(UgFifo *fifo); +int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd); +int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd); -int ug_stack_init(UgStack *stack, uint32_t size) +int ug_fifo_init(UgFifo *fifo, uint32_t size) { - if (stack == NULL) { + if (fifo == NULL) { return -1; } - stack->size = size; - stack->count = 0; - stack->stack = calloc(size, sizeof(UgCmd)); + fifo->size = size; + fifo->in = 0; + fifo->out = 0; + fifo->count = 0; + fifo->vec = calloc(size, sizeof(UgCmd)); - if (stack->stack == NULL) { + if (fifo->vec == NULL) { return -1; } return 0; } -int ug_stack_free(UgStack *stack) +int ug_fifo_free(UgFifo *fifo) { - if (stack != NULL && stack->stack != NULL) { - free(stack->stack); - stack->size = 0; - stack->count = 0; + if (fifo != NULL && fifo->vec != NULL) { + free(fifo->vec); + fifo->size = 0; + fifo->in = 0; + fifo->out = 0; + fifo->count = 0; } return 0; } -#define STACK_STEP 10 +#define FIFO_STEP 10 -int ug_stack_push(UgStack *stack, UgCmd *cmd) +int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd) { - if (stack == NULL || cmd == NULL) { + if (fifo == NULL || cmd == NULL) { return -1; } - if (stack->count >= stack->size) { - // UgCmd *tmp = reallocarray(stack->stack, stack->size + - // STACK_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; + if (fifo->count >= fifo->size) { + // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + + // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; // } - // stack->stack = tmp; - // stack->size += STACK_STEP; + // fifo->vec = tmp; + // fifo->size += fifo_STEP; return -1; } - stack->stack[stack->count++] = *cmd; + fifo->vec[fifo->in] = *cmd; + + fifo->in = (fifo->in + 1) % fifo->size; + fifo->count++; return 0; } -int ug_stack_pop(UgStack *stack, UgCmd *cmd) +int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd) { - if (stack == NULL || cmd == NULL) { + if (fifo == NULL || cmd == NULL) { return -1; } - if (stack->count <= 0) { - // UgCmd *tmp = reallocarray(stack->stack, stack->size + - // STACK_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; + if (fifo->count <= 0) { + // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + + // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; // } - // stack->stack = tmp; - // stack->size += STACK_STEP; + // fifo->vec = tmp; + // fifo->size += fifo_STEP; return -1; } - *cmd = stack->stack[--stack->count]; + *cmd = fifo->vec[fifo->out]; + + fifo->out = (fifo->out + 1) % fifo->size; + fifo->count--; return 0; } @@ -109,7 +119,7 @@ struct _UgCtx { UgTree tree; UgElemCache cache; - UgStack stack; + UgFifo fifo; struct { int width, height; @@ -158,7 +168,7 @@ int main(void) Rectangle r; Color c; - for (UgCmd cmd; ug_stack_pop(&ctx.stack, &cmd) >= 0;) { + for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { switch (cmd.type) { case CMD_RECT: printf( @@ -205,7 +215,7 @@ int ug_init(UgCtx *ctx) } ug_tree_init(&ctx->tree, MAX_ELEMS); - ug_stack_init(&ctx->stack, MAX_CMDS); + ug_fifo_init(&ctx->fifo, MAX_CMDS); ctx->cache = ug_cache_init(); ctx->layout = row; @@ -222,6 +232,7 @@ int ug_destroy(UgCtx *ctx) ug_tree_destroy(&ctx->tree); ug_cache_free(&ctx->cache); + ug_fifo_free(&ctx->fifo); return 0; } @@ -295,13 +306,13 @@ int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) } ctx->div_using = div_node; -// printf( -// "elem.rect: x=%d x=%d w=%d h=%d\n", -// c_elem->rec.x, -// c_elem->rec.y, -// c_elem->rec.w, -// c_elem->rec.h -// ); + // printf( + // "elem.rect: x=%d x=%d w=%d h=%d\n", + // c_elem->rec.x, + // c_elem->rec.y, + // c_elem->rec.w, + // c_elem->rec.h + // ); UgCmd cmd = { .type = CMD_RECT, .rect = @@ -313,7 +324,7 @@ int ug_div_begin(UgCtx *ctx, const char *name, UgRect div) .color = {0xff, 0x00, 0x00, 0xff}, // red }, }; - ug_stack_push(&ctx->stack, &cmd); + ug_fifo_enqueue(&ctx->fifo, &cmd); return 0; } From d6358944ace823e9dad7be0973aeb841c2fdb12b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 11 Jan 2024 18:53:20 +0100 Subject: [PATCH 009/250] some layout work --- cache.c | 5 +- ugui.c | 318 ++++++++++++++++++++++++++++++------------------------ ugui.h | 38 ++++--- vectree.c | 12 ++- 4 files changed, 210 insertions(+), 163 deletions(-) diff --git a/cache.c b/cache.c index 1ed0d35..5195892 100644 --- a/cache.c +++ b/cache.c @@ -26,7 +26,7 @@ // hash table (id -> cache index) typedef struct { - UgId id; + UgId id; uint32_t index; } IdElem; @@ -204,9 +204,8 @@ UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) } // Insert an element in the cache -UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index) +UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index) { *index = ug_cache_get_free_spot(cache); return ug_cache_insert_at(cache, g, *index); } - diff --git a/ugui.c b/ugui.c index 540a9c5..3e57bf8 100644 --- a/ugui.c +++ b/ugui.c @@ -17,6 +17,13 @@ #define DIV_FILL \ (UgRect) { .x = 0, .y = 0, .w = 0, .h = 0 } +#define MARK() \ + do { \ + printf("lmao\n"); \ + } while (0) + +#define FTEST(e, f) ((e)->flags & (f)) + #define STACK_STEP 10 #define MAX_ELEMS 128 #define MAX_CMDS 256 @@ -162,9 +169,12 @@ int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); int ug_div_end(UgCtx *ctx); int ug_input_window_size(UgCtx *ctx, int width, int height); UgId djb2(const char *str); +UgId FNV_1a(const char *str); int ug_button(UgCtx *ctx, const char *label, UgRect size); +UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect); + int main(void) { UgCtx ctx; @@ -190,28 +200,43 @@ int main(void) // main div, fill the whole window ug_div_begin(&ctx, "main", DIV_FILL); - - ug_button(&ctx, "button0", (UgRect) {.w = 100, .h = 16}); - + { + ug_button( + &ctx, + "batt342353453452wrwea", + (UgRect) {.y = 100, .x = 130, .w = 100, .h = 16} + ); + ug_button( + &ctx, + "arieasd3ree2234tast", + (UgRect) {.x = 10, .w = 30, .h = 30} + ); + ug_button( + &ctx, + "bughsdfsfdstton2", + (UgRect) {.x = 10, .w = 30, .h = 30} + ); + } ug_div_end(&ctx); ug_frame_end(&ctx); // drawing BeginDrawing(); - ClearBackground(BLACK); + // ClearBackground(BLACK); + printf("----- Draw Begin -----\n"); Color c; for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { switch (cmd.type) { case CMD_RECT: - // printf( - // "rect x=%d y=%d w=%d h=%d\n", - // cmd.rect.rect.x, - // cmd.rect.rect.y, - // cmd.rect.rect.w, - // cmd.rect.rect.h - //); + printf( + "draw rect x=%d y=%d w=%d h=%d\n", + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h + ); c = (Color) { .r = cmd.rect.color.r, .g = cmd.rect.color.g, @@ -231,6 +256,7 @@ int main(void) break; } } + printf("----- Draw End -----\n\n"); EndDrawing(); @@ -243,6 +269,24 @@ int main(void) return 0; } +// search the element of the corresponding id in the cache, if no element is found +// insert a new one of that id. Return the pointer to the element +int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) +{ + int is_new = 0; + uint32_t cache_idx; + + UgElem *c_elem = ug_cache_search(&ctx->cache, id); + if (c_elem == NULL) { + UgElem tmp = {.id = id}; + c_elem = ug_cache_insert_new(&ctx->cache, &tmp, &cache_idx); + is_new = 1; + } + + *elem = c_elem; + return is_new; +} + int ug_init(UgCtx *ctx) { if (ctx == NULL) { @@ -276,9 +320,14 @@ void print_tree(UgCtx *ctx) { printf("ctx->tree: ["); for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) { - printf("%d, ", x); + printf( + "[%d:%d,%.4lx], ", + x, + ug_tree_parentof(&ctx->tree, x), + ug_tree_get(&ctx->tree, x) & 0xffff + ); } - printf("-1]\n"); + printf("[-1]]\n"); } int ug_frame_begin(UgCtx *ctx) @@ -315,8 +364,12 @@ int ug_frame_begin(UgCtx *ctx) } // FIXME: check errors - uint32_t cache_idx; - ug_cache_insert(&ctx->cache, &root, &cache_idx); + UgElem *c_elem; + int is_new = search_or_insert(ctx, &c_elem, root.id); + if (is_new || FTEST(&root, ELEM_UPDATED)) { + *c_elem = root; + } + ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0); if (ctx->div_using < 0) { @@ -329,6 +382,8 @@ int ug_frame_begin(UgCtx *ctx) // The root element does not push anything to the stack // TODO: add a background color taken from a theme or config + printf("##### Frame Begin #####\n"); + return 0; } @@ -344,6 +399,7 @@ int ug_frame_end(UgCtx *ctx) // 2. clear input fields ctx->input.flags = 0; + printf("##### Frame End #####\n\n"); return 0; } @@ -367,6 +423,20 @@ int ug_input_window_size(UgCtx *ctx, int width, int height) return 0; } +UgId FNV_1a(const char *str) +{ + const uint64_t fnv_off = 0xcbf29ce484222325; + const uint64_t fnv_prime = 0x100000001b3; + + uint64_t hash = fnv_off; + for (uint32_t c; (c = str[0]) != 0; str++) { + hash ^= c; + hash *= fnv_prime; + } + + return hash; +} + UgId djb2(const char *str) { uint64_t hash = 5381; @@ -385,98 +455,55 @@ int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) return -1; } - UgId id = djb2(label); + UgId id = FNV_1a(label); - // TODO: do layouting if the element is new or the parent has updated - int is_new_elem = 0; + UgElem *c_elem; + int is_new = search_or_insert(ctx, &c_elem, id); - // add the element if it does not exist - UgElem *c_elem = ug_cache_search(&ctx->cache, id); - if (c_elem == NULL) { - UgElem elem = {0}; - uint32_t c_idx; - c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); - is_new_elem = 1; + // FIXME: why save the id in the tree and not something more direct like + // the element pointer or the index into the cache vector? + int div_node = ug_tree_add(&ctx->tree, id, ctx->div_using); + if (div_node < 0) { + // do something + printf("Error adding to tree\n"); } // take a reference to the parent // FIXME: if the tree held pointers to the elements then no more // redundant cache search - UgId parent_id = ctx->tree.vector[ctx->div_using]; + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); UgElem *parent = ug_cache_search(&ctx->cache, parent_id); if (parent == NULL) { // Error, did you forget to do frame_begin()? return -1; } - // FIXME: why save the id in the tree and not something more direct like - // the element pointer or the index into the cache vector? - int div_node = ug_tree_add(&ctx->tree, c_elem->id, ctx->div_using); - if (div_node < 0) { - // do something - printf("Error adding to tree\n"); - } + print_tree(ctx); + + // Use the current div ctx->div_using = div_node; - // print_tree(ctx); + // 1. Fill the element fields + // this resets the flags + c_elem->type = ETYPE_DIV; + c_elem->flags = 0; - // layouting - // TODO: do layout - if (is_new_elem || parent->flags & ELEM_UPDATED) { - c_elem->id = id; - c_elem->type = ETYPE_DIV; - - // 1. Select the right origin offset - switch (parent->div.layout) { - case DIV_LAYOUT_ROW: - c_elem->rect = (UgRect) { - .x = parent->div.origin_r.x + div.x, - .y = parent->div.origin_r.y + div.y, - }; - break; - case DIV_LAYOUT_COLUMN: - c_elem->rect = (UgRect) { - .x = parent->div.origin_c.x + div.x, - .y = parent->div.origin_c.y + div.y, - }; - break; - case DIV_LAYOUT_FLOATING: - c_elem->rect = (UgRect) { - .x = div.x, - .y = div.y, - }; - break; - default: - // Error - break; - } - - // 2. Calculate width & height - // TODO: what about negative values? - c_elem->rect.w = div.w != 0 ? div.w : parent->rect.w; - c_elem->rect.h = div.h != 0 ? div.h : parent->rect.h; + // do layout and update flags only if the element was updated + if (is_new || FTEST(parent, ELEM_UPDATED)) { + // 2. layout the element + c_elem->rect = position_element(ctx, parent, div); // 3. Mark the element as updated c_elem->flags |= ELEM_UPDATED; - // 4. Set div information + // 4. Fill the div fields c_elem->div.layout = parent->div.layout; c_elem->div.origin_c = (UgPoint) { .x = c_elem->rect.x, .y = c_elem->rect.y, }; - c_elem->div.origin_r = c_elem->div.origin_c; c_elem->div.color_bg = RGBA(0xff0000ff); - - // 4. Update the origins of the parent - parent->div.origin_r = (UgPoint) { - .x = c_elem->rect.x + c_elem->rect.w, - .y = c_elem->rect.y, - }; - parent->div.origin_c = (UgPoint) { - .x = c_elem->rect.x, - .y = c_elem->rect.y + c_elem->rect.h, - }; + c_elem->div.origin_r = c_elem->div.origin_c; } else { // TODO: check active // TODO: check resizeable @@ -504,84 +531,99 @@ int ug_div_end(UgCtx *ctx) return 0; } +// position the rectangle inside the parent according to the layout +UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect) +{ + UgRect elem_rect = {0}; + UgPoint origin = {0}; + + // 1. Select the right origin + switch (parent->div.layout) { + case DIV_LAYOUT_ROW: + origin = parent->div.origin_r; + break; + case DIV_LAYOUT_COLUMN: + origin = parent->div.origin_c; + break; + case DIV_LAYOUT_FLOATING: // none + default: + // Error + break; + } + + // 2. Position the rect + elem_rect.x = origin.x + rect.x; + elem_rect.y = origin.y + rect.y; + + // 3. Calculate width & height + // TODO: what about negative values? + // FIXME: account for origin offset!! + elem_rect.w = rect.w > 0 ? rect.w : parent->rect.w; + elem_rect.h = rect.h > 0 ? rect.h : parent->rect.h; + + // 4. Update the origins of the parent + parent->div.origin_r = (UgPoint) { + .x = elem_rect.x + elem_rect.w, + .y = elem_rect.y, + }; + parent->div.origin_c = (UgPoint) { + .x = elem_rect.x, + .y = elem_rect.y + elem_rect.h, + }; + + /* + printf( + "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) -> {%d %d %d + %d}\n", parent->id, rect.x, rect.y, rect.w, rect.h, parent->rect.x, + parent->rect.y, + parent->rect.w, + parent->rect.h, + elem_rect.x, + elem_rect.y, + elem_rect.w, + elem_rect.h + ); + */ + return elem_rect; +} + int ug_button(UgCtx *ctx, const char *label, UgRect size) { if (ctx == NULL || label == NULL) { return -1; } - UgId id = djb2(label); + UgId id = FNV_1a(label); // TODO: do layouting if the element is new or the parent has updated - int is_new_elem = 0; + UgElem *c_elem; + int is_new = search_or_insert(ctx, &c_elem, id); - // add the element if it does not exist - UgElem *c_elem = ug_cache_search(&ctx->cache, id); - if (c_elem == NULL) { - UgElem elem = {0}; - uint32_t c_idx; - c_elem = ug_cache_insert(&ctx->cache, &elem, &c_idx); - is_new_elem = 1; - } + // add it to the tree + ug_tree_add(&ctx->tree, id, ctx->div_using); + print_tree(ctx); // take a reference to the parent // FIXME: if the tree held pointers to the elements then no more // redundant cache search - UgId parent_id = ctx->tree.vector[ctx->div_using]; + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); UgElem *parent = ug_cache_search(&ctx->cache, parent_id); if (parent == NULL) { // Error, did you forget to do frame_begin()? return -1; } + // 1. Fill the element fields + // this resets the flags + c_elem->type = ETYPE_BUTTON; + c_elem->flags = 0; + // if the element is new or the parent was updated then redo layout - if (is_new_elem || parent->flags & ELEM_UPDATED) { - c_elem->id = id; - c_elem->type = ETYPE_BUTTON; + if (is_new || parent->flags & ELEM_UPDATED) { + // 2. Layout + c_elem->rect = position_element(ctx, parent, size); - // 1. Select the right origin offset - switch (parent->div.layout) { - case DIV_LAYOUT_ROW: - c_elem->rect = (UgRect) { - .x = parent->div.origin_r.x + size.x, - .y = parent->div.origin_r.y + size.y, - }; - break; - case DIV_LAYOUT_COLUMN: - c_elem->rect = (UgRect) { - .x = parent->div.origin_c.x + size.x, - .y = parent->div.origin_c.y + size.y, - }; - break; - case DIV_LAYOUT_FLOATING: - c_elem->rect = (UgRect) { - .x = size.x, - .y = size.y, - }; - break; - default: - // Error - break; - } - - // 2. Calculate width & height - // TODO: what about negative values? - c_elem->rect.w = size.w != 0 ? size.w : parent->rect.w; - c_elem->rect.h = size.h != 0 ? size.h : parent->rect.h; - - /// Not a div, so not needed - // 3. Mark the element as updated - // 4. Set div information - - // 4. Update the origins of the parent - parent->div.origin_r = (UgPoint) { - .x = c_elem->rect.x + c_elem->rect.w, - .y = c_elem->rect.y, - }; - parent->div.origin_c = (UgPoint) { - .x = c_elem->rect.x, - .y = c_elem->rect.y + c_elem->rect.h, - }; + // 3. TODO: Fill the button specific fields } else { // TODO: Check for interactions } diff --git a/ugui.h b/ugui.h index 36739d2..4d91d68 100644 --- a/ugui.h +++ b/ugui.h @@ -28,23 +28,20 @@ enum UgElemFlags { }; typedef struct { - UgId id; - uint32_t flags; - UgRect rect; - - union { - uint32_t type_int; - UgElemType type; - }; + UgId id; + uint32_t flags; + UgRect rect; + UgElemType type; // type-specific fields union { - struct { + struct UgDiv { enum { DIV_LAYOUT_ROW = 0, DIV_LAYOUT_COLUMN, DIV_LAYOUT_FLOATING, } layout; + UgPoint origin_r, origin_c; UgColor color_bg; } div; // Div @@ -70,22 +67,23 @@ typedef struct { typedef struct _UgCtx UgCtx; // tree implementation -int ug_tree_init(UgTree *tree, unsigned int size); -int ug_tree_pack(UgTree *tree); -int ug_tree_resize(UgTree *tree, unsigned int newsize); -int ug_tree_add(UgTree *tree, UgId elem, int parent); -int ug_tree_prune(UgTree *tree, int ref); -int ug_tree_subtree_size(UgTree *tree, int ref); -int ug_tree_children_it(UgTree *tree, int parent, int *cursor); -int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); -int ug_tree_parentof(UgTree *tree, int node); -int ug_tree_destroy(UgTree *tree); +int ug_tree_init(UgTree *tree, unsigned int size); +int ug_tree_pack(UgTree *tree); +int ug_tree_resize(UgTree *tree, unsigned int newsize); +int ug_tree_add(UgTree *tree, UgId elem, int parent); +int ug_tree_prune(UgTree *tree, int ref); +int ug_tree_subtree_size(UgTree *tree, int ref); +int ug_tree_children_it(UgTree *tree, int parent, int *cursor); +int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); +int ug_tree_parentof(UgTree *tree, int node); +int ug_tree_destroy(UgTree *tree); +UgId ug_tree_get(UgTree *tree, int node); // cache implementation UgElemCache ug_cache_init(void); void ug_cache_free(UgElemCache *cache); UgElem *ug_cache_search(UgElemCache *cache, UgId id); -UgElem *ug_cache_insert(UgElemCache *cache, const UgElem *g, uint32_t *index); +UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index); int ug_init(UgCtx *ctx); int ug_destroy(UgCtx *ctx); diff --git a/vectree.c b/vectree.c index 5bd7bb1..ab58cd5 100644 --- a/vectree.c +++ b/vectree.c @@ -128,7 +128,7 @@ int ug_tree_resize(UgTree *tree, unsigned int newsize) return -1; } - int *neworrefs = realloc(tree->ordered_refs, (newsize + 1)* sizeof(int)); + int *neworrefs = realloc(tree->ordered_refs, (newsize + 1) * sizeof(int)); if (neworrefs == NULL) { return -1; } @@ -319,7 +319,7 @@ int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) } while (IS_VALID_REF(tree, ref)); // This line is why tree->ordered_refs has to be size+1 - queue[off+1] = -1; + queue[off + 1] = -1; } // PRINT_ARR(queue, tree->size); @@ -345,3 +345,11 @@ int ug_tree_parentof(UgTree *tree, int node) } return tree->refs[node]; } + +UgId ug_tree_get(UgTree *tree, int node) +{ + if (tree == NULL || !IS_VALID_REF(tree, node)) { + return 0; + } + return tree->vector[node]; +} From 305df93182e8a0a6651476e0e2f1beb5fba1bbe5 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 12 Jan 2024 19:59:04 +0100 Subject: [PATCH 010/250] more layouting --- ugui.c | 233 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 189 insertions(+), 44 deletions(-) diff --git a/ugui.c b/ugui.c index 3e57bf8..334568a 100644 --- a/ugui.c +++ b/ugui.c @@ -150,6 +150,10 @@ struct _UgCtx { int width, height; } size; + struct { + UgPoint margin; + } style; + // input structure, it describes the events received between frames struct { uint32_t flags; @@ -159,21 +163,22 @@ struct _UgCtx { }; // layouting -int ug_layout_row(void); -int ug_layout_column(void); -int ug_layout_floating(void); -int ug_layout_next_row(void); -int ug_layout_next_column(void); +int ug_layout_set_row(UgCtx *ctx); +int ug_layout_set_column(UgCtx *ctx); +int ug_layout_set_floating(UgCtx *ctx); +// these reset the offsets introduced by the previous elements +int ug_layout_next_row(UgCtx *ctx); +int ug_layout_next_column(UgCtx *ctx); -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); -int ug_div_end(UgCtx *ctx); -int ug_input_window_size(UgCtx *ctx, int width, int height); -UgId djb2(const char *str); -UgId FNV_1a(const char *str); +int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); +int ug_div_end(UgCtx *ctx); +int ug_input_window_size(UgCtx *ctx, int width, int height); +static UgId djb2(const char *str); +static UgId FNV_1a(const char *str); int ug_button(UgCtx *ctx, const char *label, UgRect size); -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect); +UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); int main(void) { @@ -201,21 +206,16 @@ int main(void) // main div, fill the whole window ug_div_begin(&ctx, "main", DIV_FILL); { + ug_layout_set_column(&ctx); ug_button( &ctx, - "batt342353453452wrwea", - (UgRect) {.y = 100, .x = 130, .w = 100, .h = 16} - ); - ug_button( - &ctx, - "arieasd3ree2234tast", - (UgRect) {.x = 10, .w = 30, .h = 30} - ); - ug_button( - &ctx, - "bughsdfsfdstton2", - (UgRect) {.x = 10, .w = 30, .h = 30} + "button0", + (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} ); + ug_layout_next_column(&ctx); + ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); + ug_layout_next_column(&ctx); + ug_button(&ctx, "button2", (UgRect) {.w = 30, .h = 30}); } ug_div_end(&ctx); @@ -271,7 +271,7 @@ int main(void) // search the element of the corresponding id in the cache, if no element is found // insert a new one of that id. Return the pointer to the element -int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) +static int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) { int is_new = 0; uint32_t cache_idx; @@ -287,6 +287,21 @@ int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) return is_new; } +static int get_parent(UgCtx *ctx, UgElem **parent) +{ + // take a reference to the parent + // FIXME: if the tree held pointers to the elements then no more + // redundant cache search + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + return 0; +} + int ug_init(UgCtx *ctx) { if (ctx == NULL) { @@ -300,6 +315,9 @@ int ug_init(UgCtx *ctx) ctx->layout = row; ctx->div_using = 0; + // TODO: add style config + ctx->style.margin = (UgPoint) {2, 2}; + return 0; } @@ -316,7 +334,7 @@ int ug_destroy(UgCtx *ctx) return 0; } -void print_tree(UgCtx *ctx) +static void print_tree(UgCtx *ctx) { printf("ctx->tree: ["); for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) { @@ -423,7 +441,7 @@ int ug_input_window_size(UgCtx *ctx, int width, int height) return 0; } -UgId FNV_1a(const char *str) +static UgId FNV_1a(const char *str) { const uint64_t fnv_off = 0xcbf29ce484222325; const uint64_t fnv_prime = 0x100000001b3; @@ -437,7 +455,7 @@ UgId FNV_1a(const char *str) return hash; } -UgId djb2(const char *str) +static UgId djb2(const char *str) { uint64_t hash = 5381; uint32_t c; @@ -468,13 +486,8 @@ int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) printf("Error adding to tree\n"); } - // take a reference to the parent - // FIXME: if the tree held pointers to the elements then no more - // redundant cache search - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? + UgElem *parent; + if (get_parent(ctx, &parent)) { return -1; } @@ -491,7 +504,7 @@ int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) // do layout and update flags only if the element was updated if (is_new || FTEST(parent, ELEM_UPDATED)) { // 2. layout the element - c_elem->rect = position_element(ctx, parent, div); + c_elem->rect = position_element(ctx, parent, div, 0); // 3. Mark the element as updated c_elem->flags |= ELEM_UPDATED; @@ -532,7 +545,7 @@ int ug_div_end(UgCtx *ctx) } // position the rectangle inside the parent according to the layout -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect) +UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) { UgRect elem_rect = {0}; UgPoint origin = {0}; @@ -571,6 +584,18 @@ UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect) .y = elem_rect.y + elem_rect.h, }; + // if using the style then apply margins + if (style && parent->div.layout != DIV_LAYOUT_FLOATING) { + elem_rect.x += ctx->style.margin.x; + elem_rect.y += ctx->style.margin.y; + + parent->div.origin_r.x += ctx->style.margin.x; + // parent->div.origin_r.y += ctx->style.margin.y; + + // parent->div.origin_c.x += ctx->style.margin.x; + parent->div.origin_c.y += ctx->style.margin.y; + } + /* printf( "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) -> {%d %d %d @@ -603,13 +628,8 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) ug_tree_add(&ctx->tree, id, ctx->div_using); print_tree(ctx); - // take a reference to the parent - // FIXME: if the tree held pointers to the elements then no more - // redundant cache search - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? + UgElem *parent; + if (get_parent(ctx, &parent)) { return -1; } @@ -621,7 +641,7 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) // if the element is new or the parent was updated then redo layout if (is_new || parent->flags & ELEM_UPDATED) { // 2. Layout - c_elem->rect = position_element(ctx, parent, size); + c_elem->rect = position_element(ctx, parent, size, 1); // 3. TODO: Fill the button specific fields } else { @@ -641,3 +661,128 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) return 0; } + +int ug_layout_set_row(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + if (parent->type != ETYPE_DIV) { + // what? + return -1; + } + + parent->div.layout = DIV_LAYOUT_ROW; + + return 0; +} + +int ug_layout_set_column(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + if (parent->type != ETYPE_DIV) { + // what? + return -1; + } + + parent->div.layout = DIV_LAYOUT_COLUMN; + + return 0; +} + +int ug_layout_set_floating(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + if (parent->type != ETYPE_DIV) { + // what? + return -1; + } + + parent->div.layout = DIV_LAYOUT_FLOATING; + + return 0; +} + +int ug_layout_next_row(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + if (parent->type != ETYPE_DIV) { + // what? + return -1; + } + + parent->div.origin_r = (UgPoint) { + .x = parent->rect.x, + .y = parent->div.origin_c.y, + }; + + parent->div.origin_c = parent->div.origin_r; + + return 0; +} + +int ug_layout_next_column(UgCtx *ctx) +{ + if (ctx == NULL) { + return -1; + } + + UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); + UgElem *parent = ug_cache_search(&ctx->cache, parent_id); + if (parent == NULL) { + // Error, did you forget to do frame_begin()? + return -1; + } + + if (parent->type != ETYPE_DIV) { + // what? + return -1; + } + + parent->div.origin_c = (UgPoint) { + .x = parent->div.origin_r.x, + .y = parent->rect.y, + }; + + parent->div.origin_r = parent->div.origin_c; + + return 0; +} From 97295df51683615f5e95d0bd14e98b4f98b2da90 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 12 Jan 2024 21:33:14 +0100 Subject: [PATCH 011/250] some style --- ugui.c | 38 +++++++++++++++++++++++++++++++------- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/ugui.c b/ugui.c index 334568a..10771de 100644 --- a/ugui.c +++ b/ugui.c @@ -150,8 +150,13 @@ struct _UgCtx { int width, height; } size; - struct { - UgPoint margin; + struct { // css box model + UgRect padding; + UgRect border; + UgRect margin; + UgColor bgcolor; // background color + UgColor fgcolor; // foreground color + UgColor bcolor; // border color } style; // input structure, it describes the events received between frames @@ -316,7 +321,13 @@ int ug_init(UgCtx *ctx) ctx->div_using = 0; // TODO: add style config - ctx->style.margin = (UgPoint) {2, 2}; + ctx->style.margin = (UgRect) {1, 1, 1, 1}; + ctx->style.padding = (UgRect) {0}; + ctx->style.border = (UgRect) {0}; + + ctx->style.bgcolor = (UgColor) {0}; + ctx->style.fgcolor = (UgColor) {0}; + ctx->style.bcolor = (UgColor) {0}; return 0; } @@ -585,15 +596,28 @@ UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) }; // if using the style then apply margins + // FIXME: this does not work if (style && parent->div.layout != DIV_LAYOUT_FLOATING) { elem_rect.x += ctx->style.margin.x; elem_rect.y += ctx->style.margin.y; - parent->div.origin_r.x += ctx->style.margin.x; - // parent->div.origin_r.y += ctx->style.margin.y; + // total keep-out borders + UgRect margin_tot = { + .x = ctx->style.padding.x + ctx->style.border.x + + ctx->style.margin.x, + .y = ctx->style.padding.y + ctx->style.border.y + + ctx->style.margin.y, + .w = ctx->style.padding.w + ctx->style.border.x + + ctx->style.margin.w, + .h = ctx->style.padding.h + ctx->style.border.x + + ctx->style.margin.h, + }; - // parent->div.origin_c.x += ctx->style.margin.x; - parent->div.origin_c.y += ctx->style.margin.y; + parent->div.origin_r.x += margin_tot.x + margin_tot.w; + // parent->div.origin_r.y += margin_tot.h; + + // parent->div.origin_c.x += margin_tot.w; + parent->div.origin_c.y += margin_tot.y + margin_tot.h; } /* From a4974c8df8dcf65a5a6c726df4ca09bf8e170fa4 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 5 Mar 2024 14:13:41 +0100 Subject: [PATCH 012/250] fm: initial commit --- fm/README | 1 + 1 file changed, 1 insertion(+) create mode 100644 fm/README diff --git a/fm/README b/fm/README new file mode 100644 index 0000000..3c09197 --- /dev/null +++ b/fm/README @@ -0,0 +1 @@ +File Manager using ugui From 3a8a55d17751a6bfb4b195ec700234da8140d143 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 10 Mar 2024 17:26:03 +0100 Subject: [PATCH 013/250] checkpoint --- Makefile | 4 +- fm/libconf.c | 193 +++++++++++++++++++++++++++++++++ fm/libconf.h | 7 ++ timer.c | 69 ++++++++++++ timer.h | 16 +++ ugui.c | 301 ++++++++++++++++++++++++++++++++++----------------- ugui.h | 3 +- 7 files changed, 493 insertions(+), 100 deletions(-) create mode 100644 fm/libconf.c create mode 100644 fm/libconf.h create mode 100644 timer.c create mode 100644 timer.h diff --git a/Makefile b/Makefile index 31a689a..0d82e80 100644 --- a/Makefile +++ b/Makefile @@ -4,10 +4,12 @@ LDFLAGS = -Lraylib/lib -lm all: ugui -ugui: ugui.o vectree.o cache.o raylib/lib/libraylib.a +ugui: ugui.o vectree.o cache.o timer.o raylib/lib/libraylib.a ugui.o: ugui.c ugui.h vectree.o: vectree.c ugui.h cache.o: cache.c ugui.h + +timer.o: timer.c timer.h diff --git a/fm/libconf.c b/fm/libconf.c new file mode 100644 index 0000000..07ac86e --- /dev/null +++ b/fm/libconf.c @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +#define PATHS_NO 3 + +static char temp[PATH_MAX] = {0}; +static int valid_paths = 0; +static char paths[PATHS_NO][PATH_MAX] = {0}; // 0: xdg, 1: fallback, 2: global + +static const mode_t CONFDIR_MODE = S_IRWXU | S_IRWXU | S_IRWXU | S_IRWXU; + +static void err(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + + fprintf(stderr, "[libconf]: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + + va_end(ap); +} + +// implements "mkdir -p" +static int mkdir_p(const char *path) +{ + char tmp_path[PATH_MAX] = {0}; + strncpy(tmp_path, path, PATH_MAX - 1); + struct stat st; + + for (char *tk = strtok(tmp_path, "/"); tk != NULL; tk = strtok(NULL, "/")) { + if (tk != tmp_path) { + tk[-1] = '/'; + } + + if (stat(tmp_path, &st)) { + if (errno != ENOENT) { + err("could not stat() %s: %s", + tmp_path, + strerror(errno)); + return 1; + } + + if (mkdir(tmp_path, CONFDIR_MODE)) { + err("could not mkdir() %s: %s", + tmp_path, + strerror(errno)); + return 1; + } + } else if (!S_ISDIR(st.st_mode)) { + err("could not create directory %s: file exists but it is " + "not a directory", + tmp_path); + return 1; + } + } + return 0; +} + +// TODO: add a way to add a custom directory to the paths, for example via an +// environment variable +// TODO: maybe add a LIBCONF_PATH that overrides the defaults +/* default paths are: + * 1. ${XDG_CONFIG_HOME}/libconf/appid.d/ + * 2. ${HOME}/.config/libconf/appid.d/ + * 3. etc/libconf/appid.d/ + */ +static int fill_paths(const char *id) +{ + // TODO: verify id + if (id == NULL) { + err("must provide a valid app id"); + return 1; + } + + const char *xdg = getenv("XDG_CONFIG_HOME"); + if (xdg != NULL) { + snprintf(paths[0], PATH_MAX, "%s/libconf/%s.d", xdg, id); + } + + const char *home = getenv("HOME"); + if (home) { + snprintf(temp, PATH_MAX, "%s/.config/libconf/%s.d", home, id); + + if (mkdir_p(temp) == 0) { + strcpy(paths[1], temp); + } + } + + // do not create global config path since most likely we don't have + // the necessary permissions to do so + snprintf(paths[2], PATH_MAX, "/etc/libconf/%s.d", id); + + for (size_t i = 0; i < PATHS_NO; i++) { + printf("paths[%ld] = %s\n", i, paths[i]); + } + + valid_paths = 1; + + return 0; +} + +// get config file path +const char *lcnf_get_conf_file(const char *id, const char *name, int global) +{ + static char str[PATH_MAX] = {0}; + + if (id == NULL) { + return NULL; + } + + if (!valid_paths) { + if (fill_paths(id)) { + return NULL; + } + } + + // quick path for global config file + if (global && paths[2][0] != '\0') { + snprintf(str, PATH_MAX, "%s/%s", paths[2], name); + return str; + } + + int found = 0; + for (size_t i = 0; i < PATHS_NO; i++) { + if (paths[i][0] == '\0') { + continue; + } + + struct stat st; + snprintf(str, PATH_MAX, "%s/%s", paths[i], name); + + if (stat(str, &st) && errno != ENOENT) { + err("could not stat %s: %s", str, strerror(errno)); + } + + if (S_ISREG(st.st_mode)) { + found = 1; + break; + } + } + + return found ? str : NULL; +} + +// get config file directory path +const char *lcnf_get_conf_dir(const char *id, int global) +{ + if (id == NULL) { + return NULL; + } + + if (global) { + return paths[2][0] != '\0' ? paths[2] : NULL; + } + + if (paths[0][0] != '\0') { + return paths[0]; + } else if (paths[1][0] != '\0') { + return paths[1]; + } else { + return NULL; + } +} + +// TODO: watch directory for file updates +int lcnf_watch_conf_dir(const char *id, int global); + +// TODO: collect all files into a temporary one, useful for when you have multiple +// configuration files like 00-foo.conf, 01-bar.conf and 99-baz.conf +const char *lcnf_collect_conf_files(const char *id, int global); + +#if 1 + +int main(void) +{ + const char *p = lcnf_get_conf_file("pippo", "pippo.toml", 0); + if (p) { + printf("config found: %s\n", p); + } else { + printf("config not found\n"); + } + return 0; +} + +#endif diff --git a/fm/libconf.h b/fm/libconf.h new file mode 100644 index 0000000..f92c29b --- /dev/null +++ b/fm/libconf.h @@ -0,0 +1,7 @@ +#ifndef LIBCONF_H_ +#define LIBCONF_H_ + +const char *lcnf_get_conf_file(const char *id, const char *name, int global); +const char *lcnf_get_conf_dir(const char *id, int global); + +#endif diff --git a/timer.c b/timer.c new file mode 100644 index 0000000..03b7a4c --- /dev/null +++ b/timer.c @@ -0,0 +1,69 @@ +#define _POSIX_C_SOURCE 200809l + +#include + +#include "timer.h" + +const clockid_t CLOCK_ID = CLOCK_PROCESS_CPUTIME_ID; + +struct _timer { + struct timespec start, stop; + struct timespec times[TIMER_MAX_PARTIAL]; +} timer = {0}; + +int timer_start(void) { return clock_gettime(CLOCK_ID, &timer.start); } + +int timer_reset(void) +{ + timer = (struct _timer) {0}; + return 0; +} + +int timer_stop(void) { return clock_gettime(CLOCK_ID, &timer.stop); } + +// partial clocks also update the stop time +int timer_partial(int idx) +{ + if (idx > TIMER_MAX_PARTIAL || idx < 0) { + return -1; + } + clock_gettime(CLOCK_ID, &timer.stop); + return clock_gettime(CLOCK_ID, &(timer.times[idx])); +} + +size_t timer_get_us(int idx) +{ + if (idx > TIMER_MAX_PARTIAL) { + return -1; + } + + struct timespec ts = {0}; + if (idx < 0) { + ts = timer.stop; + } else { + ts = timer.times[idx]; + } + ts.tv_sec -= timer.start.tv_sec; + ts.tv_nsec -= timer.start.tv_nsec; + + // FIXME: check overflow + return (ts.tv_nsec / 1000) + (ts.tv_sec * 1000000); +} + +double timer_get_sec(int idx) +{ + if (idx > TIMER_MAX_PARTIAL) { + return -1; + } + + struct timespec ts = {0}; + if (idx < 0) { + ts = timer.stop; + } else { + ts = timer.times[idx]; + } + ts.tv_sec -= timer.start.tv_sec; + ts.tv_nsec -= timer.start.tv_nsec; + + return (double)ts.tv_sec + ((double)ts.tv_nsec / 1e9); +} diff --git a/timer.h b/timer.h new file mode 100644 index 0000000..94e546b --- /dev/null +++ b/timer.h @@ -0,0 +1,16 @@ +#ifndef UG_TIMER_H_ +#define UG_TIMER_H_ + +#include + +#define TIMER_MAX_PARTIAL 10 + +int timer_start(void); +int timer_stop(void); +int timer_reset(void); +int timer_partial(int idx); + +size_t timer_get_us(int idx); +double timer_get_sec(int idx); + +#endif diff --git a/ugui.c b/ugui.c index 10771de..fb7cd88 100644 --- a/ugui.c +++ b/ugui.c @@ -7,6 +7,7 @@ #include "raylib.h" #include "ugui.h" +#include "timer.h" #define RGBA(u) \ (UgColor) \ @@ -22,7 +23,11 @@ printf("lmao\n"); \ } while (0) -#define FTEST(e, f) ((e)->flags & (f)) +#define FTEST(e, f) ((e)->flags & (f)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define ABS(a) ((a) < 0 ? -(a) : (a)) +#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) #define STACK_STEP 10 #define MAX_ELEMS 128 @@ -132,7 +137,10 @@ int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd) } enum InputEventFlags { - INPUT_CTX_SIZE = 1 << 0, // window size was changed + INPUT_CTX_SIZE = 1 << 0, // window size was changed + INPUT_CTX_FOCUS = 1 << 1, // window focus changed + INPUT_CTX_MOUSEMOVE = 1 << 2, // mouse was moved + INPUT_CTX_MOUSEBTN = 1 << 3, // mouse button pressed or released }; struct _UgCtx { @@ -161,7 +169,16 @@ struct _UgCtx { // input structure, it describes the events received between frames struct { + // flags: lists which input events have been received uint32_t flags; + int has_focus; + int mouse_x, mouse_y, mdelta_x, mdelta_y; + // mouse_down: bitmap of mouse buttons that are held + // mouse_updated: bitmap of mouse buttons that have been updated + // mouse_released = mouse_updated & ~mouse_down + // mouse_pressed = mouse_updated & mouse_down + uint32_t mouse_down; + uint32_t mouse_updated; } input; int div_using; // tree node indicating the current active div @@ -175,104 +192,16 @@ int ug_layout_set_floating(UgCtx *ctx); int ug_layout_next_row(UgCtx *ctx); int ug_layout_next_column(UgCtx *ctx); -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); -int ug_div_end(UgCtx *ctx); -int ug_input_window_size(UgCtx *ctx, int width, int height); -static UgId djb2(const char *str); +int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); +int ug_div_end(UgCtx *ctx); +int ug_input_window_size(UgCtx *ctx, int width, int height); + +// static UgId djb2(const char *str); static UgId FNV_1a(const char *str); int ug_button(UgCtx *ctx, const char *label, UgRect size); -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); - -int main(void) -{ - UgCtx ctx; - ug_init(&ctx); - - int width = 800, height = 450; - SetConfigFlags(FLAG_WINDOW_RESIZABLE); - InitWindow(width, height, "Ugui Test"); - ug_input_window_size(&ctx, width, height); - - // Main loop - while (!WindowShouldClose()) { - - // Input handling - if (IsWindowResized()) { - width = GetScreenWidth(); - height = GetScreenHeight(); - ug_input_window_size(&ctx, width, height); - } - - // UI handling - ug_frame_begin(&ctx); - - // main div, fill the whole window - ug_div_begin(&ctx, "main", DIV_FILL); - { - ug_layout_set_column(&ctx); - ug_button( - &ctx, - "button0", - (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} - ); - ug_layout_next_column(&ctx); - ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); - ug_layout_next_column(&ctx); - ug_button(&ctx, "button2", (UgRect) {.w = 30, .h = 30}); - } - ug_div_end(&ctx); - - ug_frame_end(&ctx); - - // drawing - BeginDrawing(); - // ClearBackground(BLACK); - - printf("----- Draw Begin -----\n"); - Color c; - for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { - switch (cmd.type) { - case CMD_RECT: - printf( - "draw rect x=%d y=%d w=%d h=%d\n", - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h - ); - c = (Color) { - .r = cmd.rect.color.r, - .g = cmd.rect.color.g, - .b = cmd.rect.color.b, - .a = cmd.rect.color.a, - }; - DrawRectangle( - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h, - c - ); - break; - default: - printf("Unknown cmd type: %d\n", cmd.type); - break; - } - } - printf("----- Draw End -----\n\n"); - - EndDrawing(); - - WaitTime(0.2); - } - - CloseWindow(); - - ug_destroy(&ctx); - return 0; -} +static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); // search the element of the corresponding id in the cache, if no element is found // insert a new one of that id. Return the pointer to the element @@ -385,6 +314,7 @@ int ug_frame_begin(UgCtx *ctx) .color_bg = {0}, }, }; + // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree @@ -392,13 +322,22 @@ int ug_frame_begin(UgCtx *ctx) root.flags |= ELEM_UPDATED; } + // if the window has focus then the root element also has focus, no other + // computation needed, child elements need to check the mouse positon and + // other stuff + if (ctx->input.has_focus) { + root.flags |= ELEM_HASFOCUS; + } + // FIXME: check errors + // 2. Get the root element from the cache and update it UgElem *c_elem; int is_new = search_or_insert(ctx, &c_elem, root.id); if (is_new || FTEST(&root, ELEM_UPDATED)) { *c_elem = root; } + // 3. Push the root element into the element tree ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0); if (ctx->div_using < 0) { @@ -432,6 +371,7 @@ int ug_frame_end(UgCtx *ctx) return 0; } +// Window size was changed int ug_input_window_size(UgCtx *ctx, int width, int height) { if (ctx == NULL) { @@ -452,6 +392,48 @@ int ug_input_window_size(UgCtx *ctx, int width, int height) return 0; } +// Window gained/lost focus +int ug_input_changefoucs(UgCtx *ctx, int has_focus) +{ + if (ctx == NULL) { + return -1; + } + + // FIXME: raylib only has an API to query the focus status so we have to + // update the input flag only if the focus changed + if (ctx->input.has_focus != (!!has_focus)) { + ctx->input.flags |= INPUT_CTX_FOCUS; + } + ctx->input.has_focus = !!has_focus; + + return 0; +} + +// Mouse was moved, report absolute position +int ug_input_mouse_abs(UgCtx *ctx, int x, int y) { return 1; } + +// Mouse was moved, report relative motion +int ug_input_mouse_delta(UgCtx *ctx, int dx, int dy) +{ + if (ctx == NULL) { + return -1; + } + + ctx->input.mdelta_x = dx; + ctx->input.mdelta_y = dy; + + int mx, my; + mx = ctx->input.mouse_x + dx; + my = ctx->input.mouse_y + dy; + + ctx->input.mouse_x = CLAMP(mx, 0, ctx->size.width); + ctx->input.mouse_y = CLAMP(my, 0, ctx->size.height); + + ctx->input.flags |= INPUT_CTX_MOUSEMOVE; + + return 0; +} + static UgId FNV_1a(const char *str) { const uint64_t fnv_off = 0xcbf29ce484222325; @@ -466,6 +448,7 @@ static UgId FNV_1a(const char *str) return hash; } +#if 0 static UgId djb2(const char *str) { uint64_t hash = 5381; @@ -477,6 +460,7 @@ static UgId djb2(const char *str) return hash; } +#endif int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) { @@ -556,7 +540,7 @@ int ug_div_end(UgCtx *ctx) } // position the rectangle inside the parent according to the layout -UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) +static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) { UgRect elem_rect = {0}; UgPoint origin = {0}; @@ -663,7 +647,7 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) c_elem->flags = 0; // if the element is new or the parent was updated then redo layout - if (is_new || parent->flags & ELEM_UPDATED) { + if (is_new || FTEST(parent, ELEM_UPDATED)) { // 2. Layout c_elem->rect = position_element(ctx, parent, size, 1); @@ -810,3 +794,124 @@ int ug_layout_next_column(UgCtx *ctx) return 0; } + +#if 1 +int main(void) +{ + UgCtx ctx; + ug_init(&ctx); + + int width = 800, height = 450; + SetConfigFlags(FLAG_WINDOW_RESIZABLE); + InitWindow(width, height, "Ugui Test"); + ug_input_window_size(&ctx, width, height); + + // Main loop + while (!WindowShouldClose()) { + const int PARTIAL_INPUT = 0; + const int PARTIAL_LAYOUT = 1; + const int PARTIAL_DRAW = 2; + timer_start(); + + /*** Start Input Handling ***/ + if (IsWindowResized()) { + width = GetScreenWidth(); + height = GetScreenHeight(); + ug_input_window_size(&ctx, width, height); + } + + ug_input_changefoucs(&ctx, IsWindowFocused()); + + // FIXME: In raylib it doesn't seem to be a quick way to check if + // a mouse input event was received, so for now just use + // the delta information + Vector2 mousedelta = GetMouseDelta(); + if (mousedelta.x || mousedelta.y) { + ug_input_mouse_delta(&ctx, mousedelta.x, mousedelta.y); + } + timer_partial(PARTIAL_INPUT); + /*** End Input Handling ***/ + + /*** Start UI Handling ***/ + ug_frame_begin(&ctx); + + // main div, fill the whole window + ug_div_begin(&ctx, "main", DIV_FILL); + { + ug_layout_set_column(&ctx); + ug_button( + &ctx, + "button0", + (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} + ); + ug_layout_next_column(&ctx); + ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); + ug_layout_next_column(&ctx); + ug_button(&ctx, "button2", (UgRect) {.w = 30, .h = 30}); + } + ug_div_end(&ctx); + + ug_frame_end(&ctx); + timer_partial(PARTIAL_LAYOUT); + /*** End UI Handling ***/ + + /*** Start UI Drawing ***/ + BeginDrawing(); + // ClearBackground(BLACK); + + printf("----- Draw Begin -----\n"); + Color c; + for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { + switch (cmd.type) { + case CMD_RECT: + printf( + "draw rect x=%d y=%d w=%d h=%d\n", + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h + ); + c = (Color) { + .r = cmd.rect.color.r, + .g = cmd.rect.color.g, + .b = cmd.rect.color.b, + .a = cmd.rect.color.a, + }; + DrawRectangle( + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h, + c + ); + break; + default: + printf("Unknown cmd type: %d\n", cmd.type); + break; + } + } + printf("----- Draw End -----\n\n"); + + EndDrawing(); + timer_partial(PARTIAL_DRAW); + timer_stop(); + /*** End UI Drawing ***/ + + printf("input time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_INPUT)); + printf("layout time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_LAYOUT)); + printf("draw time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_DRAW)); + printf("total time: %lfms\n", 1e3 * timer_get_sec(-1)); + + // Throttle Frames + // TODO: add an fps limit, time frame generation and log it + const float TARGET_FPS = 10; + float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); + WaitTime(wait_time); + } + + CloseWindow(); + + ug_destroy(&ctx); + return 0; +} +#endif diff --git a/ugui.h b/ugui.h index 4d91d68..05819fd 100644 --- a/ugui.h +++ b/ugui.h @@ -24,7 +24,8 @@ typedef enum { } UgElemType; enum UgElemFlags { - ELEM_UPDATED = 1 << 0, + ELEM_UPDATED = 1 << 0, + ELEM_HASFOCUS = 1 << 1, }; typedef struct { From 5427b191c2ca4a8fed1c880c3012c20dd2480aae Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 15 Mar 2024 22:49:07 +0100 Subject: [PATCH 014/250] mouse input --- ugui.c | 88 ++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 71 insertions(+), 17 deletions(-) diff --git a/ugui.c b/ugui.c index fb7cd88..50eab41 100644 --- a/ugui.c +++ b/ugui.c @@ -172,7 +172,7 @@ struct _UgCtx { // flags: lists which input events have been received uint32_t flags; int has_focus; - int mouse_x, mouse_y, mdelta_x, mdelta_y; + UgPoint mouse_pos, mouse_delta; // mouse_down: bitmap of mouse buttons that are held // mouse_updated: bitmap of mouse buttons that have been updated // mouse_released = mouse_updated & ~mouse_down @@ -221,6 +221,14 @@ static int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) return is_new; } +static int is_point_in_rect(UgPoint p, UgRect r) +{ + if ((p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h)) { + return 1; + } + return 0; +} + static int get_parent(UgCtx *ctx, UgElem **parent) { // take a reference to the parent @@ -242,6 +250,8 @@ int ug_init(UgCtx *ctx) return -1; } + *ctx = (UgCtx) {0}; + ug_tree_init(&ctx->tree, MAX_ELEMS); ug_fifo_init(&ctx->fifo, MAX_CMDS); @@ -389,6 +399,8 @@ int ug_input_window_size(UgCtx *ctx, int width, int height) ctx->size.height = height; } + ctx->input.flags |= INPUT_CTX_SIZE; + return 0; } @@ -419,15 +431,15 @@ int ug_input_mouse_delta(UgCtx *ctx, int dx, int dy) return -1; } - ctx->input.mdelta_x = dx; - ctx->input.mdelta_y = dy; + ctx->input.mouse_delta.x = dx; + ctx->input.mouse_delta.y = dy; int mx, my; - mx = ctx->input.mouse_x + dx; - my = ctx->input.mouse_y + dy; + mx = ctx->input.mouse_pos.x + dx; + my = ctx->input.mouse_pos.y + dy; - ctx->input.mouse_x = CLAMP(mx, 0, ctx->size.width); - ctx->input.mouse_y = CLAMP(my, 0, ctx->size.height); + ctx->input.mouse_pos.x = CLAMP(mx, 0, ctx->size.width); + ctx->input.mouse_pos.y = CLAMP(my, 0, ctx->size.height); ctx->input.flags |= INPUT_CTX_MOUSEMOVE; @@ -512,10 +524,14 @@ int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) }; c_elem->div.color_bg = RGBA(0xff0000ff); c_elem->div.origin_r = c_elem->div.origin_c; + } else if (FTEST(parent, ELEM_HASFOCUS)) { + if (is_point_in_rect(ctx->input.mouse_pos, c_elem->rect)) { + c_elem->flags |= ELEM_HASFOCUS; + } } else { // TODO: check active - // TODO: check resizeable // TODO: check scrollbars + // TODO: check resizeable } // Add the background to the draw stack @@ -643,15 +659,21 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) // 1. Fill the element fields // this resets the flags - c_elem->type = ETYPE_BUTTON; - c_elem->flags = 0; + c_elem->type = ETYPE_BUTTON; + c_elem->flags = 0; + UgColor bg_color = RGBA(0x0000ffff); // if the element is new or the parent was updated then redo layout if (is_new || FTEST(parent, ELEM_UPDATED)) { // 2. Layout c_elem->rect = position_element(ctx, parent, size, 1); - // 3. TODO: Fill the button specific fields + // TODO: 3. Fill the button specific fields + } else if (FTEST(parent, ELEM_HASFOCUS)) { + if (is_point_in_rect(ctx->input.mouse_pos, c_elem->rect)) { + c_elem->flags |= ELEM_HASFOCUS; + bg_color = RGBA(0x00ff00ff); + } } else { // TODO: Check for interactions } @@ -662,7 +684,7 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) .rect = { .rect = c_elem->rect, - .color = RGBA(0x0000ffff), + .color = bg_color, }, }; ug_fifo_enqueue(&ctx->fifo, &cmd); @@ -806,6 +828,10 @@ int main(void) InitWindow(width, height, "Ugui Test"); ug_input_window_size(&ctx, width, height); + double median_times[10][4] = {0}; + size_t frame = 0; + double median_input, median_layout, median_draw, median_tot; + // Main loop while (!WindowShouldClose()) { const int PARTIAL_INPUT = 0; @@ -897,14 +923,42 @@ int main(void) timer_stop(); /*** End UI Drawing ***/ - printf("input time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_INPUT)); - printf("layout time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_LAYOUT)); - printf("draw time: %lfms\n", 1e3 * timer_get_sec(PARTIAL_DRAW)); - printf("total time: %lfms\n", 1e3 * timer_get_sec(-1)); + median_times[frame][PARTIAL_INPUT] = + 1e3 * timer_get_sec(PARTIAL_INPUT); + median_times[frame][PARTIAL_LAYOUT] = + 1e3 * timer_get_sec(PARTIAL_LAYOUT); + median_times[frame][PARTIAL_DRAW] = + 1e3 * timer_get_sec(PARTIAL_DRAW); + median_times[frame][3] = 1e3 * timer_get_sec(-1); + + frame += 1; + frame %= 10; + + if (frame == 0) { + median_input = 0; + median_layout = 0; + median_draw = 0; + median_tot = 0; + for (size_t i = 0; i < 10; i++) { + median_input += median_times[i][PARTIAL_INPUT]; + median_layout += median_times[i][PARTIAL_LAYOUT]; + median_draw += median_times[i][PARTIAL_DRAW]; + median_tot += median_times[i][3]; + } + median_input /= 10; + median_layout /= 10; + median_draw /= 10; + median_tot /= 10; + } + + printf("input time: %lfms\n", median_input); + printf("layout time: %lfms\n", median_layout); + printf("draw time: %lfms\n", median_draw); + printf("total time: %lfms\n", median_tot); // Throttle Frames // TODO: add an fps limit, time frame generation and log it - const float TARGET_FPS = 10; + const float TARGET_FPS = 100; float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); WaitTime(wait_time); } From a374a37971aeafb9351f41cd5e335da5d050c46b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 15 Mar 2024 23:30:02 +0100 Subject: [PATCH 015/250] mouse buttons --- ugui.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/ugui.c b/ugui.c index 50eab41..fe53db9 100644 --- a/ugui.c +++ b/ugui.c @@ -20,9 +20,10 @@ #define MARK() \ do { \ - printf("lmao\n"); \ + printf("================== lmao ==================\n"); \ } while (0) +#define BIT(x) (1 << (x)) #define FTEST(e, f) ((e)->flags & (f)) #define MAX(a, b) ((a) > (b) ? (a) : (b)) #define MIN(a, b) ((a) < (b) ? (a) : (b)) @@ -343,6 +344,10 @@ int ug_frame_begin(UgCtx *ctx) // 2. Get the root element from the cache and update it UgElem *c_elem; int is_new = search_or_insert(ctx, &c_elem, root.id); + + // flags always need to be set to the new flags + c_elem->flags = root.flags; + if (is_new || FTEST(&root, ELEM_UPDATED)) { *c_elem = root; } @@ -377,6 +382,25 @@ int ug_frame_end(UgCtx *ctx) // 2. clear input fields ctx->input.flags = 0; +// draw mouse position +#if 1 + UgCmd cmd = { + .type = CMD_RECT, + .rect = + { + .rect = + { + .x = ctx->input.mouse_pos.x - 2, + .y = ctx->input.mouse_pos.y - 2, + .w = 4, + .h = 4, + }, + .color = RGBA(0xff00ffff), + }, + }; + ug_fifo_enqueue(&(ctx->fifo), &cmd); +#endif + printf("##### Frame End #####\n\n"); return 0; } @@ -421,6 +445,43 @@ int ug_input_changefoucs(UgCtx *ctx, int has_focus) return 0; } +enum ug_mouse_buttons { + UG_BTN_LEFT = BIT(0), + UG_BTN_MIDDLE = BIT(1), + UG_BTN_RIGHT = BIT(2), + UG_BTN_4 = BIT(3), + UG_BTN_5 = BIT(4), +}; + +const uint32_t mouse_supported_mask = + UG_BTN_LEFT | UG_BTN_MIDDLE | UG_BTN_RIGHT | UG_BTN_4 | UG_BTN_5; + +// Mouse Button moved +int ug_input_mouse_button(UgCtx *ctx, uint32_t button_mask) +{ + if (ctx == NULL) { + return -1; + } + + if (button_mask & ~mouse_supported_mask) { + return -1; + } + + ctx->input.mouse_updated = ctx->input.mouse_down ^ button_mask; + ctx->input.mouse_down = button_mask; + ctx->input.flags |= INPUT_CTX_MOUSEBTN; + + printf( + "Mouse Down: %s%s%s%s%s\n", + button_mask & UG_BTN_LEFT ? "BTN_LEFT " : "", + button_mask & UG_BTN_RIGHT ? "BTN_RIGHT " : "", + button_mask & UG_BTN_MIDDLE ? "BTN_MIDDLE " : "", + button_mask & UG_BTN_4 ? "BTN_4 " : "", + button_mask & UG_BTN_5 ? "BTN_5 " : "" + ); + return 0; +} + // Mouse was moved, report absolute position int ug_input_mouse_abs(UgCtx *ctx, int x, int y) { return 1; } @@ -855,6 +916,13 @@ int main(void) if (mousedelta.x || mousedelta.y) { ug_input_mouse_delta(&ctx, mousedelta.x, mousedelta.y); } + + uint32_t mask = 0; + mask |= IsMouseButtonDown(MOUSE_BUTTON_LEFT) ? UG_BTN_LEFT : 0; + mask |= IsMouseButtonDown(MOUSE_BUTTON_RIGHT) ? UG_BTN_RIGHT : 0; + mask |= IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) ? UG_BTN_MIDDLE : 0; + ug_input_mouse_button(&ctx, mask); + timer_partial(PARTIAL_INPUT); /*** End Input Handling ***/ From 2dcc1b582c270ee6a6049de1814d3d71ae800534 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 17 Mar 2024 23:34:45 +0100 Subject: [PATCH 016/250] started working on events --- ugui.c | 42 ++++++++++++++++++++++++++++++++---------- ugui.h | 11 +++++++++++ 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/ugui.c b/ugui.c index fe53db9..7f08025 100644 --- a/ugui.c +++ b/ugui.c @@ -316,6 +316,7 @@ int ug_frame_begin(UgCtx *ctx) .id = ROOT_ID, .type = ETYPE_DIV, .flags = 0, + .event = 0, .rect = space, .div = { @@ -697,6 +698,14 @@ static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int styl return elem_rect; } +int is_hovered(UgCtx *ctx, UgElem *elem) +{ + if (ctx == NULL || elem == NULL) { + return 0; + } + return is_point_in_rect(ctx->input.mouse_pos, elem->rect); +} + int ug_button(UgCtx *ctx, const char *label, UgRect size) { if (ctx == NULL || label == NULL) { @@ -730,13 +739,23 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) c_elem->rect = position_element(ctx, parent, size, 1); // TODO: 3. Fill the button specific fields - } else if (FTEST(parent, ELEM_HASFOCUS)) { - if (is_point_in_rect(ctx->input.mouse_pos, c_elem->rect)) { + } + + // TODO: Check for interactions + if (FTEST(parent, ELEM_HASFOCUS)) { + if (is_hovered(ctx, c_elem)) { c_elem->flags |= ELEM_HASFOCUS; + c_elem->event |= EVENT_MOUSE_HOVER; bg_color = RGBA(0x00ff00ff); + + if (ctx->input.mouse_down & UG_BTN_LEFT) { + c_elem->event |= EVENT_MOUSE_HOLD; + } else { + c_elem->event &= ~EVENT_MOUSE_HOLD; + } + } else { + c_elem->event &= ~EVENT_MOUSE_HOVER; } - } else { - // TODO: Check for interactions } // Draw the button @@ -750,7 +769,7 @@ int ug_button(UgCtx *ctx, const char *label, UgRect size) }; ug_fifo_enqueue(&ctx->fifo, &cmd); - return 0; + return c_elem->event; } int ug_layout_set_row(UgCtx *ctx) @@ -933,11 +952,14 @@ int main(void) ug_div_begin(&ctx, "main", DIV_FILL); { ug_layout_set_column(&ctx); - ug_button( - &ctx, - "button0", - (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} - ); + if (ug_button( + &ctx, + "button0", + (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} + ) & + EVENT_MOUSE_HOLD) { + printf("HOLDING button0\n"); + } ug_layout_next_column(&ctx); ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); ug_layout_next_column(&ctx); diff --git a/ugui.h b/ugui.h index 05819fd..c2ae79f 100644 --- a/ugui.h +++ b/ugui.h @@ -28,9 +28,20 @@ enum UgElemFlags { ELEM_HASFOCUS = 1 << 1, }; +enum UgElemEvent { + EVENT_KEY_PRESS = 1 << 0, + EVENT_KEY_RELEASE = 1 << 1, + EVENT_KEY_HOLD = 1 << 2, + EVENT_MOUSE_HOVER = 1 << 3, + EVENT_MOUSE_PRESS = 1 << 4, + EVENT_MOUSE_RELEASE = 1 << 5, + EVENT_MOUSE_HOLD = 1 << 6, +}; + typedef struct { UgId id; uint32_t flags; + uint32_t event; UgRect rect; UgElemType type; From 39e78ea0783fd0ea284f47c989645e19ac3d7138 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 29 Oct 2024 22:45:47 +0100 Subject: [PATCH 017/250] ported rewrite2 to c3 --- .clang-format | 41 -- .gitignore | 8 +- .gitmodules | 3 + LICENSE | 0 Makefile | 15 - README.md | 0 cache.c | 211 -------- compile_flags.txt | 5 - docs/.gitkeep | 0 fm/README | 1 - fm/libconf.c | 193 -------- fm/libconf.h | 7 - get_raylib.sh | 8 - lib/.gitkeep | 0 lib/raylib.c3l | 1 + project.json | 56 +++ resources/.gitkeep | 0 scripts/.gitkeep | 0 src/.gitkeep | 0 src/cache.c3 | 133 ++++++ src/fifo.c3 | 49 ++ src/main.c3 | 196 ++++++++ src/ugui_data.c3 | 184 +++++++ src/ugui_impl.c3 | 244 ++++++++++ src/ugui_input.c3 | 78 +++ src/ugui_layout.c3 | 153 ++++++ src/vtree.c3 | 333 +++++++++++++ stuff/generic_hash.h | 137 ------ stuff/generic_stack.h | 118 ----- stuff/main.c | 214 --------- stuff/vectree.h | 348 -------------- test/.gitkeep | 0 test/test_bitsruct.c3 | 14 + test/test_union.c3 | 26 + test/test_vtree.c3 | 7 + timer.c | 69 --- timer.h | 16 - ugui.c | 1061 ----------------------------------------- ugui.h | 105 ---- vectree.c | 355 -------------- 40 files changed, 1478 insertions(+), 2911 deletions(-) delete mode 100644 .clang-format create mode 100644 .gitmodules create mode 100644 LICENSE delete mode 100644 Makefile create mode 100644 README.md delete mode 100644 cache.c delete mode 100644 compile_flags.txt create mode 100644 docs/.gitkeep delete mode 100644 fm/README delete mode 100644 fm/libconf.c delete mode 100644 fm/libconf.h delete mode 100755 get_raylib.sh create mode 100644 lib/.gitkeep create mode 160000 lib/raylib.c3l create mode 100644 project.json create mode 100644 resources/.gitkeep create mode 100644 scripts/.gitkeep create mode 100644 src/.gitkeep create mode 100644 src/cache.c3 create mode 100644 src/fifo.c3 create mode 100644 src/main.c3 create mode 100644 src/ugui_data.c3 create mode 100644 src/ugui_impl.c3 create mode 100644 src/ugui_input.c3 create mode 100644 src/ugui_layout.c3 create mode 100644 src/vtree.c3 delete mode 100644 stuff/generic_hash.h delete mode 100644 stuff/generic_stack.h delete mode 100755 stuff/main.c delete mode 100644 stuff/vectree.h create mode 100644 test/.gitkeep create mode 100644 test/test_bitsruct.c3 create mode 100644 test/test_union.c3 create mode 100644 test/test_vtree.c3 delete mode 100644 timer.c delete mode 100644 timer.h delete mode 100644 ugui.c delete mode 100644 ugui.h delete mode 100644 vectree.c diff --git a/.clang-format b/.clang-format deleted file mode 100644 index b6d0dcb..0000000 --- a/.clang-format +++ /dev/null @@ -1,41 +0,0 @@ -# linux kernel style formatting -BasedOnStyle: LLVM -IndentWidth: 8 -UseTab: AlignWithSpaces - -BreakBeforeBraces: Linux -AllowShortIfStatementsOnASingleLine: false -IndentCaseLabels: false -ColumnLimit: 85 - -InsertBraces: true -SortIncludes: Never -BinPackParameters: false -BinPackArguments: false -Cpp11BracedListStyle: true -SpaceBeforeCpp11BracedList: true -SeparateDefinitionBlocks: Always -AlignAfterOpenBracket: BlockIndent -InsertNewlineAtEOF: true - -AlignConsecutiveDeclarations: - Enabled: true - AcrossEmptyLines: false - AcrossComments: false - AlignCompound: true - PadOperators: false - -AlignConsecutiveMacros: - Enabled: true - AcrossEmptyLines: false - AcrossComments: true - -AlignConsecutiveBitFields: - Enabled: true - AcrossEmptyLines: false - AcrossComments: true - -AlignConsecutiveAssignments: - Enabled: true - AcrossEmptyLines: false - AcrossComments: true diff --git a/.gitignore b/.gitignore index 15fae14..228cff3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,2 @@ -microgui -ugui *.o -test/test -**/compile_commands.json -**/.cache -test -raylib/* +build/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0bc165c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/raylib.c3l"] + path = lib/raylib.c3l + url = https://github.com/NexushasTaken/raylib.c3l diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/Makefile b/Makefile deleted file mode 100644 index 0d82e80..0000000 --- a/Makefile +++ /dev/null @@ -1,15 +0,0 @@ -CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/include -CC = gcc -LDFLAGS = -Lraylib/lib -lm - -all: ugui - -ugui: ugui.o vectree.o cache.o timer.o raylib/lib/libraylib.a - -ugui.o: ugui.c ugui.h - -vectree.o: vectree.c ugui.h - -cache.o: cache.c ugui.h - -timer.o: timer.c timer.h diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/cache.c b/cache.c deleted file mode 100644 index 5195892..0000000 --- a/cache.c +++ /dev/null @@ -1,211 +0,0 @@ -// LRU cache: -/* - * The cache uses a pool (array) containing all the elements and a hash table - * associating the position in the pool with the element id - */ - -#include -#include - -#include "ugui.h" - -// To have less collisions TABLE_SIZE has to be larger than the cache size -#define CACHE_SIZE 265 -#define TABLE_SIZE (CACHE_SIZE * 1.5f) -#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3) -#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6) -#define CACHE_BRESET(b) \ - for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \ - ; -#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63) -#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63))) - -/* Hash Table Implementation ----------------------------------------------------- */ - -#define HASH_MAXSIZE 4096 - -// hash table (id -> cache index) -typedef struct { - UgId id; - uint32_t index; -} IdElem; - -typedef struct _IdTable { - uint32_t items, size, exp; - IdElem bucket[]; -} IdTable; - -IdTable *table_create(uint32_t size) -{ - if (!size || size > HASH_MAXSIZE) { - return NULL; - } - /* round to the greater power of two */ - /* FIXME: check for intger overflow here */ - uint32_t exp = 32 - __builtin_clz(size - 1); - size = 1 << (exp); - /* FIXME: check for intger overflow here */ - IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size); - if (ht) { - ht->items = 0; - ht->size = size; - memset(ht->bucket, 0, sizeof(IdTable) * size); - } - return ht; -} - -void table_destroy(IdTable *ht) -{ - if (ht) { - free(ht); - } -} - -// Find and return the element by pointer -IdElem *table_search(IdTable *ht, UgId id) -{ - if (!ht) { - return NULL; - } - - // In this case id is the hash - uint32_t idx = id % ht->size; - - for (uint32_t x = 0, i; x < ht->size; x++) { - i = (idx + x) % ht->size; - if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) { - return &(ht->bucket[i]); - } - } - return NULL; -} - -// FIXME: this simply overrides the found item -IdElem *table_insert(IdTable *ht, IdElem entry) -{ - IdElem *r = table_search(ht, entry.id); - if (r != NULL) { - if (r->id != 0) { - ht->items++; - } - *r = entry; - } - return r; -} - -IdElem *table_remove(IdTable *ht, UgId id) -{ - if (!ht) { - return NULL; - } - IdElem *r = table_search(ht, id); - if (r) { - r->id = 0; - } - return r; -} - -/* Cache Implementation ---------------------------------------------------------- */ - -// Every CACHE_CYCLES operations mark not-present the unused elements -#define CACHE_CYCLE(c) \ - do { \ - if (++(c->cycles) > CACHE_NCYCLES) { \ - for (int i = 0; i < CACHE_BSIZE; i++) { \ - c->present[i] &= c->used[i]; \ - c->used[i] = 0; \ - } \ - c->cycles = 0; \ - } \ - } while (0) - -/* FIXME: check for allocation errors */ -UgElemCache ug_cache_init(void) -{ - IdTable *t = table_create(TABLE_SIZE); - UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE); - uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE); - uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE); - CACHE_BRESET(p); - CACHE_BRESET(u); - return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0}; -} - -void ug_cache_free(UgElemCache *cache) -{ - if (cache) { - table_destroy(cache->table); - free(cache->array); - free(cache->present); - free(cache->used); - } -} - -UgElem *ug_cache_search(UgElemCache *cache, UgId id) -{ - if (!cache) { - return NULL; - } - IdElem *r; - r = table_search(cache->table, id); - /* MISS */ - if (!r || id != r->id) { - return NULL; - } - /* MISS, the data is not valid (not present) */ - if (!CACHE_BTEST(cache->present, r->index)) { - return NULL; - } - /* HIT, set as recently used */ - CACHE_BSET(cache->used, r->index); - return (&cache->array[r->index]); -} - -/* Look for a free spot in the present bitmap and return its index */ -/* If there is no free space left then just return the first position */ -int ug_cache_get_free_spot(UgElemCache *cache) -{ - if (!cache) { - return -1; - } - int x = 0; - for (int b = 0; b < CACHE_BSIZE; b++) { - if (cache->present[b] == 0) { - x = 64; - } else { - x = __builtin_clzll(cache->present[b]); - } - x = 64 - x; - if (!CACHE_BTEST(cache->present, x + 64 * b)) { - return x + 64 * b; - } - } - return 0; -} - -UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) -{ - if (!cache) { - return NULL; - } - UgElem *spot = NULL; - - /* Set used and present */ - CACHE_BSET(cache->present, index); - CACHE_BSET(cache->used, index); - CACHE_CYCLE(cache); - spot = &(cache->array[index]); - *spot = *g; - IdElem e = {.id = g->id, .index = index}; - if (!table_insert(cache->table, e)) { - return NULL; - } - return spot; -} - -// Insert an element in the cache -UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index) -{ - *index = ug_cache_get_free_spot(cache); - return ug_cache_insert_at(cache, g, *index); -} diff --git a/compile_flags.txt b/compile_flags.txt deleted file mode 100644 index 3e90386..0000000 --- a/compile_flags.txt +++ /dev/null @@ -1,5 +0,0 @@ --Iraylib/include --Wall --Wextra --pedantic --std=c11 diff --git a/docs/.gitkeep b/docs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/fm/README b/fm/README deleted file mode 100644 index 3c09197..0000000 --- a/fm/README +++ /dev/null @@ -1 +0,0 @@ -File Manager using ugui diff --git a/fm/libconf.c b/fm/libconf.c deleted file mode 100644 index 07ac86e..0000000 --- a/fm/libconf.c +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include -#include - -#include -#include -#include - -#define PATHS_NO 3 - -static char temp[PATH_MAX] = {0}; -static int valid_paths = 0; -static char paths[PATHS_NO][PATH_MAX] = {0}; // 0: xdg, 1: fallback, 2: global - -static const mode_t CONFDIR_MODE = S_IRWXU | S_IRWXU | S_IRWXU | S_IRWXU; - -static void err(const char *fmt, ...) -{ - va_list ap; - va_start(ap, fmt); - - fprintf(stderr, "[libconf]: "); - vfprintf(stderr, fmt, ap); - fprintf(stderr, "\n"); - - va_end(ap); -} - -// implements "mkdir -p" -static int mkdir_p(const char *path) -{ - char tmp_path[PATH_MAX] = {0}; - strncpy(tmp_path, path, PATH_MAX - 1); - struct stat st; - - for (char *tk = strtok(tmp_path, "/"); tk != NULL; tk = strtok(NULL, "/")) { - if (tk != tmp_path) { - tk[-1] = '/'; - } - - if (stat(tmp_path, &st)) { - if (errno != ENOENT) { - err("could not stat() %s: %s", - tmp_path, - strerror(errno)); - return 1; - } - - if (mkdir(tmp_path, CONFDIR_MODE)) { - err("could not mkdir() %s: %s", - tmp_path, - strerror(errno)); - return 1; - } - } else if (!S_ISDIR(st.st_mode)) { - err("could not create directory %s: file exists but it is " - "not a directory", - tmp_path); - return 1; - } - } - return 0; -} - -// TODO: add a way to add a custom directory to the paths, for example via an -// environment variable -// TODO: maybe add a LIBCONF_PATH that overrides the defaults -/* default paths are: - * 1. ${XDG_CONFIG_HOME}/libconf/appid.d/ - * 2. ${HOME}/.config/libconf/appid.d/ - * 3. etc/libconf/appid.d/ - */ -static int fill_paths(const char *id) -{ - // TODO: verify id - if (id == NULL) { - err("must provide a valid app id"); - return 1; - } - - const char *xdg = getenv("XDG_CONFIG_HOME"); - if (xdg != NULL) { - snprintf(paths[0], PATH_MAX, "%s/libconf/%s.d", xdg, id); - } - - const char *home = getenv("HOME"); - if (home) { - snprintf(temp, PATH_MAX, "%s/.config/libconf/%s.d", home, id); - - if (mkdir_p(temp) == 0) { - strcpy(paths[1], temp); - } - } - - // do not create global config path since most likely we don't have - // the necessary permissions to do so - snprintf(paths[2], PATH_MAX, "/etc/libconf/%s.d", id); - - for (size_t i = 0; i < PATHS_NO; i++) { - printf("paths[%ld] = %s\n", i, paths[i]); - } - - valid_paths = 1; - - return 0; -} - -// get config file path -const char *lcnf_get_conf_file(const char *id, const char *name, int global) -{ - static char str[PATH_MAX] = {0}; - - if (id == NULL) { - return NULL; - } - - if (!valid_paths) { - if (fill_paths(id)) { - return NULL; - } - } - - // quick path for global config file - if (global && paths[2][0] != '\0') { - snprintf(str, PATH_MAX, "%s/%s", paths[2], name); - return str; - } - - int found = 0; - for (size_t i = 0; i < PATHS_NO; i++) { - if (paths[i][0] == '\0') { - continue; - } - - struct stat st; - snprintf(str, PATH_MAX, "%s/%s", paths[i], name); - - if (stat(str, &st) && errno != ENOENT) { - err("could not stat %s: %s", str, strerror(errno)); - } - - if (S_ISREG(st.st_mode)) { - found = 1; - break; - } - } - - return found ? str : NULL; -} - -// get config file directory path -const char *lcnf_get_conf_dir(const char *id, int global) -{ - if (id == NULL) { - return NULL; - } - - if (global) { - return paths[2][0] != '\0' ? paths[2] : NULL; - } - - if (paths[0][0] != '\0') { - return paths[0]; - } else if (paths[1][0] != '\0') { - return paths[1]; - } else { - return NULL; - } -} - -// TODO: watch directory for file updates -int lcnf_watch_conf_dir(const char *id, int global); - -// TODO: collect all files into a temporary one, useful for when you have multiple -// configuration files like 00-foo.conf, 01-bar.conf and 99-baz.conf -const char *lcnf_collect_conf_files(const char *id, int global); - -#if 1 - -int main(void) -{ - const char *p = lcnf_get_conf_file("pippo", "pippo.toml", 0); - if (p) { - printf("config found: %s\n", p); - } else { - printf("config not found\n"); - } - return 0; -} - -#endif diff --git a/fm/libconf.h b/fm/libconf.h deleted file mode 100644 index f92c29b..0000000 --- a/fm/libconf.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef LIBCONF_H_ -#define LIBCONF_H_ - -const char *lcnf_get_conf_file(const char *id, const char *name, int global); -const char *lcnf_get_conf_dir(const char *id, int global); - -#endif diff --git a/get_raylib.sh b/get_raylib.sh deleted file mode 100755 index cbe25df..0000000 --- a/get_raylib.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/sh - -mkdir -p raylib -wget https://github.com/raysan5/raylib/releases/download/5.0/raylib-5.0_linux_amd64.tar.gz -tar -xvf raylib-5.0_linux_amd64.tar.gz -mv ./raylib-5.0_linux_amd64/* ./raylib/ - -rm -rf raylib-5.0_linux_amd64 raylib-5.0_linux_amd64.tar.gz diff --git a/lib/.gitkeep b/lib/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/raylib.c3l b/lib/raylib.c3l new file mode 160000 index 0000000..c7ebe05 --- /dev/null +++ b/lib/raylib.c3l @@ -0,0 +1 @@ +Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e diff --git a/project.json b/project.json new file mode 100644 index 0000000..463cd65 --- /dev/null +++ b/project.json @@ -0,0 +1,56 @@ +{ + // Language version of C3. + "langrev": "1", + // Warnings used for all targets. + "warnings": [ "no-unused" ], + // Directories where C3 library files may be found. + "dependency-search-paths": [ "lib" ], + // Libraries to use for all targets. + "dependencies": [ "raylib" ], + "features": [ + // See rcore.c3 + //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", + //"SUPPORT_STANDARD_FILEIO", + //"SUPPORT_FILE_SYSTEM_FUNCTIONS", + //"SUPPORT_DATA_ENCODER", + // See text.c3 + //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", + //"SUPPORT_TEXT_C_STRING_MANAGEMENT", + //"SUPPORT_RANDOM_GENERATION", + + "SUPPORT_RAYGUI", + //"RAYGUI_NO_ICONS", + //"RAYGUI_CUSTOM_ICONS", + ], + // Authors, optionally with email. + "authors": [ "John Doe " ], + // Version using semantic versioning. + "version": "0.1.0", + // Sources compiled for all targets. + "sources": [ "src/**" ], + // C sources if the project also compiles C sources + // relative to the project file. + // "c-sources": [ "csource/**" ], + // Include directories for C sources relative to the project file. + // "c-include-dirs": [ "csource/include" ], + // Output location, relative to project file. + "output": "build", + // Architecture and OS target. + // You can use 'c3c --list-targets' to list all valid targets. + // "target": "windows-x64", + // Targets. + "targets": { + "ugui": { + // Executable or library. + "type": "executable", + // Additional libraries, sources + // and overrides of global settings here. + }, + }, + // Global settings. + // CPU name, used for optimizations in the LLVM backend. + "cpu": "generic", + // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". + "opt": "O0", + // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. +} diff --git a/resources/.gitkeep b/resources/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/scripts/.gitkeep b/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/.gitkeep b/src/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/cache.c3 b/src/cache.c3 new file mode 100644 index 0000000..2123194 --- /dev/null +++ b/src/cache.c3 @@ -0,0 +1,133 @@ +module cache(); + +/* LRU Cache + * The cache uses a pool (array) to store all the elements, each element has + * a key (id) and a value. A HashMap correlates the ids to an index in the pool. + * To keep track of which items were recently used two bit arrays are kept, one + * stores the "used" flag for each index and anothe the "present" flag. + * Every NCYCLES operations the present and used arrays are updated to free up + * the elements that were not recently used. + */ + +// FIXME: this module should really allocate all resources on an arena or temp +// allocator, since all memory allocations are connected and freeing +// happens at the same time + +import std::core::mem; +import std::collections::bitset; +import std::collections::map; + +def BitArr = bitset::BitSet() @private; +def IdTable = map::Map() @private; +def IdTableEntry = map::Entry() @private; + +const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); + +struct Cache { + BitArr present, used; + IdTable table; + Value[] pool; + usz cycle_count; +} + +// Every CACHE_CYCLES operations mark as not-present the unused elements +macro Cache.cycle(&cache) @private { + cache.cycle_count++; + if (cache.cycle_count > CACHE_NCYCLES) { + for (usz i = 0; i < cache.present.data.len; i++) { + cache.present.data[i] &= cache.used.data[i]; + cache.used.data[i] = 0; + } + cache.cycle_count = 0; + } +} + +fn void! Cache.init(&cache) +{ + cache.table = map::new()(SIZE); + // FIXME: this shit is SLOW + foreach (idx, bit : cache.used) { cache.used[idx] = false; } + foreach (idx, bit : cache.present) { cache.present[idx] = false; } + cache.pool = mem::new_array(Value, SIZE); +} + +fn void Cache.free(&cache) +{ + (void)cache.table.free(); + (void)mem::free(cache.pool); +} + +fn Value*! Cache.search(&cache, Key id) +{ + // get_entry() faults on miss + IdTableEntry* entry = cache.table.get_entry(id)!; + + /* MISS */ + if (entry.key != id) { + return SearchResult.MISSING?; + } + + /* MISS, the data is not valid (not present) */ + if (!cache.present[entry.value]) { + // if the data is not present but it is still in the table, remove it + cache.table.remove(id)!; + return SearchResult.MISSING?; + } + + /* HIT, set as recently used */ + cache.used[entry.value] = true; + return &(cache.pool[entry.value]); +} + +/* Look for a free spot in the present bitmap and return its index */ +/* If there is no free space left then just return the first position */ +fn usz Cache.get_free_spot(&cache) @private +{ + // FIXME: This shit is SLOW, especially when clz() exists + foreach (idx, bit: cache.present) { + if (bit == false) { + return idx; + } + } + return 0; +} + +fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private +{ + // TODO: verify index, g and id + Value* spot; + + /* Set used and present */ + cache.present.set(index); + cache.used.set(index); + cache.cycle(); + + spot = &(cache.pool[index]); + *spot = *g; + cache.table.set(id, index); + return spot; +} + +// Insert an element in the cache, returns the index +fn Value*! Cache.insert_new(&cache, Value* g, Key id) +{ + usz index = cache.get_free_spot(); + return cache.insert_at(g, id, index); +} + +fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) +{ + Value*! c = cache.search(id); + if (catch e = c) { + if (e != SearchResult.MISSING) { + return e?; + } else { + // if the element is new (inserted) set the is_new flag + if (is_new) *is_new = true; + return cache.insert_new(g, id); + } + } else { + if (is_new) *is_new = false; + return c; + } +} diff --git a/src/fifo.c3 b/src/fifo.c3 new file mode 100644 index 0000000..dbc50f0 --- /dev/null +++ b/src/fifo.c3 @@ -0,0 +1,49 @@ +module fifo(); + +import std::core::mem; + +fault FifoErr { + FULL, + EMPTY, +} + +// TODO: specify the allocator + +struct Fifo { + Type[] arr; + usz out; + usz count; +} + +fn void! Fifo.init(&fifo, usz size) +{ + fifo.arr = mem::new_array(Type, size); + fifo.out = 0; + fifo.count = 0; +} + +fn void Fifo.free(&fifo) +{ + (void)mem::free(fifo.arr); +} + +fn void! Fifo.enqueue(&fifo, Type *elem) +{ + if (fifo.count >= fifo.arr.len) { + return FifoErr.FULL?; + } + usz in = (fifo.out + fifo.count) % fifo.arr.len; + fifo.arr[in] = *elem; + fifo.count++; +} + +fn Type*! Fifo.dequeue(&fifo) +{ + if (fifo.count == 0) { + return FifoErr.EMPTY?; + } + Type *ret = &fifo.arr[fifo.out]; + fifo.count--; + fifo.out = (fifo.out + 1) % fifo.arr.len; + return ret; +} diff --git a/src/main.c3 b/src/main.c3 new file mode 100644 index 0000000..989e1d7 --- /dev/null +++ b/src/main.c3 @@ -0,0 +1,196 @@ +import std::io; +import vtree; +import cache; +import ugui; +import rl; + +fn int main(String[] args) +{ + ugui::Ctx ctx; + ctx.init()!!; + + short width = 800; + short height = 450; + rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); + rl::init_window(width, height, "Ugui Test"); + ctx.input_window_size(width, height)!!; + + double[10][4] median_times; + isz frame; + double median_input, median_layout, median_draw, median_tot; + + // Main loop + while (!rl::window_should_close()) { + const int PARTIAL_INPUT = 0; + const int PARTIAL_LAYOUT = 1; + const int PARTIAL_DRAW = 2; + //timer_start(); + + /*** Start Input Handling ***/ + if (rl::is_window_resized()) { + width = (short)rl::get_screen_width(); + height = (short)rl::get_screen_height(); + ctx.input_window_size(width, height)!!; + } + + ctx.input_changefocus(rl::is_window_focused()); + + // FIXME: In raylib it doesn't seem to be a quick way to check if + // a mouse input event was received, so for now just use + // the delta information + rl::Vector2 mousedelta = rl::get_mouse_delta(); + if (mousedelta.x || mousedelta.y) { + ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y); + } + + ugui::MouseButtons buttons; + buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); + buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); + buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); + ctx.input_mouse_button(buttons); + + //timer_partial(PARTIAL_INPUT); + /*** End Input Handling ***/ + + /*** Start UI Handling ***/ + ctx.frame_begin()!!; + + // main div, fill the whole window + ctx.div_begin("main", ugui::DIV_FILL)!!; + {| + ctx.layout_set_column()!!; + if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) { + io::printn("HOLDING button0"); + } + ctx.layout_next_column()!!; + ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!; + ctx.layout_next_column()!!; + ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!; + |}; + ctx.div_end()!!; + + ctx.frame_end()!!; + //timer_partial(PARTIAL_LAYOUT); + /*** End UI Handling ***/ + + /*** Start UI Drawing ***/ + rl::begin_drawing(); + // ClearBackground(BLACK); + + io::printn("----- Draw Begin -----"); + rl::Color c; + for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { + switch (cmd.type) { + case ugui::CmdType.CMD_RECT: + io::printfn( + "draw rect x=%d y=%d w=%d h=%d", + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h + ); + c = rl::Color{ + .r = cmd.rect.color.r, + .g = cmd.rect.color.g, + .b = cmd.rect.color.b, + .a = cmd.rect.color.a, + }; + rl::draw_rectangle( + cmd.rect.rect.x, + cmd.rect.rect.y, + cmd.rect.rect.w, + cmd.rect.rect.h, + c + ); + default: + io::printfn("Unknown cmd type: %d", cmd.type); + } + } + io::printf("----- Draw End -----\n\n"); + + rl::end_drawing(); + //timer_partial(PARTIAL_DRAW); + //timer_stop(); + /*** End UI Drawing ***/ + /* + median_times[frame][PARTIAL_INPUT] = + 1e3 * timer_get_sec(PARTIAL_INPUT); + median_times[frame][PARTIAL_LAYOUT] = + 1e3 * timer_get_sec(PARTIAL_LAYOUT); + median_times[frame][PARTIAL_DRAW] = + 1e3 * timer_get_sec(PARTIAL_DRAW); + median_times[frame][3] = 1e3 * timer_get_sec(-1); + */ + frame += 1; + frame %= 10; + /* + if (frame == 0) { + median_input = 0; + median_layout = 0; + median_draw = 0; + median_tot = 0; + for (size_t i = 0; i < 10; i++) { + median_input += median_times[i][PARTIAL_INPUT]; + median_layout += median_times[i][PARTIAL_LAYOUT]; + median_draw += median_times[i][PARTIAL_DRAW]; + median_tot += median_times[i][3]; + } + median_input /= 10; + median_layout /= 10; + median_draw /= 10; + median_tot /= 10; + } + + printf("input time: %lfms\n", median_input); + printf("layout time: %lfms\n", median_layout); + printf("draw time: %lfms\n", median_draw); + printf("total time: %lfms\n", median_tot); + + // Throttle Frames + // TODO: add an fps limit, time frame generation and log it + const float TARGET_FPS = 100; + float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); + WaitTime(wait_time); + */ + } + + rl::close_window(); + + ctx.free(); + return 0; +} + +fn void! test_vtree() @test +{ + vtree::VTree() vt; + vt.init(10)!!; + defer vt.free(); + + assert(vt.size() == 10, "Size is incorrect"); + + isz ref = vt.add("Ciao Mamma", 0)!!; + String s = vt.get(ref)!!; + assert(s == "Ciao Mamma", "String is incorrect"); + isz par = vt.parentof(0)!!; + assert(ref == par, "Not Root"); + + vt.print(); +} + +def StrCache = cache::Cache(); +fn void! test_cache() @test +{ + StrCache cc; + cc.init()!!; + defer cc.free(); + + String*! r = cc.search(1); + if (catch ex = r) { + if (ex != SearchResult.MISSING) { + return ex?; + } + } + + r = cc.get_or_insert(&&"Ciao Mamma", 1)!; + assert(*r!! == "Ciao Mamma", "incorrect string"); +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 new file mode 100644 index 0000000..7883b62 --- /dev/null +++ b/src/ugui_data.c3 @@ -0,0 +1,184 @@ +module ugui; + +import vtree; +import cache; +import fifo; + +struct Rect { + short x, y, w, h; +} + +struct Point { + short x, y; +} + +struct Color{ + char r, g, b, a; +} + +// element ids are just long ints +def Id = usz; + +enum ElemType { + ETYPE_NONE, + ETYPE_DIV, + ETYPE_BUTTON, +} + +bitstruct ElemFlags : uint { + bool updated : 0; + bool has_focus : 1; +} + +bitstruct ElemEvents : uint { + bool key_press : 0; + bool key_release : 1; + bool key_hold : 2; + bool mouse_hover : 3; + bool mouse_press : 4; + bool mouse_release : 5; + bool mouse_hold : 6; +} + +enum DivLayout { + LAYOUT_ROW, + LAYOUT_COLUMN, + LAYOUT_FLOATING, +} + +// div element +struct Div { + DivLayout layout; + Point origin_r, origin_c; + Color color_bg; +} + +// element structure +struct Elem { + Id id; + ElemFlags flags; + ElemEvents events; + Rect rect; + ElemType type; + union { + Div div; + } +} + + +// relationships between elements are stored in a tree, it stores just the ids +def IdTree = vtree::VTree() @private; + +// elements themselves are kept in a cache +const uint MAX_ELEMENTS = 2048; +def ElemCache = cache::Cache() @private; + +def CmdQueue = fifo::Fifo(); + +fault UgError { + INVALID_SIZE, + EVENT_UNSUPPORTED, + UNEXPECTED_ELEMENT, +} + +fn Id fnv1a(String str) +{ + const ulong FNV_OFF = 0xcbf29ce484222325; + const ulong FNV_PRIME = 0x100000001b3; + + ulong hash = FNV_OFF; + foreach (c : str) { + hash ^= c; + hash *= FNV_PRIME; + } + + return hash; +} + +macro hash(String str) { return fnv1a(str); } + +macro uint_to_rgba(uint u) { + return Color{ + .r = (char)((u >> 24) & 0xff), + .g = (char)((u >> 16) & 0xff), + .b = (char)((u >> 8) & 0xff), + .a = (char)((u >> 0) & 0xff) + }; +} + +const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; + +macro abs(a) { return a < 0 ? -a : a; } +macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); } + +const uint STACK_STEP = 10; +const uint MAX_ELEMS = 128; +const uint MAX_CMDS = 256; +const uint ROOT_ID = 1; + +// command type +enum CmdType { + CMD_RECT, +} + +// command to draw a rect +struct CmdRect { + Rect rect; + Color color; +} + +// command structure +struct Cmd { + CmdType type; + union { + CmdRect rect; + } +} + +enum Layout { + ROW, + COLUMN, + FLOATING +} + +// global style, similar to the css box model +struct Style { // css box model + Rect padding; + Rect border; + Rect margin; + Color bgcolor; // background color + Color fgcolor; // foreground color + Color bcolor; // border color +} + + +struct Ctx { + Layout layout; + IdTree tree; + ElemCache cache; + CmdQueue cmd_queue; + // total size in pixels of the context + ushort width, height; + Style style; + + bool has_focus; + struct input { + InputEvents events; + struct mouse { + Point pos, delta; + // mouse_down: bitmap of mouse buttons that are held + // mouse_updated: bitmap of mouse buttons that have been updated + // mouse_released = mouse_updated & ~mouse_down + // mouse_pressed = mouse_updated & mouse_down + MouseButtons down; + MouseButtons updated; + } + } + + isz active_div; // tree node indicating the current active div +} + +macro point_in_rect(Point p, Rect r) +{ + return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); +} diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 new file mode 100644 index 0000000..f2d3dca --- /dev/null +++ b/src/ugui_impl.c3 @@ -0,0 +1,244 @@ +module ugui; + +import std::io; + +// return a pointer to the parent of the current active div +fn Elem*! Ctx.get_parent(&ctx) +{ + // FIXME: if the tree held pointers to the elements then no more + // redundant cache search + Id parent_id = ctx.tree.get(ctx.active_div)!; + return ctx.cache.search(parent_id); +} + +fn void! Ctx.init(&ctx) +{ + ctx.tree.init(MAX_ELEMENTS)!; + defer catch { (void)ctx.tree.free(); } + //ug_fifo_init(&ctx.fifo, MAX_CMDS); + + ctx.cache.init()!; + defer catch { (void)ctx.cache.free(); } + + ctx.cmd_queue.init(MAX_ELEMENTS)!; + defer catch { (void)ctx.cmd_queue.free(); } + + ctx.layout = Layout.ROW; + ctx.active_div = 0; + + // TODO: add style config + ctx.style.margin = Rect{1, 1, 1, 1}; +} + +fn void Ctx.free(&ctx) +{ + (void)ctx.tree.free(); + (void)ctx.cache.free(); + (void)ctx.cmd_queue.free(); +} + +fn void! Ctx.frame_begin(&ctx) +{ + // 1. Create the root div element + // NOTE: in c3 everythong is zero initialized by default + Rect space = { + .w = ctx.width, + .h = ctx.height, + }; + + Elem root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .rect = space, + .div = { + .layout = LAYOUT_ROW, + }, + }; + + // The root should have the updated flag only if the size of the window + // was changed between frames, this propagates an element size recalculation + // down the element tree + if (ctx.input.events.resize) { + root.flags.updated = true; + } + + // if the window has focus then the root element also has focus, no other + // computation needed, child elements need to check the mouse positon and + // other stuff + if (ctx.has_focus) { + root.flags.has_focus = true; + } + + // FIXME: check errors + // 2. Get the root element from the cache and update it + bool is_new; + Elem empty_elem; + Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!; + // flags always need to be set to the new flags + c_elem.flags = root.flags; + if (is_new || root.flags.updated) { + *c_elem = root; + } + + // 3. Push the root element into the element tree + ctx.active_div = ctx.tree.add(root.id, 0)!; + + // print_tree(ctx); + + // The root element does not push anything to the stack + // TODO: add a background color taken from a theme or config + + io::printn("##### Frame Begin #####"); +} + +fn void! Ctx.frame_end(&ctx) +{ + // 1. clear the tree + ctx.tree.prune(0)!; + + // 2. clear input fields + ctx.input.events = (InputEvents)0; + + // draw mouse position +$if 1: + Cmd cmd = { + .type = CMD_RECT, + .rect.rect = { + .x = ctx.input.mouse.pos.x - 2, + .y = ctx.input.mouse.pos.y - 2, + .w = 4, + .h = 4, + }, + .rect.color = uint_to_rgba(0xff00ffff) + }; + ctx.cmd_queue.enqueue(&cmd)!; +$endif + + io::printn("##### Frame End #####"); +} + +fn void! Ctx.div_begin(&ctx, String label, Rect size) +{ + Id id = hash(label); + + bool is_new; + Elem empty_elem; + Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + + // FIXME: why save the id in the tree and not something more direct like + // the element pointer or the index into the cache vector? + isz div_node = ctx.tree.add(id, ctx.active_div)!; + + Elem *parent = ctx.get_parent()!; + + ctx.tree.print(); + + // Use the current div + ctx.active_div = div_node; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_DIV; + c_elem.flags = (ElemFlags)0; + + // do layout and update flags only if the element was updated + if (is_new || parent.flags.updated) { + // 2. layout the element + c_elem.rect = ctx.position_element(parent, size); + + // 3. Mark the element as updated + c_elem.flags.updated = true; + // 4. Fill the div fields + c_elem.div.layout = parent.div.layout; + c_elem.div.origin_c = Point{ + .x = c_elem.rect.x, + .y = c_elem.rect.y, + }; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.origin_r = c_elem.div.origin_c; + } else if (parent.flags.has_focus) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) { + c_elem.flags.has_focus = true; + } + } else { + // TODO: check active + // TODO: check scrollbars + // TODO: check resizeable + } + + // Add the background to the draw stack + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = c_elem.div.color_bg, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; +} + +fn void! Ctx.div_end(&ctx) +{ + // the active_div returns to the parent of the current one + ctx.active_div = ctx.tree.parentof(ctx.active_div)!; +} + +// @ensure elem != null +fn bool Ctx.is_hovered(&ctx, Elem *elem) +{ + return point_in_rect(ctx.input.mouse.pos, elem.rect); +} + +fn ElemEvents! Ctx.button(&ctx, String label, Rect size) +{ + Id id = hash(label); + + // TODO: do layouting if the element is new or the parent has updated + bool is_new; + Elem empty_elem; + Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + ctx.tree.print(); + + Elem *parent = ctx.get_parent()!; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_BUTTON; + c_elem.flags = (ElemFlags)0; + Color bg_color = uint_to_rgba(0x0000ffff); + + // if the element is new or the parent was updated then redo layout + if (is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + + // TODO: 3. Fill the button specific fields + } + + // TODO: Check for interactions + if (parent.flags.has_focus) { + if (ctx.is_hovered(c_elem)) { + c_elem.flags.has_focus = true; + c_elem.events.mouse_hover = true; + bg_color = uint_to_rgba(0x00ff00ff); + c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left; + } else { + c_elem.events.mouse_hover = false; + } + } + + // Draw the button + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 new file mode 100644 index 0000000..d8c63f3 --- /dev/null +++ b/src/ugui_input.c3 @@ -0,0 +1,78 @@ +module ugui; + +import std::io; + +// TODO: this could be a bitstruct +bitstruct InputEvents : uint { + bool resize : 0; // window size was changed + bool change_focus : 1; // window focus changed + bool mouse_move : 2; // mouse was moved + bool mouse_btn : 3; // mouse button pressed or released +} + +// Window size was changed +fn void! Ctx.input_window_size(&ctx, short width, short height) +{ + if (width <= 0 || height <= 0) { + return UgError.INVALID_SIZE?; + } + ctx.width = width; + ctx.height = height; + ctx.input.events.resize = true; +} + +// Window gained/lost focus +fn void Ctx.input_changefocus(&ctx, bool has_focus) +{ + // FIXME: raylib only has an API to query the focus status so we have to + // update the input flag only if the focus changed + if (ctx.has_focus != has_focus) { + ctx.input.events.change_focus = true; + } + ctx.has_focus = has_focus; +} + +bitstruct MouseButtons : uint { + bool btn_left : 0; + bool btn_middle : 1; + bool btn_right : 2; + bool btn_4 : 3; + bool btn_5 : 4; +} + +// Mouse Button moved +fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) +{ + ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; + ctx.input.mouse.down = buttons; + ctx.input.events.mouse_btn = true; + + io::printfn( + "Mouse Down: %s%s%s%s%s", + buttons.btn_left ? "BTN_LEFT " : "", + buttons.btn_right ? "BTN_RIGHT " : "", + buttons.btn_middle ? "BTN_MIDDLE " : "", + buttons.btn_4 ? "BTN_4 " : "", + buttons.btn_5 ? "BTN_5 " : "" + ); +} + +// Mouse was moved, report absolute position +// TODO: implement this +fn void Ctx.input_mouse_abs(&ctx, short x, short y) { return; } + +// Mouse was moved, report relative motion +fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) +{ + ctx.input.mouse.delta.x = dx; + ctx.input.mouse.delta.y = dy; + + short mx, my; + mx = ctx.input.mouse.pos.x + dx; + my = ctx.input.mouse.pos.y + dy; + + ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width); + ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height); + + ctx.input.events.mouse_move = true; +} diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 new file mode 100644 index 0000000..5c38c17 --- /dev/null +++ b/src/ugui_layout.c3 @@ -0,0 +1,153 @@ +module ugui; + + +fn void! Ctx.layout_set_row(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_ROW; +} + +fn void! Ctx.layout_set_column(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_COLUMN; +} + +fn void! Ctx.layout_set_floating(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.layout = LAYOUT_FLOATING; +} + +fn void! Ctx.layout_next_row(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.origin_r = Point{ + .x = parent.rect.x, + .y = parent.div.origin_c.y, + }; + parent.div.origin_c = parent.div.origin_r; +} + +fn void! Ctx.layout_next_column(&ctx) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + Elem *parent = ctx.cache.search(parent_id)!; + + if (parent.type != ETYPE_DIV) { + // what? + return UgError.UNEXPECTED_ELEMENT?; + } + + parent.div.origin_c = Point{ + .x = parent.div.origin_r.x, + .y = parent.rect.y, + }; + parent.div.origin_r = parent.div.origin_c; +} + +// position the rectangle inside the parent according to the layout +fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) +{ + Rect elem_rect; + Point origin; + + // 1. Select the right origin + switch (parent.div.layout) { + case LAYOUT_ROW: + origin = parent.div.origin_r; + case LAYOUT_COLUMN: + origin = parent.div.origin_c; + case LAYOUT_FLOATING: // none + default: + // Error + } + + // 2. Position the rect + elem_rect.x = origin.x + rect.x; + elem_rect.y = origin.y + rect.y; + + // 3. Calculate width & height + // TODO: what about negative values? + // FIXME: account for origin offset!! + elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w; + elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h; + + // 4. Update the origins of the parent + parent.div.origin_r = Point{ + .x = elem_rect.x + elem_rect.w, + .y = elem_rect.y, + }; + parent.div.origin_c = Point{ + .x = elem_rect.x, + .y = elem_rect.y + elem_rect.h, + }; + + // if using the style then apply margins + // FIXME: this does not work + if (style && parent.div.layout != LAYOUT_FLOATING) { + elem_rect.x += ctx.style.margin.x; + elem_rect.y += ctx.style.margin.y; + + // total keep-out borders + Rect margin_tot = { + .x = ctx.style.padding.x + ctx.style.border.x + + ctx.style.margin.x, + .y = ctx.style.padding.y + ctx.style.border.y + + ctx.style.margin.y, + .w = ctx.style.padding.w + ctx.style.border.x + + ctx.style.margin.w, + .h = ctx.style.padding.h + ctx.style.border.x + + ctx.style.margin.h, + }; + + parent.div.origin_r.x += margin_tot.x + margin_tot.w; + // parent.div.origin_r.y += margin_tot.h; + + // parent.div.origin_c.x += margin_tot.w; + parent.div.origin_c.y += margin_tot.y + margin_tot.h; + } + + /* + printf( + "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d + %d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x, + parent.rect.y, + parent.rect.w, + parent.rect.h, + elem_rect.x, + elem_rect.y, + elem_rect.w, + elem_rect.h + ); + */ + return elem_rect; +} diff --git a/src/vtree.c3 b/src/vtree.c3 new file mode 100644 index 0000000..8e5d7b1 --- /dev/null +++ b/src/vtree.c3 @@ -0,0 +1,333 @@ +module vtree(); + +import std::core::mem; +import std::io; + +struct VTree { + usz elements; + ElemType[] vector; // vector of element ids + isz[] refs, ordered_refs; +} + +fault VTreeError { + CANNOT_SHRINK, + INVALID_REFERENCE, + TREE_FULL, + REFERENCE_NOT_PRESENT, + INVALID_ARGUMENT, +} + +macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } +macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } +macro VTree.size(&tree) { return tree.refs.len; } + +// macro to zero an elemen +macro @zero() +{ + $if $assignable(0, ElemType): + return 0; + $else + return ElemType{0}; + $endif +} + +fn void! VTree.init(&tree, usz size) +{ + tree.vector = mem::new_array(ElemType, size); + defer catch { (void)mem::free(tree.vector); } + + tree.refs = mem::new_array(isz, size); + defer catch { (void)mem::free(tree.refs); } + + tree.ordered_refs = mem::new_array(isz, size); + defer catch { (void)mem::free(tree.ordered_refs); } + + // set all refs to -1, meaning invalid (free) element + tree.refs[..] = -1; + + tree.elements = 0; +} + +fn void VTree.free(&tree) +{ + (void)mem::free(tree.vector); + (void)mem::free(tree.refs); + (void)mem::free(tree.ordered_refs); +} + +fn void VTree.pack(&tree) +{ + // TODO: add a PACKED flag to skip this + + isz free_spot = -1; + for (usz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == -1) { + free_spot = i; + continue; + } + + // find a item that can be packed + if (free_spot >= 0 && tree.refs[i] >= 0) { + isz old_ref = i; + + // move the item + tree.vector[free_spot] = tree.vector[i]; + tree.refs[free_spot] = tree.refs[i]; + + tree.vector[i] = @zero(); + tree.refs[i] = -1; + + // and move all references + for (usz j = 0; j < tree.size(); j++) { + if (tree.refs[j] == old_ref) { + tree.refs[j] = free_spot; + } + } + + // mark the free spot as used + free_spot = -1; + } + } +} + +fn void! VTree.resize(&tree, usz newsize) +{ + // return error when shrinking with too many elements + if (newsize < tree.elements) { + return VTreeError.CANNOT_SHRINK?; + } + + // pack the vector when shrinking to avoid data loss + if ((int)newsize < tree.size()) { + // FIXME: packing destroys all references to elements of vec + // so shrinking may cause dangling pointers + return VTreeError.CANNOT_SHRINK?; + } + + usz old_size = tree.size(); + + tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.vector); } + + tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.refs); } + + tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize]; + defer catch { (void)mem::free(tree.ordered_refs); } + + if (newsize > tree.size()) { + tree.vector[old_size..newsize-1] = @zero(); + tree.refs[old_size..newsize-1] = -1; + } +} + +// add an element to the tree, return it's ref +fn isz! VTree.add(&tree, ElemType elem, isz parent) +{ + // invalid parent + if (!tree.ref_is_valid(parent)) { + return VTreeError.INVALID_REFERENCE?; + } + + // no space left + if (tree.elements >= tree.size()) { + return VTreeError.TREE_FULL?; + } + + // check if the parent exists + // if there are no elements in the tree the first add will set the root + if (!tree.ref_is_present(parent) && tree.elements != 0) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + // get the first free spot + isz free_spot = -1; + for (usz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == -1) { + free_spot = i; + break; + } + } + if (free_spot < 0) { + return VTreeError.TREE_FULL?; + } + + // finally add the element + tree.vector[free_spot] = elem; + tree.refs[free_spot] = parent; + tree.elements++; + + return free_spot; +} + +// prune the tree starting from the ref +// returns the number of pruned elements +fn usz! VTree.prune(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return 0; + } + + tree.vector[ref] = @zero(); + tree.refs[ref] = -1; + tree.elements--; + + usz count = 1; + for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) { + if (tree.refs[i] == ref) { + count += tree.prune(i)!; + } + } + + return count; +} + +// find the size of the subtree starting from ref +fn usz! VTree.subtree_size(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return 0; + } + + usz count = 1; + for (usz i = 0; i < tree.size(); i++) { + // only root has the reference to itself + if (tree.refs[i] == ref && ref != i) { + count += tree.subtree_size(i)!; + } + } + + return count; +} + +// iterate through the first level children, use a cursor like strtok_r +fn isz! VTree.children_it(&tree, isz parent, isz *cursor) +{ + if (cursor == null) { + return VTreeError.INVALID_ARGUMENT?; + } + + // if the cursor is out of bounds then we are done for sure + if (!tree.ref_is_valid(*cursor)) { + return VTreeError.INVALID_REFERENCE?; + } + + // same for the parent, if it's invalid it can't have children + if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { + return VTreeError.INVALID_REFERENCE?; + } + + // find the first child, update the cursor and return the ref + for (isz i = *cursor; i < tree.size(); i++) { + if (tree.refs[i] == parent) { + *cursor = i + 1; + return i; + } + } + + // if no children are found return -1 + *cursor = -1; + return -1; +} + +/* iterates trough every leaf of the subtree in the following manner + * node [x], x: visit order + * [0] + * / | \ + * / [2] [3] + * [1] | + * / \ [6] + * [4] [5] + */ +fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) +{ + if (cursor == null) { + return VTreeError.INVALID_ARGUMENT?; + } + + isz[] queue = tree.ordered_refs; + + // TODO: this could also be done when adding or removing elements + // first call, create a ref array ordered like we desire + if (*cursor == -1) { + *cursor = 0; + queue[..] = -1; + + // iterate through the queue appending found children + isz pos, off; + do { + // printf ("ref=%d\n", ref); + for (isz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == ref) { + queue[pos++] = i; + } + } + + for (; ref == queue[off] && off < tree.size(); off++); + ref = queue[off]; + + } while (tree.ref_is_valid(ref)); + // This line is why tree.ordered_refs has to be size+1 + queue[off + 1] = -1; + } + + // PRINT_ARR(queue, tree.size()); + // return -1; + + // on successive calls just iterate through the queue until we find an + // invalid ref, if the user set the cursor to -1 it means it has found what + // he needed, so free + if (*cursor < 0) { + return -1; + } else if (tree.ref_is_valid(*cursor)) { + return queue[(*cursor)++]; + } + + return -1; +} + +fn isz! VTree.parentof(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + return tree.refs[ref]; +} + +fn ElemType! VTree.get(&tree, isz ref) +{ + if (!tree.ref_is_valid(ref)) { + return VTreeError.INVALID_REFERENCE?; + } + + if (!tree.ref_is_present(ref)) { + return VTreeError.REFERENCE_NOT_PRESENT?; + } + + return tree.vector[ref]; +} + +fn void VTree.print(&tree) +{ + for (isz i = 0; i < tree.size(); i++) { + if (tree.refs[i] == -1) { + continue; + } + io::printf("[%d] {parent=%d, data=", i, tree.refs[i]); + io::print(tree.vector[i]); + io::printn("}"); + } +} diff --git a/stuff/generic_hash.h b/stuff/generic_hash.h deleted file mode 100644 index 1aa7601..0000000 --- a/stuff/generic_hash.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef _HASH_GENERIC -#define _HASH_GENERIC - -#include -#include -#include - - -// FIXME: change the api to just one HASH_DECL to HASH_PROTO and HASH_DEFINE - - -#define HASH_MAXSIZE 4096 -// for fibonacci hashing, 2^{32,64}/ -#define HASH_RATIO32 ((uint64_t)2654435769u) -#define HASH_RATIO64 ((uint64_t)11400714819322457583u) -// salt for string hashing -#define HASH_SALT ((uint64_t)0xbabb0cac) - - -/* Ready-made compares */ -static inline int hash_cmp_u32(uint32_t a, uint32_t b) { return a == b; } -static inline int hash_cmp_u64(uint64_t a, uint64_t b) { return a == b; } -static inline int hash_cmp_str(const char *a, const char *b) { return strcmp(a, b) == 0; } - - -/* Ready-made hashes */ -static inline uint32_t hash_u64(uint64_t c) -{ - return (uint64_t)((((uint64_t)c+HASH_SALT)*HASH_RATIO64)>>32); -} -static inline uint32_t hash_u32(uint32_t c) -{ - return (uint32_t)((((uint64_t)c<<31)*HASH_RATIO64)>>32); -} -static inline uint32_t hash_str(const char *s) -{ - uint32_t h = HASH_SALT; - const uint8_t *v = (const uint8_t *)(s); - for (int x = *s; x; x--) { - h += v[x-1]; - h += h << 10; - h ^= h >> 6; - } - h += h << 3; - h ^= h >> 11; - h += h << 15; - return h; -} - - -#define HASH_DECL(htname, codetype, datatype, hashfn, cmpfn) \ -struct htname##_entry { \ - codetype code; \ - datatype data; \ -}; \ -\ -struct htname##_ref { \ - uint32_t items, size, exp; \ - struct htname##_entry bucket[]; \ -}; \ -\ -\ -struct htname##_ref * htname##_create(uint32_t size) \ -{ \ - if (!size || size > HASH_MAXSIZE) \ - return NULL; \ - /* round to the greater power of two */ \ - /* FIXME: check for intger overflow here */ \ - uint32_t exp = 32-__builtin_clz(size-1); \ - size = 1<<(exp); \ - /* FIXME: check for intger overflow here */ \ - struct htname##_ref *ht = malloc(sizeof(struct htname##_ref)+sizeof(struct htname##_entry)*size); \ - if (ht) { \ - ht->items = 0; \ - ht->size = size; \ - ht->exp = exp; \ - memset(ht->bucket, 0, sizeof(struct htname##_entry)*size); \ - } \ - return ht; \ -} \ -\ -\ -void htname##_destroy(struct htname##_ref *ht) \ -{ \ - if (ht) free(ht); \ -} \ -\ -\ -static uint32_t htname##_lookup(struct htname##_ref *ht, uint32_t hash, uint32_t idx) \ -{ \ - if (!ht) return 0; \ - uint32_t mask = ht->size-1; \ - uint32_t step = (hash >> (32 - ht->exp)) | 1; \ - return (idx + step) & mask; \ -} \ -\ -\ -/* Find and return the element by code */ \ -struct htname##_entry * htname##_search(struct htname##_ref *ht, codetype code)\ -{ \ - if (!ht) return NULL; \ - uint32_t h = hashfn(code); \ - for (uint32_t i=h, x=0; ; x++) { \ - i = htname##_lookup(ht, h, i); \ - if (x > (ht->size<<1) || \ - !ht->bucket[i].code || \ - cmpfn(ht->bucket[i].code, code) \ - ) { \ - return &(ht->bucket[i]); \ - } \ - } \ - return NULL; \ -} \ -\ -\ -/* FIXME: this simply overrides the found item */ \ -struct htname##_entry * htname##_insert(struct htname##_ref *ht, struct htname##_entry *entry) \ -{ \ - struct htname##_entry *r = htname##_search(ht, entry->code); \ - if (r) { \ - if (!r->code) \ - ht->items++; \ - *r = *entry; \ - } \ - return r; \ -} \ -\ -\ -struct htname##_entry * htname##_remove(struct htname##_ref *ht, codetype code)\ -{ \ - if (!ht) return NULL; \ - struct htname##_entry *r = htname##_search(ht, code); \ - if (r) r->code = 0; \ - return r; \ -} \ - -#endif diff --git a/stuff/generic_stack.h b/stuff/generic_stack.h deleted file mode 100644 index b3d5f1e..0000000 --- a/stuff/generic_stack.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef _STACK_GENERIC_H -#define _STACK_GENERIC_H - - -#define STACK_STEP 8 -#define STACK_SALT 0xbabb0cac - -// FIXME: find a way to not re-hash the whole stack when removing one item - -// incremental hash for every grow -#if STACK_DISABLE_HASH -#define STACK_HASH(p, s, h) {} -#else -#define STACK_HASH(p, s, h) \ -{ \ - unsigned char *v = (unsigned char *)(p); \ - for (int x = (s); x; x--) { \ - (h) += v[x-1]; \ - (h) += (h) << 10; \ - (h) ^= (h) >> 6; \ - } \ - (h) += (h) << 3; \ - (h) ^= (h) >> 11; \ - (h) += (h) << 15; \ -} -#endif - - -// TODO: add a rolling hash -#define STACK_DECL(stackname, type) \ -struct stackname { \ - type *items; \ - int size, idx, old_idx; \ - unsigned int hash, old_hash; \ -}; \ -\ -\ -struct stackname stackname##_init(void) \ -{ \ - return (struct stackname){0, .hash = STACK_SALT}; \ -} \ -\ -\ -int stackname##_grow(struct stackname *stack, int step) \ -{ \ - if (!stack) \ - return -1; \ - stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \ - if(!stack->items) \ - return -1; \ - memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \ - stack->size += step; \ - return 0; \ -} \ -\ -\ -int stackname##_push(struct stackname *stack, type *e) \ -{ \ - if (!stack || !e) \ - return -1; \ - if (stack->idx >= stack->size) \ - if (stackname##_grow(stack, STACK_STEP)) \ - return -1; \ - stack->items[stack->idx++] = *e; \ - STACK_HASH(e, sizeof(type), stack->hash); \ - return 0; \ -} \ -\ -\ -type stackname##_pop(struct stackname *stack) \ -{ \ - if (!stack || stack->idx == 0 || stack->size == 0) \ - return (type){0}; \ - stack->hash = STACK_SALT; \ - STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \ - return stack->items[stack->idx--]; \ -} \ -\ -\ -int stackname##_clear(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - stack->old_idx = stack->idx; \ - stack->old_hash = stack->hash; \ - stack->hash = STACK_SALT; \ - stack->idx = 0; \ - return 0; \ -} \ -\ -\ -int stackname##_changed(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - return stack->hash != stack->old_hash; \ -} \ -\ -\ -int stackname##_size_changed(struct stackname *stack) \ -{ \ - if (!stack) \ - return -1; \ - return stack->size != stack->old_idx; \ -} \ -\ -\ -int stackname##_free(struct stackname *stack) \ -{ \ - if (stack) { \ - stackname##_clear(stack); \ - if (stack->items) \ - free(stack->items); \ - } \ - return 0; \ -} \ - -#endif diff --git a/stuff/main.c b/stuff/main.c deleted file mode 100755 index 764be50..0000000 --- a/stuff/main.c +++ /dev/null @@ -1,214 +0,0 @@ -//#!/usr/bin/tcc -run - -#include -#include -#include - - -#define HASH_SALT (0xbabb0cac) -#define HASH_RATIO64 ((unsigned long int)11400714819322457583u) - - -struct ntree_node -{ - unsigned int id; - const char *name; - int children_no, children_size; - struct ntree_node *children, *parent; -}; - - -static char tmp_name_buffer[16] = {"node:"}; - - -unsigned int hash_str_to_uint(const char *s) -{ - unsigned int h = HASH_SALT; - const unsigned char *v = (const unsigned char *)(s); - for (int x = *s; x; x--) { - h += v[x-1]; - h += h << 10; - h ^= h >> 6; - } - h += h << 3; - h ^= h >> 11; - h += h << 15; - return h; -} - - -unsigned int hash_u32(unsigned int c) -{ - return (unsigned int)((((unsigned long int)c<<31)*HASH_RATIO64)>>32); -} - - -const char * generate_new_name(void) -{ - static int count = 0; - unsigned int h = hash_u32(count++); - snprintf(tmp_name_buffer+sizeof("node:"), 16-sizeof("node:"), "%x", h); - return tmp_name_buffer; -} - - -int tree_prune(struct ntree_node *node) -{ - if (node == NULL) - return 0; - - if ((node->children_no == 0 && node->children != NULL) || - (node->children_no != 0 && node->children == NULL)) { - printf("ERR: (%x %s) Inconsistent node children", node->id, node->name); - } else for (int i = 0; i < node->children_no; i++) { - tree_prune(&(node->children[i])); - } - - if (node->children != NULL) { - free(node->children); - } - free((void *)node->name); - if (node->parent != NULL) - node->parent->children_no--; - *node = (struct ntree_node){0}; - - return 0; -} - - -struct ntree_node * tree_append(struct ntree_node *parent, const char *name) -{ - if (name == NULL) - return NULL; - if (strlen(name) == 0) - name = generate_new_name(); - - // generate the children information - unsigned int id = hash_str_to_uint(name); - char *str = malloc(strlen(name)+1); - if (str == NULL) - return NULL; - strcpy(str, name); - - // grow the parent buffer if necessary - if (parent->children_no >= parent->children_size) { - struct ntree_node *temp = NULL; - temp = realloc(parent->children, (parent->children_size+1)*sizeof(struct ntree_node)); - if (temp == NULL) { - free(str); - return NULL; - } - - parent->children = temp; - parent->children[parent->children_size] = (struct ntree_node){0}; - parent->children_size++; - } - - // find an open spot for the child - struct ntree_node *child = NULL; - for (int i = 0; i < parent->children_size; i++) { - if (parent->children[i].id == 0) - child = &(parent->children[i]); - } - if (child == NULL) - return NULL; - - child->name = str; - child->id = id; - child->children = NULL; - child->children_no = 0; - child->parent = parent; - parent->children_no++; - //printf("append to %s, children: %d\n", parent->name, parent->children_no); - return child; -} - - -struct ntree_node * tree_find_id(struct ntree_node *root, unsigned int id) -{ - if (id == 0 || root == NULL) - return NULL; - - if (root->id == id) - return root; - - else for (int i = 0; i < root->children_size; i++) { - if (root->children[i].id != 0 && tree_find_id(&(root->children[i]), id) != NULL) - return &(root->children[i]); - } - return NULL; -} - - -// TODO: add a foreach_child function -struct ntree_node * tree_find(struct ntree_node *root, const char *name) -{ - if (name == NULL || strlen(name) == 0 || root == NULL) - return NULL; - unsigned int id = hash_str_to_uint(name); - return tree_find_id(root, id); -} - - -int tree_size(struct ntree_node *root) -{ - if (root == NULL) - return 0; - - int count = root->children_no; - if (count > 0) { - for (int i = 0; i < root->children_size; i++) { - if (root->children[i].id != 0) - count += tree_size(&(root->children[i])); - } - } - - return count; -} - - -static int tree_print_ind(struct ntree_node *node, int dd) -{ - for (int i = 0; i < dd; i++) printf(" "); - printf("[%s]\n", node->name); - for (int i = 0; i < node->children_size; i++) { - if (node->children[i].id == 0) continue; - tree_print_ind(&(node->children[i]), dd+1); - } - return node->children_no; -} - - -int tree_print(struct ntree_node *root) -{ - if (root == NULL) - return 1; - - tree_print_ind(root, 0); - - return 0; -} - - -int main(void) -{ - char *root_name = malloc(sizeof("root")); - strcpy(root_name, "root"); - struct ntree_node root = {.name = root_name}; - struct ntree_node *n; - - n = tree_append(&root, "node 0:0"); - tree_append(n, "node 0:0:0"); - tree_append(&root, "node 0:1"); - tree_append(&root, "node 0:2"); - printf("Number of nodes %d\n", tree_size(&root)); - tree_print(&root); - - tree_prune(tree_find(&root, "node 0:0")); - printf("Number of nodes %d\n", tree_size(&root)); - - tree_prune(&root); - printf("Number of nodes %d\n", tree_size(&root)); - - return 0; -} diff --git a/stuff/vectree.h b/stuff/vectree.h deleted file mode 100644 index 949bee1..0000000 --- a/stuff/vectree.h +++ /dev/null @@ -1,348 +0,0 @@ -#ifndef _VECTREE_H -#define _VECTREE_H - -#ifdef VTREE_DTYPE - -typedef struct { - int size, elements; - VTREE_DTYPE *vector; - int *refs; -} Vtree; - -int vtree_init(Vtree *tree, unsigned int size); -int vtree_pack(Vtree *tree); -int vtree_resize(Vtree *tree, unsigned int newsize); -int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent); -int vtree_prune(Vtree *tree, int ref); -int vtree_subtree_size(Vtree *tree, int ref); -int vtree_children_it(Vtree *tree, int parent, int *cursor); -int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor); -int vtree_destroy(Vtree *tree); - -#ifdef VTREE_IMPL - -#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) -#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) - -int vtree_init(Vtree *tree, unsigned int size) -{ - if (tree == NULL) { - return -1; - } - - tree->vector = malloc(sizeof(VTREE_DTYPE) * size); - if (tree->vector == NULL) { - return -1; - } - - tree->refs = malloc(sizeof(int) * size); - if (tree->refs == NULL) { - free(tree->vector); - return -1; - } - - // set all refs to -1, meaning invalid (free) element - for (unsigned int i = 0; i < size; i++) { - tree->refs[i] = -1; - } - - // fill vector with zeroes - memset(tree->vector, 0, size * sizeof(VTREE_DTYPE)); - - tree->size = size; - tree->elements = 0; - - return 0; -} - -int vtree_destroy(Vtree *tree) -{ - if (tree == NULL) { - return -1; - } - - free(tree->vector); - free(tree->refs); - - return 0; -} - -int vtree_pack(Vtree *tree) -{ - if (tree == NULL) { - return -1; - } - - // TODO: add a PACKED flag to skip this - - int free_spot = -1; - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == -1) { - free_spot = i; - continue; - } - - // find a item that can be packed - if (free_spot >= 0 && tree->refs[i] >= 0) { - int old_ref = i; - - // move the item - tree->vector[free_spot] = tree->vector[i]; - tree->refs[free_spot] = tree->refs[i]; - - tree->vector[i] = (VTREE_DTYPE){0}; - tree->refs[i] = -1; - - // and move all references - for (int x = 0; x < tree->size; x++) { - if (tree->refs[x] == old_ref) { - tree->refs[x] = free_spot; - } - } - - // mark the free spot as used - free_spot = -1; - } - } - - return 0; -} - -int vtree_resize(Vtree *tree, unsigned int newsize) -{ - if (tree == NULL) { - return -1; - } - - // return error when shrinking with too many elements - if ((int)newsize < tree->elements) { - return -1; - } - - // pack the vector when shrinking to avoid data loss - if ((int)newsize < tree->size) { - //if (vtree_pack(tree) < 0) { - // return -1; - //} - // TODO: allow shrinking, since packing destroys all references - return -1; - } - - VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE)); - if (newvec == NULL) { - return -1; - } - - int *newrefs = realloc(tree->refs, newsize * sizeof(int)); - if (newrefs == NULL) { - return -1; - } - - tree->vector = newvec; - tree->refs = newrefs; - if ((int)newsize > tree->size) { - for (int i = tree->size; i < (int)newsize; i++) { - tree->vector[i] = (VTREE_DTYPE){0}; - tree->refs[i] = -1; - } - } - - tree->size = newsize; - - return 0; -} - -// add an element to the tree, return it's ref -int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent) -{ - if (tree == NULL) { - return -1; - } - - // invalid parent - if (!IS_VALID_REF(tree, parent)) { - return -1; - } - - // no space left - if (tree->elements >= tree->size) { - return -1; - } - - // check if the parent exists - // if there are no elements in the tree the first add will set the root - if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { - return -1; - } - - // get the first free spot - int free_spot = -1; - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == -1) { - free_spot = i; - break; - } - } - if (free_spot < 0) { - return -1; - } - - // finally add the element - tree->vector[free_spot] = elem; - tree->refs[free_spot] = parent; - tree->elements++; - - return free_spot; -} - -// prune the tree starting from the ref -// returns the number of pruned elements -int vtree_prune(Vtree *tree, int ref) -{ - if (tree == NULL) { - return -1; - } - - if (!IS_VALID_REF(tree, ref)) { - return -1; - } - - if (!REF_IS_PRESENT(tree, ref)) { - return 0; - } - - tree->vector[ref] = (VTREE_DTYPE){0}; - tree->refs[ref] = -1; - - int count = 1; - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == ref) { - count += vtree_prune(tree, i); - } - } - - return count; -} - -// find the size of the subtree starting from ref -int vtree_subtree_size(Vtree *tree, int ref) -{ - if (tree == NULL) { - return -1; - } - - if (!IS_VALID_REF(tree, ref)) { - return -1; - } - - if (!REF_IS_PRESENT(tree, ref)) { - return 0; - } - - int count = 1; - for (int i = 0; i < tree->size; i++) { - // only root has the reference to itself - if (tree->refs[i] == ref && ref != i) { - count += vtree_subtree_size(tree, i); - } - } - - return count; -} - -// iterate through the first level children, use a cursor like strtok_r -int vtree_children_it(Vtree *tree, int parent, int *cursor) -{ - if (tree == NULL || cursor == NULL) { - return -1; - } - - // if the cursor is out of bounds then we are done for sure - if (!IS_VALID_REF(tree, *cursor)) { - return -1; - } - - // same for the parent, if it's invalid it can't have children - if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { - return -1; - } - - // find the first child, update the cursor and return the ref - for (int i = *cursor; i < tree->size; i++) { - if (tree->refs[i] == parent) { - *cursor = i + 1; - return i; - } - } - - // if no children are found return -1 - *cursor = -1; - return -1; -} - -/* iterates trough every leaf of the subtree in the following manner - * node [x], x: visit order - * [0] - * / | \ - * / [2] [3] - * [1] | - * / \ [6] - * [4] [5] - */ -int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor) -{ - if (tree == NULL || queue_p == NULL || cursor == NULL) { - return -1; - } - - int *queue = *queue_p; - - // TODO: this could also be done when adding or removing elements - // first call, create a ref array ordered like we desire - if (queue == NULL) { - *cursor = 0; - // create a queue of invalid refs, size is the worst case - queue = malloc(sizeof(int) * tree->size); - if (queue == NULL) { - return -1; - } - for (int i = 0; i < tree->size; i++) { - queue[i] = -1; - } - *queue_p = queue; - - // iterate through the queue appending found children - int pos = 0, off = 0; - do { - //printf ("ref=%d\n", ref); - - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == ref) { - queue[pos++] = i; - } - } - - for (;ref == queue[off] && off < tree->size; off++); - ref = queue[off]; - - } while (IS_VALID_REF(tree, ref)); - } - - //PRINT_ARR(queue, tree->size); - //return -1; - - // on successive calls just iterate through the queue until we find an - // invalid ref - int ret = queue[(*cursor)++]; - if (!IS_VALID_REF(tree, ret)) { - free(queue); - } - - return ret; -} -#endif // VTREE_IMPL - -#endif // VTREE_DTYPE - -#endif - diff --git a/test/.gitkeep b/test/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/test_bitsruct.c3 b/test/test_bitsruct.c3 new file mode 100644 index 0000000..06d1722 --- /dev/null +++ b/test/test_bitsruct.c3 @@ -0,0 +1,14 @@ +bitstruct Bits : uint { + bool a : 0; + bool b : 1; + bool c : 2; +} + +fn int main() +{ + Bits a = {false, true, false}; + Bits b = {true, true, false}; + Bits c = a | b; + + return 0; +} diff --git a/test/test_union.c3 b/test/test_union.c3 new file mode 100644 index 0000000..df09af1 --- /dev/null +++ b/test/test_union.c3 @@ -0,0 +1,26 @@ + +struct CmdA { + int a, b; +} + +struct CmdB { + float a, b; +} + +union AnyCmd { + CmdA a; + CmdB b; +} + +struct Cmd { + int type; + AnyCmd cmd; +} + +fn int main() +{ + Cmd c; + c.type = 1; + c.cmd.a = {.a = 1, .b = 2}; + return 0; +} diff --git a/test/test_vtree.c3 b/test/test_vtree.c3 new file mode 100644 index 0000000..187ce91 --- /dev/null +++ b/test/test_vtree.c3 @@ -0,0 +1,7 @@ +import std::io; +import vtree; + +fn int main() +{ + return 0; +} diff --git a/timer.c b/timer.c deleted file mode 100644 index 03b7a4c..0000000 --- a/timer.c +++ /dev/null @@ -1,69 +0,0 @@ -#define _POSIX_C_SOURCE 200809l - -#include - -#include "timer.h" - -const clockid_t CLOCK_ID = CLOCK_PROCESS_CPUTIME_ID; - -struct _timer { - struct timespec start, stop; - struct timespec times[TIMER_MAX_PARTIAL]; -} timer = {0}; - -int timer_start(void) { return clock_gettime(CLOCK_ID, &timer.start); } - -int timer_reset(void) -{ - timer = (struct _timer) {0}; - return 0; -} - -int timer_stop(void) { return clock_gettime(CLOCK_ID, &timer.stop); } - -// partial clocks also update the stop time -int timer_partial(int idx) -{ - if (idx > TIMER_MAX_PARTIAL || idx < 0) { - return -1; - } - clock_gettime(CLOCK_ID, &timer.stop); - return clock_gettime(CLOCK_ID, &(timer.times[idx])); -} - -size_t timer_get_us(int idx) -{ - if (idx > TIMER_MAX_PARTIAL) { - return -1; - } - - struct timespec ts = {0}; - if (idx < 0) { - ts = timer.stop; - } else { - ts = timer.times[idx]; - } - ts.tv_sec -= timer.start.tv_sec; - ts.tv_nsec -= timer.start.tv_nsec; - - // FIXME: check overflow - return (ts.tv_nsec / 1000) + (ts.tv_sec * 1000000); -} - -double timer_get_sec(int idx) -{ - if (idx > TIMER_MAX_PARTIAL) { - return -1; - } - - struct timespec ts = {0}; - if (idx < 0) { - ts = timer.stop; - } else { - ts = timer.times[idx]; - } - ts.tv_sec -= timer.start.tv_sec; - ts.tv_nsec -= timer.start.tv_nsec; - - return (double)ts.tv_sec + ((double)ts.tv_nsec / 1e9); -} diff --git a/timer.h b/timer.h deleted file mode 100644 index 94e546b..0000000 --- a/timer.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef UG_TIMER_H_ -#define UG_TIMER_H_ - -#include - -#define TIMER_MAX_PARTIAL 10 - -int timer_start(void); -int timer_stop(void); -int timer_reset(void); -int timer_partial(int idx); - -size_t timer_get_us(int idx); -double timer_get_sec(int idx); - -#endif diff --git a/ugui.c b/ugui.c deleted file mode 100644 index 7f08025..0000000 --- a/ugui.c +++ /dev/null @@ -1,1061 +0,0 @@ -#define _DEFAULT_SOURCE - -#include -#include -#include -#include - -#include "raylib.h" -#include "ugui.h" -#include "timer.h" - -#define RGBA(u) \ - (UgColor) \ - { \ - .r = (u >> 24) & 0xff, .g = (u >> 16) & 0xff, .b = (u >> 8) & 0xff, \ - .a = (u >> 0) & 0xff \ - } -#define DIV_FILL \ - (UgRect) { .x = 0, .y = 0, .w = 0, .h = 0 } - -#define MARK() \ - do { \ - printf("================== lmao ==================\n"); \ - } while (0) - -#define BIT(x) (1 << (x)) -#define FTEST(e, f) ((e)->flags & (f)) -#define MAX(a, b) ((a) > (b) ? (a) : (b)) -#define MIN(a, b) ((a) < (b) ? (a) : (b)) -#define ABS(a) ((a) < 0 ? -(a) : (a)) -#define CLAMP(x, min, max) ((x) < (min) ? (min) : ((x) > (max) ? (max) : (x))) - -#define STACK_STEP 10 -#define MAX_ELEMS 128 -#define MAX_CMDS 256 -#define ROOT_ID 1 - -typedef struct _UgCmd { - enum { - CMD_RECT = 0, - } type; - - union { - struct { - UgRect rect; - UgColor color; - } rect; - }; -} UgCmd; - -typedef struct _UgFifo { - int size, in, out, count; - UgCmd *vec; -} UgFifo; - -int ug_fifo_init(UgFifo *fifo, uint32_t size); -int ug_fifo_free(UgFifo *fifo); -int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd); -int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd); - -int ug_fifo_init(UgFifo *fifo, uint32_t size) -{ - if (fifo == NULL) { - return -1; - } - - fifo->size = size; - fifo->in = 0; - fifo->out = 0; - fifo->count = 0; - fifo->vec = calloc(size, sizeof(UgCmd)); - - if (fifo->vec == NULL) { - return -1; - } - - return 0; -} - -int ug_fifo_free(UgFifo *fifo) -{ - if (fifo != NULL && fifo->vec != NULL) { - free(fifo->vec); - fifo->size = 0; - fifo->in = 0; - fifo->out = 0; - fifo->count = 0; - } - return 0; -} - -#define FIFO_STEP 10 - -int ug_fifo_enqueue(UgFifo *fifo, UgCmd *cmd) -{ - if (fifo == NULL || cmd == NULL) { - return -1; - } - - if (fifo->count >= fifo->size) { - // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + - // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; - // } - // fifo->vec = tmp; - // fifo->size += fifo_STEP; - return -1; - } - - fifo->vec[fifo->in] = *cmd; - - fifo->in = (fifo->in + 1) % fifo->size; - fifo->count++; - - return 0; -} - -int ug_fifo_dequeue(UgFifo *fifo, UgCmd *cmd) -{ - if (fifo == NULL || cmd == NULL) { - return -1; - } - - if (fifo->count <= 0) { - // UgCmd *tmp = reallocarray(fifo->vec, fifo->size + - // fifo_STEP, sizeof(UgCmd)); if (tmp == NULL) { return -1; - // } - // fifo->vec = tmp; - // fifo->size += fifo_STEP; - return -1; - } - - *cmd = fifo->vec[fifo->out]; - - fifo->out = (fifo->out + 1) % fifo->size; - fifo->count--; - - return 0; -} - -enum InputEventFlags { - INPUT_CTX_SIZE = 1 << 0, // window size was changed - INPUT_CTX_FOCUS = 1 << 1, // window focus changed - INPUT_CTX_MOUSEMOVE = 1 << 2, // mouse was moved - INPUT_CTX_MOUSEBTN = 1 << 3, // mouse button pressed or released -}; - -struct _UgCtx { - enum { - row = 0, - column, - floating, - } layout; - - UgTree tree; - UgElemCache cache; - UgFifo fifo; - - struct { - int width, height; - } size; - - struct { // css box model - UgRect padding; - UgRect border; - UgRect margin; - UgColor bgcolor; // background color - UgColor fgcolor; // foreground color - UgColor bcolor; // border color - } style; - - // input structure, it describes the events received between frames - struct { - // flags: lists which input events have been received - uint32_t flags; - int has_focus; - UgPoint mouse_pos, mouse_delta; - // mouse_down: bitmap of mouse buttons that are held - // mouse_updated: bitmap of mouse buttons that have been updated - // mouse_released = mouse_updated & ~mouse_down - // mouse_pressed = mouse_updated & mouse_down - uint32_t mouse_down; - uint32_t mouse_updated; - } input; - - int div_using; // tree node indicating the current active div -}; - -// layouting -int ug_layout_set_row(UgCtx *ctx); -int ug_layout_set_column(UgCtx *ctx); -int ug_layout_set_floating(UgCtx *ctx); -// these reset the offsets introduced by the previous elements -int ug_layout_next_row(UgCtx *ctx); -int ug_layout_next_column(UgCtx *ctx); - -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div); -int ug_div_end(UgCtx *ctx); -int ug_input_window_size(UgCtx *ctx, int width, int height); - -// static UgId djb2(const char *str); -static UgId FNV_1a(const char *str); - -int ug_button(UgCtx *ctx, const char *label, UgRect size); - -static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style); - -// search the element of the corresponding id in the cache, if no element is found -// insert a new one of that id. Return the pointer to the element -static int search_or_insert(UgCtx *ctx, UgElem **elem, UgId id) -{ - int is_new = 0; - uint32_t cache_idx; - - UgElem *c_elem = ug_cache_search(&ctx->cache, id); - if (c_elem == NULL) { - UgElem tmp = {.id = id}; - c_elem = ug_cache_insert_new(&ctx->cache, &tmp, &cache_idx); - is_new = 1; - } - - *elem = c_elem; - return is_new; -} - -static int is_point_in_rect(UgPoint p, UgRect r) -{ - if ((p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h)) { - return 1; - } - return 0; -} - -static int get_parent(UgCtx *ctx, UgElem **parent) -{ - // take a reference to the parent - // FIXME: if the tree held pointers to the elements then no more - // redundant cache search - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - return 0; -} - -int ug_init(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - *ctx = (UgCtx) {0}; - - ug_tree_init(&ctx->tree, MAX_ELEMS); - ug_fifo_init(&ctx->fifo, MAX_CMDS); - - ctx->cache = ug_cache_init(); - ctx->layout = row; - ctx->div_using = 0; - - // TODO: add style config - ctx->style.margin = (UgRect) {1, 1, 1, 1}; - ctx->style.padding = (UgRect) {0}; - ctx->style.border = (UgRect) {0}; - - ctx->style.bgcolor = (UgColor) {0}; - ctx->style.fgcolor = (UgColor) {0}; - ctx->style.bcolor = (UgColor) {0}; - - return 0; -} - -int ug_destroy(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - ug_tree_destroy(&ctx->tree); - ug_cache_free(&ctx->cache); - ug_fifo_free(&ctx->fifo); - - return 0; -} - -static void print_tree(UgCtx *ctx) -{ - printf("ctx->tree: ["); - for (int c = -1, x; (x = ug_tree_level_order_it(&ctx->tree, 0, &c)) != -1;) { - printf( - "[%d:%d,%.4lx], ", - x, - ug_tree_parentof(&ctx->tree, x), - ug_tree_get(&ctx->tree, x) & 0xffff - ); - } - printf("[-1]]\n"); -} - -int ug_frame_begin(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - // 1. Create the root div element - UgRect space = { - .x = 0, - .y = 0, - .w = ctx->size.width, - .h = ctx->size.height, - }; - UgElem root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .flags = 0, - .event = 0, - .rect = space, - .div = - { - .layout = DIV_LAYOUT_ROW, - .origin_r = {0}, - .origin_c = {0}, - .color_bg = {0}, - }, - }; - - // The root should have the updated flag only if the size of the window - // was changed between frames, this propagates an element size recalculation - // down the element tree - if (ctx->input.flags & INPUT_CTX_SIZE) { - root.flags |= ELEM_UPDATED; - } - - // if the window has focus then the root element also has focus, no other - // computation needed, child elements need to check the mouse positon and - // other stuff - if (ctx->input.has_focus) { - root.flags |= ELEM_HASFOCUS; - } - - // FIXME: check errors - // 2. Get the root element from the cache and update it - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, root.id); - - // flags always need to be set to the new flags - c_elem->flags = root.flags; - - if (is_new || FTEST(&root, ELEM_UPDATED)) { - *c_elem = root; - } - - // 3. Push the root element into the element tree - ctx->div_using = ug_tree_add(&ctx->tree, root.id, 0); - - if (ctx->div_using < 0) { - printf("why?\n"); - return -1; - } - - // print_tree(ctx); - - // The root element does not push anything to the stack - // TODO: add a background color taken from a theme or config - - printf("##### Frame Begin #####\n"); - - return 0; -} - -int ug_frame_end(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - // 1. clear the tree - ug_tree_prune(&ctx->tree, 0); - - // 2. clear input fields - ctx->input.flags = 0; - -// draw mouse position -#if 1 - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = - { - .x = ctx->input.mouse_pos.x - 2, - .y = ctx->input.mouse_pos.y - 2, - .w = 4, - .h = 4, - }, - .color = RGBA(0xff00ffff), - }, - }; - ug_fifo_enqueue(&(ctx->fifo), &cmd); -#endif - - printf("##### Frame End #####\n\n"); - return 0; -} - -// Window size was changed -int ug_input_window_size(UgCtx *ctx, int width, int height) -{ - if (ctx == NULL) { - return -1; - } - - if (width <= 0 || height <= 0) { - return -1; - } - - if (width >= 0) { - ctx->size.width = width; - } - if (height >= 0) { - ctx->size.height = height; - } - - ctx->input.flags |= INPUT_CTX_SIZE; - - return 0; -} - -// Window gained/lost focus -int ug_input_changefoucs(UgCtx *ctx, int has_focus) -{ - if (ctx == NULL) { - return -1; - } - - // FIXME: raylib only has an API to query the focus status so we have to - // update the input flag only if the focus changed - if (ctx->input.has_focus != (!!has_focus)) { - ctx->input.flags |= INPUT_CTX_FOCUS; - } - ctx->input.has_focus = !!has_focus; - - return 0; -} - -enum ug_mouse_buttons { - UG_BTN_LEFT = BIT(0), - UG_BTN_MIDDLE = BIT(1), - UG_BTN_RIGHT = BIT(2), - UG_BTN_4 = BIT(3), - UG_BTN_5 = BIT(4), -}; - -const uint32_t mouse_supported_mask = - UG_BTN_LEFT | UG_BTN_MIDDLE | UG_BTN_RIGHT | UG_BTN_4 | UG_BTN_5; - -// Mouse Button moved -int ug_input_mouse_button(UgCtx *ctx, uint32_t button_mask) -{ - if (ctx == NULL) { - return -1; - } - - if (button_mask & ~mouse_supported_mask) { - return -1; - } - - ctx->input.mouse_updated = ctx->input.mouse_down ^ button_mask; - ctx->input.mouse_down = button_mask; - ctx->input.flags |= INPUT_CTX_MOUSEBTN; - - printf( - "Mouse Down: %s%s%s%s%s\n", - button_mask & UG_BTN_LEFT ? "BTN_LEFT " : "", - button_mask & UG_BTN_RIGHT ? "BTN_RIGHT " : "", - button_mask & UG_BTN_MIDDLE ? "BTN_MIDDLE " : "", - button_mask & UG_BTN_4 ? "BTN_4 " : "", - button_mask & UG_BTN_5 ? "BTN_5 " : "" - ); - return 0; -} - -// Mouse was moved, report absolute position -int ug_input_mouse_abs(UgCtx *ctx, int x, int y) { return 1; } - -// Mouse was moved, report relative motion -int ug_input_mouse_delta(UgCtx *ctx, int dx, int dy) -{ - if (ctx == NULL) { - return -1; - } - - ctx->input.mouse_delta.x = dx; - ctx->input.mouse_delta.y = dy; - - int mx, my; - mx = ctx->input.mouse_pos.x + dx; - my = ctx->input.mouse_pos.y + dy; - - ctx->input.mouse_pos.x = CLAMP(mx, 0, ctx->size.width); - ctx->input.mouse_pos.y = CLAMP(my, 0, ctx->size.height); - - ctx->input.flags |= INPUT_CTX_MOUSEMOVE; - - return 0; -} - -static UgId FNV_1a(const char *str) -{ - const uint64_t fnv_off = 0xcbf29ce484222325; - const uint64_t fnv_prime = 0x100000001b3; - - uint64_t hash = fnv_off; - for (uint32_t c; (c = str[0]) != 0; str++) { - hash ^= c; - hash *= fnv_prime; - } - - return hash; -} - -#if 0 -static UgId djb2(const char *str) -{ - uint64_t hash = 5381; - uint32_t c; - - while ((c = (str++)[0]) != 0) { - hash = ((hash << 5) + hash) + c; - } /* hash * 33 + c */ - - return hash; -} -#endif - -int ug_div_begin(UgCtx *ctx, const char *label, UgRect div) -{ - if (ctx == NULL || label == NULL) { - return -1; - } - - UgId id = FNV_1a(label); - - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, id); - - // FIXME: why save the id in the tree and not something more direct like - // the element pointer or the index into the cache vector? - int div_node = ug_tree_add(&ctx->tree, id, ctx->div_using); - if (div_node < 0) { - // do something - printf("Error adding to tree\n"); - } - - UgElem *parent; - if (get_parent(ctx, &parent)) { - return -1; - } - - print_tree(ctx); - - // Use the current div - ctx->div_using = div_node; - - // 1. Fill the element fields - // this resets the flags - c_elem->type = ETYPE_DIV; - c_elem->flags = 0; - - // do layout and update flags only if the element was updated - if (is_new || FTEST(parent, ELEM_UPDATED)) { - // 2. layout the element - c_elem->rect = position_element(ctx, parent, div, 0); - - // 3. Mark the element as updated - c_elem->flags |= ELEM_UPDATED; - - // 4. Fill the div fields - c_elem->div.layout = parent->div.layout; - c_elem->div.origin_c = (UgPoint) { - .x = c_elem->rect.x, - .y = c_elem->rect.y, - }; - c_elem->div.color_bg = RGBA(0xff0000ff); - c_elem->div.origin_r = c_elem->div.origin_c; - } else if (FTEST(parent, ELEM_HASFOCUS)) { - if (is_point_in_rect(ctx->input.mouse_pos, c_elem->rect)) { - c_elem->flags |= ELEM_HASFOCUS; - } - } else { - // TODO: check active - // TODO: check scrollbars - // TODO: check resizeable - } - - // Add the background to the draw stack - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = c_elem->rect, - .color = c_elem->div.color_bg, - }, - }; - ug_fifo_enqueue(&ctx->fifo, &cmd); - - return 0; -} - -int ug_div_end(UgCtx *ctx) -{ - // the div_using returns to the parent of the current one - ctx->div_using = ug_tree_parentof(&ctx->tree, ctx->div_using); - return 0; -} - -// position the rectangle inside the parent according to the layout -static UgRect position_element(UgCtx *ctx, UgElem *parent, UgRect rect, int style) -{ - UgRect elem_rect = {0}; - UgPoint origin = {0}; - - // 1. Select the right origin - switch (parent->div.layout) { - case DIV_LAYOUT_ROW: - origin = parent->div.origin_r; - break; - case DIV_LAYOUT_COLUMN: - origin = parent->div.origin_c; - break; - case DIV_LAYOUT_FLOATING: // none - default: - // Error - break; - } - - // 2. Position the rect - elem_rect.x = origin.x + rect.x; - elem_rect.y = origin.y + rect.y; - - // 3. Calculate width & height - // TODO: what about negative values? - // FIXME: account for origin offset!! - elem_rect.w = rect.w > 0 ? rect.w : parent->rect.w; - elem_rect.h = rect.h > 0 ? rect.h : parent->rect.h; - - // 4. Update the origins of the parent - parent->div.origin_r = (UgPoint) { - .x = elem_rect.x + elem_rect.w, - .y = elem_rect.y, - }; - parent->div.origin_c = (UgPoint) { - .x = elem_rect.x, - .y = elem_rect.y + elem_rect.h, - }; - - // if using the style then apply margins - // FIXME: this does not work - if (style && parent->div.layout != DIV_LAYOUT_FLOATING) { - elem_rect.x += ctx->style.margin.x; - elem_rect.y += ctx->style.margin.y; - - // total keep-out borders - UgRect margin_tot = { - .x = ctx->style.padding.x + ctx->style.border.x + - ctx->style.margin.x, - .y = ctx->style.padding.y + ctx->style.border.y + - ctx->style.margin.y, - .w = ctx->style.padding.w + ctx->style.border.x + - ctx->style.margin.w, - .h = ctx->style.padding.h + ctx->style.border.x + - ctx->style.margin.h, - }; - - parent->div.origin_r.x += margin_tot.x + margin_tot.w; - // parent->div.origin_r.y += margin_tot.h; - - // parent->div.origin_c.x += margin_tot.w; - parent->div.origin_c.y += margin_tot.y + margin_tot.h; - } - - /* - printf( - "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) -> {%d %d %d - %d}\n", parent->id, rect.x, rect.y, rect.w, rect.h, parent->rect.x, - parent->rect.y, - parent->rect.w, - parent->rect.h, - elem_rect.x, - elem_rect.y, - elem_rect.w, - elem_rect.h - ); - */ - return elem_rect; -} - -int is_hovered(UgCtx *ctx, UgElem *elem) -{ - if (ctx == NULL || elem == NULL) { - return 0; - } - return is_point_in_rect(ctx->input.mouse_pos, elem->rect); -} - -int ug_button(UgCtx *ctx, const char *label, UgRect size) -{ - if (ctx == NULL || label == NULL) { - return -1; - } - - UgId id = FNV_1a(label); - - // TODO: do layouting if the element is new or the parent has updated - UgElem *c_elem; - int is_new = search_or_insert(ctx, &c_elem, id); - - // add it to the tree - ug_tree_add(&ctx->tree, id, ctx->div_using); - print_tree(ctx); - - UgElem *parent; - if (get_parent(ctx, &parent)) { - return -1; - } - - // 1. Fill the element fields - // this resets the flags - c_elem->type = ETYPE_BUTTON; - c_elem->flags = 0; - UgColor bg_color = RGBA(0x0000ffff); - - // if the element is new or the parent was updated then redo layout - if (is_new || FTEST(parent, ELEM_UPDATED)) { - // 2. Layout - c_elem->rect = position_element(ctx, parent, size, 1); - - // TODO: 3. Fill the button specific fields - } - - // TODO: Check for interactions - if (FTEST(parent, ELEM_HASFOCUS)) { - if (is_hovered(ctx, c_elem)) { - c_elem->flags |= ELEM_HASFOCUS; - c_elem->event |= EVENT_MOUSE_HOVER; - bg_color = RGBA(0x00ff00ff); - - if (ctx->input.mouse_down & UG_BTN_LEFT) { - c_elem->event |= EVENT_MOUSE_HOLD; - } else { - c_elem->event &= ~EVENT_MOUSE_HOLD; - } - } else { - c_elem->event &= ~EVENT_MOUSE_HOVER; - } - } - - // Draw the button - UgCmd cmd = { - .type = CMD_RECT, - .rect = - { - .rect = c_elem->rect, - .color = bg_color, - }, - }; - ug_fifo_enqueue(&ctx->fifo, &cmd); - - return c_elem->event; -} - -int ug_layout_set_row(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_ROW; - - return 0; -} - -int ug_layout_set_column(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_COLUMN; - - return 0; -} - -int ug_layout_set_floating(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.layout = DIV_LAYOUT_FLOATING; - - return 0; -} - -int ug_layout_next_row(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.origin_r = (UgPoint) { - .x = parent->rect.x, - .y = parent->div.origin_c.y, - }; - - parent->div.origin_c = parent->div.origin_r; - - return 0; -} - -int ug_layout_next_column(UgCtx *ctx) -{ - if (ctx == NULL) { - return -1; - } - - UgId parent_id = ug_tree_get(&ctx->tree, ctx->div_using); - UgElem *parent = ug_cache_search(&ctx->cache, parent_id); - if (parent == NULL) { - // Error, did you forget to do frame_begin()? - return -1; - } - - if (parent->type != ETYPE_DIV) { - // what? - return -1; - } - - parent->div.origin_c = (UgPoint) { - .x = parent->div.origin_r.x, - .y = parent->rect.y, - }; - - parent->div.origin_r = parent->div.origin_c; - - return 0; -} - -#if 1 -int main(void) -{ - UgCtx ctx; - ug_init(&ctx); - - int width = 800, height = 450; - SetConfigFlags(FLAG_WINDOW_RESIZABLE); - InitWindow(width, height, "Ugui Test"); - ug_input_window_size(&ctx, width, height); - - double median_times[10][4] = {0}; - size_t frame = 0; - double median_input, median_layout, median_draw, median_tot; - - // Main loop - while (!WindowShouldClose()) { - const int PARTIAL_INPUT = 0; - const int PARTIAL_LAYOUT = 1; - const int PARTIAL_DRAW = 2; - timer_start(); - - /*** Start Input Handling ***/ - if (IsWindowResized()) { - width = GetScreenWidth(); - height = GetScreenHeight(); - ug_input_window_size(&ctx, width, height); - } - - ug_input_changefoucs(&ctx, IsWindowFocused()); - - // FIXME: In raylib it doesn't seem to be a quick way to check if - // a mouse input event was received, so for now just use - // the delta information - Vector2 mousedelta = GetMouseDelta(); - if (mousedelta.x || mousedelta.y) { - ug_input_mouse_delta(&ctx, mousedelta.x, mousedelta.y); - } - - uint32_t mask = 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_LEFT) ? UG_BTN_LEFT : 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_RIGHT) ? UG_BTN_RIGHT : 0; - mask |= IsMouseButtonDown(MOUSE_BUTTON_MIDDLE) ? UG_BTN_MIDDLE : 0; - ug_input_mouse_button(&ctx, mask); - - timer_partial(PARTIAL_INPUT); - /*** End Input Handling ***/ - - /*** Start UI Handling ***/ - ug_frame_begin(&ctx); - - // main div, fill the whole window - ug_div_begin(&ctx, "main", DIV_FILL); - { - ug_layout_set_column(&ctx); - if (ug_button( - &ctx, - "button0", - (UgRect) {.y = 100, .x = 100, .w = 30, .h = 30} - ) & - EVENT_MOUSE_HOLD) { - printf("HOLDING button0\n"); - } - ug_layout_next_column(&ctx); - ug_button(&ctx, "button1", (UgRect) {.w = 30, .h = 30}); - ug_layout_next_column(&ctx); - ug_button(&ctx, "button2", (UgRect) {.w = 30, .h = 30}); - } - ug_div_end(&ctx); - - ug_frame_end(&ctx); - timer_partial(PARTIAL_LAYOUT); - /*** End UI Handling ***/ - - /*** Start UI Drawing ***/ - BeginDrawing(); - // ClearBackground(BLACK); - - printf("----- Draw Begin -----\n"); - Color c; - for (UgCmd cmd; ug_fifo_dequeue(&ctx.fifo, &cmd) >= 0;) { - switch (cmd.type) { - case CMD_RECT: - printf( - "draw rect x=%d y=%d w=%d h=%d\n", - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h - ); - c = (Color) { - .r = cmd.rect.color.r, - .g = cmd.rect.color.g, - .b = cmd.rect.color.b, - .a = cmd.rect.color.a, - }; - DrawRectangle( - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h, - c - ); - break; - default: - printf("Unknown cmd type: %d\n", cmd.type); - break; - } - } - printf("----- Draw End -----\n\n"); - - EndDrawing(); - timer_partial(PARTIAL_DRAW); - timer_stop(); - /*** End UI Drawing ***/ - - median_times[frame][PARTIAL_INPUT] = - 1e3 * timer_get_sec(PARTIAL_INPUT); - median_times[frame][PARTIAL_LAYOUT] = - 1e3 * timer_get_sec(PARTIAL_LAYOUT); - median_times[frame][PARTIAL_DRAW] = - 1e3 * timer_get_sec(PARTIAL_DRAW); - median_times[frame][3] = 1e3 * timer_get_sec(-1); - - frame += 1; - frame %= 10; - - if (frame == 0) { - median_input = 0; - median_layout = 0; - median_draw = 0; - median_tot = 0; - for (size_t i = 0; i < 10; i++) { - median_input += median_times[i][PARTIAL_INPUT]; - median_layout += median_times[i][PARTIAL_LAYOUT]; - median_draw += median_times[i][PARTIAL_DRAW]; - median_tot += median_times[i][3]; - } - median_input /= 10; - median_layout /= 10; - median_draw /= 10; - median_tot /= 10; - } - - printf("input time: %lfms\n", median_input); - printf("layout time: %lfms\n", median_layout); - printf("draw time: %lfms\n", median_draw); - printf("total time: %lfms\n", median_tot); - - // Throttle Frames - // TODO: add an fps limit, time frame generation and log it - const float TARGET_FPS = 100; - float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); - WaitTime(wait_time); - } - - CloseWindow(); - - ug_destroy(&ctx); - return 0; -} -#endif diff --git a/ugui.h b/ugui.h deleted file mode 100644 index c2ae79f..0000000 --- a/ugui.h +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef _UGUI_H -#define _UGUI_H - -#include - -typedef struct { - int32_t x, y, w, h; -} UgRect; - -typedef struct { - int32_t x, y; -} UgPoint; - -typedef struct { - uint8_t r, g, b, a; -} UgColor; - -typedef uint64_t UgId; - -typedef enum { - ETYPE_NONE = 0, - ETYPE_DIV, - ETYPE_BUTTON, -} UgElemType; - -enum UgElemFlags { - ELEM_UPDATED = 1 << 0, - ELEM_HASFOCUS = 1 << 1, -}; - -enum UgElemEvent { - EVENT_KEY_PRESS = 1 << 0, - EVENT_KEY_RELEASE = 1 << 1, - EVENT_KEY_HOLD = 1 << 2, - EVENT_MOUSE_HOVER = 1 << 3, - EVENT_MOUSE_PRESS = 1 << 4, - EVENT_MOUSE_RELEASE = 1 << 5, - EVENT_MOUSE_HOLD = 1 << 6, -}; - -typedef struct { - UgId id; - uint32_t flags; - uint32_t event; - UgRect rect; - UgElemType type; - - // type-specific fields - union { - struct UgDiv { - enum { - DIV_LAYOUT_ROW = 0, - DIV_LAYOUT_COLUMN, - DIV_LAYOUT_FLOATING, - } layout; - - UgPoint origin_r, origin_c; - UgColor color_bg; - } div; // Div - }; -} UgElem; - -// TODO: add a packed flag -// TODO: add a fill index to skip some searching for free spots - -typedef struct { - int size, elements; - UgId *vector; // vector of element ids - int *refs, *ordered_refs; -} UgTree; - -typedef struct { - struct _IdTable *table; - UgElem *array; - uint64_t *present, *used; - int cycles; -} UgElemCache; - -typedef struct _UgCtx UgCtx; - -// tree implementation -int ug_tree_init(UgTree *tree, unsigned int size); -int ug_tree_pack(UgTree *tree); -int ug_tree_resize(UgTree *tree, unsigned int newsize); -int ug_tree_add(UgTree *tree, UgId elem, int parent); -int ug_tree_prune(UgTree *tree, int ref); -int ug_tree_subtree_size(UgTree *tree, int ref); -int ug_tree_children_it(UgTree *tree, int parent, int *cursor); -int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); -int ug_tree_parentof(UgTree *tree, int node); -int ug_tree_destroy(UgTree *tree); -UgId ug_tree_get(UgTree *tree, int node); - -// cache implementation -UgElemCache ug_cache_init(void); -void ug_cache_free(UgElemCache *cache); -UgElem *ug_cache_search(UgElemCache *cache, UgId id); -UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index); - -int ug_init(UgCtx *ctx); -int ug_destroy(UgCtx *ctx); -int ug_frame_begin(UgCtx *ctx); -int ug_frame_end(UgCtx *ctx); - -#endif // _UGUI_H diff --git a/vectree.c b/vectree.c deleted file mode 100644 index ab58cd5..0000000 --- a/vectree.c +++ /dev/null @@ -1,355 +0,0 @@ -#include -#include - -#include "ugui.h" - -#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) -#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) - -int ug_tree_init(UgTree *tree, unsigned int size) -{ - if (tree == NULL) { - return -1; - } - - tree->vector = malloc(sizeof(UgId) * size); - if (tree->vector == NULL) { - return -1; - } - - tree->refs = malloc(sizeof(int) * size); - if (tree->refs == NULL) { - free(tree->vector); - return -1; - } - - // ordered refs are used in the iterators - tree->ordered_refs = malloc(sizeof(int) * (size + 1)); - if (tree->ordered_refs == NULL) { - free(tree->vector); - free(tree->refs); - return -1; - } - - // set all refs to -1, meaning invalid (free) element - for (unsigned int i = 0; i < size; i++) { - tree->refs[i] = -1; - } - - // fill vector with zeroes - memset(tree->vector, 0, size * sizeof(UgId)); - - tree->size = size; - tree->elements = 0; - - return 0; -} - -int ug_tree_destroy(UgTree *tree) -{ - if (tree == NULL) { - return -1; - } - - free(tree->vector); - free(tree->refs); - - return 0; -} - -int ug_tree_pack(UgTree *tree) -{ - if (tree == NULL) { - return -1; - } - - // TODO: add a PACKED flag to skip this - - int free_spot = -1; - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == -1) { - free_spot = i; - continue; - } - - // find a item that can be packed - if (free_spot >= 0 && tree->refs[i] >= 0) { - int old_ref = i; - - // move the item - tree->vector[free_spot] = tree->vector[i]; - tree->refs[free_spot] = tree->refs[i]; - - tree->vector[i] = 0; - tree->refs[i] = -1; - - // and move all references - for (int x = 0; x < tree->size; x++) { - if (tree->refs[x] == old_ref) { - tree->refs[x] = free_spot; - } - } - - // mark the free spot as used - free_spot = -1; - } - } - - return 0; -} - -int ug_tree_resize(UgTree *tree, unsigned int newsize) -{ - if (tree == NULL) { - return -1; - } - - // return error when shrinking with too many elements - if ((int)newsize < tree->elements) { - return -1; - } - - // pack the vector when shrinking to avoid data loss - if ((int)newsize < tree->size) { - // if (ug_tree_pack(tree) < 0) { - // return -1; - // } - // TODO: allow shrinking, since packing destroys all references - return -1; - } - - UgId *newvec = realloc(tree->vector, newsize * sizeof(UgId)); - if (newvec == NULL) { - return -1; - } - - int *newrefs = realloc(tree->refs, newsize * sizeof(int)); - if (newrefs == NULL) { - return -1; - } - - int *neworrefs = realloc(tree->ordered_refs, (newsize + 1) * sizeof(int)); - if (neworrefs == NULL) { - return -1; - } - - tree->vector = newvec; - tree->refs = newrefs; - tree->ordered_refs = neworrefs; - if ((int)newsize > tree->size) { - for (int i = tree->size; i < (int)newsize; i++) { - tree->vector[i] = 0; - tree->refs[i] = -1; - } - } - - tree->size = newsize; - - return 0; -} - -// add an element to the tree, return it's ref -int ug_tree_add(UgTree *tree, UgId elem, int parent) -{ - if (tree == NULL) { - return -1; - } - - // invalid parent - if (!IS_VALID_REF(tree, parent)) { - return -1; - } - - // no space left - if (tree->elements >= tree->size) { - return -1; - } - - // check if the parent exists - // if there are no elements in the tree the first add will set the root - if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { - return -1; - } - - // get the first free spot - int free_spot = -1; - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == -1) { - free_spot = i; - break; - } - } - if (free_spot < 0) { - return -1; - } - - // finally add the element - tree->vector[free_spot] = elem; - tree->refs[free_spot] = parent; - tree->elements++; - - return free_spot; -} - -// prune the tree starting from the ref -// returns the number of pruned elements -int ug_tree_prune(UgTree *tree, int ref) -{ - if (tree == NULL) { - return -1; - } - - if (!IS_VALID_REF(tree, ref)) { - return -1; - } - - if (!REF_IS_PRESENT(tree, ref)) { - return 0; - } - - tree->vector[ref] = 0; - tree->refs[ref] = -1; - tree->elements--; - - int count = 1; - for (int i = 0; tree->elements > 0 && i < tree->size; i++) { - if (tree->refs[i] == ref) { - count += ug_tree_prune(tree, i); - } - } - - return count; -} - -// find the size of the subtree starting from ref -int ug_tree_subtree_size(UgTree *tree, int ref) -{ - if (tree == NULL) { - return -1; - } - - if (!IS_VALID_REF(tree, ref)) { - return -1; - } - - if (!REF_IS_PRESENT(tree, ref)) { - return 0; - } - - int count = 1; - for (int i = 0; i < tree->size; i++) { - // only root has the reference to itself - if (tree->refs[i] == ref && ref != i) { - count += ug_tree_subtree_size(tree, i); - } - } - - return count; -} - -// iterate through the first level children, use a cursor like strtok_r -int ug_tree_children_it(UgTree *tree, int parent, int *cursor) -{ - if (tree == NULL || cursor == NULL) { - return -1; - } - - // if the cursor is out of bounds then we are done for sure - if (!IS_VALID_REF(tree, *cursor)) { - return -1; - } - - // same for the parent, if it's invalid it can't have children - if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { - return -1; - } - - // find the first child, update the cursor and return the ref - for (int i = *cursor; i < tree->size; i++) { - if (tree->refs[i] == parent) { - *cursor = i + 1; - return i; - } - } - - // if no children are found return -1 - *cursor = -1; - return -1; -} - -/* iterates trough every leaf of the subtree in the following manner - * node [x], x: visit order - * [0] - * / | \ - * / [2] [3] - * [1] | - * / \ [6] - * [4] [5] - */ -int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) -{ - if (tree == NULL || cursor == NULL) { - return -1; - } - - int *queue = tree->ordered_refs; - - // TODO: this could also be done when adding or removing elements - // first call, create a ref array ordered like we desire - if (*cursor == -1) { - *cursor = 0; - for (int i = 0; i < tree->size; i++) { - queue[i] = -1; - } - - // iterate through the queue appending found children - int pos = 0, off = 0; - do { - // printf ("ref=%d\n", ref); - - for (int i = 0; i < tree->size; i++) { - if (tree->refs[i] == ref) { - queue[pos++] = i; - } - } - - for (; ref == queue[off] && off < tree->size; off++) - ; - ref = queue[off]; - - } while (IS_VALID_REF(tree, ref)); - // This line is why tree->ordered_refs has to be size+1 - queue[off + 1] = -1; - } - - // PRINT_ARR(queue, tree->size); - // return -1; - - // on successive calls just iterate through the queue until we find an - // invalid ref, if the user set the cursor to -1 it means it has found what - // he needed, so free - if (*cursor < 0) { - return -1; - } else if (IS_VALID_REF(tree, *cursor)) { - return queue[(*cursor)++]; - } - - return -1; -} - -int ug_tree_parentof(UgTree *tree, int node) -{ - if (tree == NULL || !IS_VALID_REF(tree, node) || - !REF_IS_PRESENT(tree, node)) { - return -1; - } - return tree->refs[node]; -} - -UgId ug_tree_get(UgTree *tree, int node) -{ - if (tree == NULL || !IS_VALID_REF(tree, node)) { - return 0; - } - return tree->vector[node]; -} From f48151b38e06837301575de9fcdf22885386c83f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 31 Oct 2024 00:04:49 +0100 Subject: [PATCH 018/250] initial refactor --- src/main.c3 | 82 +++++-------------------- src/ugui_button.c3 | 48 +++++++++++++++ src/ugui_data.c3 | 1 + src/ugui_impl.c3 | 147 ++++++++++++--------------------------------- src/ugui_input.c3 | 61 ++++++++++++++++--- 5 files changed, 156 insertions(+), 183 deletions(-) create mode 100644 src/ugui_button.c3 diff --git a/src/main.c3 b/src/main.c3 index 989e1d7..350e05b 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -15,18 +15,15 @@ fn int main(String[] args) rl::init_window(width, height, "Ugui Test"); ctx.input_window_size(width, height)!!; - double[10][4] median_times; isz frame; - double median_input, median_layout, median_draw, median_tot; // Main loop while (!rl::window_should_close()) { const int PARTIAL_INPUT = 0; const int PARTIAL_LAYOUT = 1; const int PARTIAL_DRAW = 2; - //timer_start(); - /*** Start Input Handling ***/ + /* Start Input Handling */ if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); height = (short)rl::get_screen_height(); @@ -48,47 +45,40 @@ fn int main(String[] args) buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); ctx.input_mouse_button(buttons); + /* End Input Handling */ - //timer_partial(PARTIAL_INPUT); - /*** End Input Handling ***/ - - /*** Start UI Handling ***/ + /* Start UI Handling */ ctx.frame_begin()!!; // main div, fill the whole window ctx.div_begin("main", ugui::DIV_FILL)!!; {| ctx.layout_set_column()!!; - if (ctx.button("button0", ugui::Rect{.y = 100, .x = 100, .w = 30, .h = 30})!!.mouse_hold) { - io::printn("HOLDING button0"); + if (ctx.button("button0", ugui::Rect{100,100,30,30})!!.mouse_press) { + io::printn("press button0"); } ctx.layout_next_column()!!; - ctx.button("button1", ugui::Rect{.w = 30, .h = 30})!!; + if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { + io::printn("press button1"); + } ctx.layout_next_column()!!; - ctx.button("button2", ugui::Rect{.w = 30, .h = 30})!!; + if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + io::printn("release button2"); + } |}; ctx.div_end()!!; ctx.frame_end()!!; - //timer_partial(PARTIAL_LAYOUT); - /*** End UI Handling ***/ + /* End UI Handling */ - /*** Start UI Drawing ***/ + /* Start UI Drawing */ rl::begin_drawing(); // ClearBackground(BLACK); - io::printn("----- Draw Begin -----"); rl::Color c; for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: - io::printfn( - "draw rect x=%d y=%d w=%d h=%d", - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h - ); c = rl::Color{ .r = cmd.rect.color.r, .g = cmd.rect.color.g, @@ -106,52 +96,10 @@ fn int main(String[] args) io::printfn("Unknown cmd type: %d", cmd.type); } } - io::printf("----- Draw End -----\n\n"); rl::end_drawing(); - //timer_partial(PARTIAL_DRAW); - //timer_stop(); - /*** End UI Drawing ***/ - /* - median_times[frame][PARTIAL_INPUT] = - 1e3 * timer_get_sec(PARTIAL_INPUT); - median_times[frame][PARTIAL_LAYOUT] = - 1e3 * timer_get_sec(PARTIAL_LAYOUT); - median_times[frame][PARTIAL_DRAW] = - 1e3 * timer_get_sec(PARTIAL_DRAW); - median_times[frame][3] = 1e3 * timer_get_sec(-1); - */ - frame += 1; - frame %= 10; - /* - if (frame == 0) { - median_input = 0; - median_layout = 0; - median_draw = 0; - median_tot = 0; - for (size_t i = 0; i < 10; i++) { - median_input += median_times[i][PARTIAL_INPUT]; - median_layout += median_times[i][PARTIAL_LAYOUT]; - median_draw += median_times[i][PARTIAL_DRAW]; - median_tot += median_times[i][3]; - } - median_input /= 10; - median_layout /= 10; - median_draw /= 10; - median_tot /= 10; - } - printf("input time: %lfms\n", median_input); - printf("layout time: %lfms\n", median_layout); - printf("draw time: %lfms\n", median_draw); - printf("total time: %lfms\n", median_tot); - - // Throttle Frames - // TODO: add an fps limit, time frame generation and log it - const float TARGET_FPS = 100; - float wait_time = MAX((1.0 / TARGET_FPS) - timer_get_sec(-1), 0); - WaitTime(wait_time); - */ + // TODO: throttle FPS } rl::close_window(); @@ -160,6 +108,7 @@ fn int main(String[] args) return 0; } +/* fn void! test_vtree() @test { vtree::VTree() vt; @@ -194,3 +143,4 @@ fn void! test_cache() @test r = cc.get_or_insert(&&"Ciao Mamma", 1)!; assert(*r!! == "Ciao Mamma", "incorrect string"); } +*/ diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 new file mode 100644 index 0000000..20d4d9e --- /dev/null +++ b/src/ugui_button.c3 @@ -0,0 +1,48 @@ +module ugui; + +// draw a button, return the events on that button +fn ElemEvents! Ctx.button(&ctx, String label, Rect size) +{ + Id id = hash(label); + + Elem *parent = ctx.get_parent()!; + Elem *c_elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_BUTTON; + Color bg_color = uint_to_rgba(0x0000ffff); + + // if the element is new or the parent was updated then redo layout + if (c_elem.flags.is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + + // TODO: 3. Fill the button specific fields + } + + // TODO: Check for interactions + // FIXME: this is not how normal buttons work, usually focus follows both + // mouse hover and mouse down + c_elem.events = ctx.get_elem_events(c_elem); + if (parent.flags.has_focus) { + if (c_elem.events.mouse_hover) { + c_elem.flags.has_focus = true; + bg_color = uint_to_rgba(0x00ff00ff); + } + } + + // Draw the button + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 7883b62..178be3b 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -28,6 +28,7 @@ enum ElemType { bitstruct ElemFlags : uint { bool updated : 0; bool has_focus : 1; + bool is_new : 2; } bitstruct ElemEvents : uint { diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index f2d3dca..27b9efe 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -11,6 +11,19 @@ fn Elem*! Ctx.get_parent(&ctx) return ctx.cache.search(parent_id); } +// get or push an element from the cache, return a pointer to it +// resets all flags except is_new which is set accordingly +macro Ctx.get_elem(&ctx, Id id) +{ + Elem empty_elem; + bool is_new; + Elem* c_elem; + c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + c_elem.flags = (ElemFlags)0; + c_elem.flags.is_new = is_new; + return c_elem; +} + fn void! Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; @@ -39,56 +52,40 @@ fn void Ctx.free(&ctx) fn void! Ctx.frame_begin(&ctx) { - // 1. Create the root div element - // NOTE: in c3 everythong is zero initialized by default - Rect space = { - .w = ctx.width, - .h = ctx.height, - }; - - Elem root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .rect = space, - .div = { - .layout = LAYOUT_ROW, - }, - }; + // 2. Get the root element from the cache and update it + Elem* c_elem = ctx.get_elem(ROOT_ID)!; // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree - if (ctx.input.events.resize) { - root.flags.updated = true; - } - + c_elem.flags.updated = ctx.input.events.resize; // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff - if (ctx.has_focus) { - root.flags.has_focus = true; - } + c_elem.flags.has_focus = ctx.has_focus; - // FIXME: check errors - // 2. Get the root element from the cache and update it - bool is_new; - Elem empty_elem; - Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, root.id, &is_new)!; - // flags always need to be set to the new flags - c_elem.flags = root.flags; - if (is_new || root.flags.updated) { - *c_elem = root; + if (c_elem.flags.is_new || c_elem.flags.updated) { + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .rect = { + .w = ctx.width, + .h = ctx.height, + }, + .div = { + .layout = LAYOUT_ROW, + }, + .flags = c_elem.flags, + }; + + *c_elem = def_root; } // 3. Push the root element into the element tree - ctx.active_div = ctx.tree.add(root.id, 0)!; - - // print_tree(ctx); + ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; // The root element does not push anything to the stack // TODO: add a background color taken from a theme or config - - io::printn("##### Frame Begin #####"); } fn void! Ctx.frame_end(&ctx) @@ -113,36 +110,22 @@ $if 1: }; ctx.cmd_queue.enqueue(&cmd)!; $endif - - io::printn("##### Frame End #####"); } fn void! Ctx.div_begin(&ctx, String label, Rect size) { Id id = hash(label); - bool is_new; - Elem empty_elem; - Elem* c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; - - // FIXME: why save the id in the tree and not something more direct like - // the element pointer or the index into the cache vector? - isz div_node = ctx.tree.add(id, ctx.active_div)!; - Elem *parent = ctx.get_parent()!; - - ctx.tree.print(); - - // Use the current div + Elem* c_elem = ctx.get_elem(id)!; + isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; // 1. Fill the element fields - // this resets the flags c_elem.type = ETYPE_DIV; - c_elem.flags = (ElemFlags)0; // do layout and update flags only if the element was updated - if (is_new || parent.flags.updated) { + if (c_elem.flags.is_new || parent.flags.updated) { // 2. layout the element c_elem.rect = ctx.position_element(parent, size); @@ -183,62 +166,10 @@ fn void! Ctx.div_end(&ctx) ctx.active_div = ctx.tree.parentof(ctx.active_div)!; } -// @ensure elem != null +/** + * @ensure elem != null +**/ fn bool Ctx.is_hovered(&ctx, Elem *elem) { return point_in_rect(ctx.input.mouse.pos, elem.rect); } - -fn ElemEvents! Ctx.button(&ctx, String label, Rect size) -{ - Id id = hash(label); - - // TODO: do layouting if the element is new or the parent has updated - bool is_new; - Elem empty_elem; - Elem *c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; - - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - ctx.tree.print(); - - Elem *parent = ctx.get_parent()!; - - // 1. Fill the element fields - // this resets the flags - c_elem.type = ETYPE_BUTTON; - c_elem.flags = (ElemFlags)0; - Color bg_color = uint_to_rgba(0x0000ffff); - - // if the element is new or the parent was updated then redo layout - if (is_new || parent.flags.updated) { - // 2. Layout - c_elem.rect = ctx.position_element(parent, size, true); - - // TODO: 3. Fill the button specific fields - } - - // TODO: Check for interactions - if (parent.flags.has_focus) { - if (ctx.is_hovered(c_elem)) { - c_elem.flags.has_focus = true; - c_elem.events.mouse_hover = true; - bg_color = uint_to_rgba(0x00ff00ff); - c_elem.events.mouse_hold = ctx.input.mouse.down.btn_left; - } else { - c_elem.events.mouse_hover = false; - } - } - - // Draw the button - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.rect, - .color = bg_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; - - return c_elem.events; -} diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index d8c63f3..886865f 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -40,21 +40,64 @@ bitstruct MouseButtons : uint { bool btn_5 : 4; } +macro Ctx.mouse_pressed(&ctx) +{ + return ctx.input.mouse.updated & ctx.input.mouse.down; +} + +macro Ctx.mouse_released(&ctx) +{ + return ctx.input.mouse.updated & ~ctx.input.mouse.down; +} + +macro Ctx.mouse_down(&ctx) +{ + return ctx.input.mouse.down; +} + +const MouseButtons BTN_NONE = (MouseButtons)0u; +const MouseButtons BTN_ANY = (MouseButtons)(uint.max); +const MouseButtons BTN_LEFT = {.btn_left = true}; +const MouseButtons BTN_MIDDLE = {.btn_middle = true}; +const MouseButtons BTN_RIGHT = {.btn_right = true}; +const MouseButtons BTN_4 = {.btn_4 = true}; +const MouseButtons BTN_5 = {.btn_5 = true}; + +// FIXME: hthis compairson could be done with a cast using MouseButtons.inner +// property but I could not figure out how +macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) +{ + return (ctx.mouse_pressed() & btn) != BTN_NONE; +} + +macro Ctx.is_mouse_released(&ctx, MouseButtons btn) +{ + return (ctx.mouse_released() & btn) != BTN_NONE; +} + +macro Ctx.is_mouse_down(&ctx, MouseButtons btn) +{ + return (ctx.mouse_down() & btn) != BTN_NONE; +} + +macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) +{ + // TODO: add the other events + ElemEvents ev = { + .mouse_hover = ctx.is_hovered(elem), + .mouse_press = ctx.is_mouse_pressed(BTN_ANY), + .mouse_release = ctx.is_mouse_released(BTN_ANY), + .mouse_hold = ctx.is_mouse_down(BTN_ANY), + }; + return ev; +} + // Mouse Button moved fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) { ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; ctx.input.mouse.down = buttons; ctx.input.events.mouse_btn = true; - - io::printfn( - "Mouse Down: %s%s%s%s%s", - buttons.btn_left ? "BTN_LEFT " : "", - buttons.btn_right ? "BTN_RIGHT " : "", - buttons.btn_middle ? "BTN_MIDDLE " : "", - buttons.btn_4 ? "BTN_4 " : "", - buttons.btn_5 ? "BTN_5 " : "" - ); } // Mouse was moved, report absolute position From 28598f0575edc644b113509ff24d8bb5823a545b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 31 Oct 2024 13:04:30 +0100 Subject: [PATCH 019/250] slider --- src/main.c3 | 4 +++ src/ugui_button.c3 | 3 --- src/ugui_data.c3 | 9 +++++++ src/ugui_impl.c3 | 8 ++++++ src/ugui_input.c3 | 24 +++++++++++++---- src/ugui_slider.c3 | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 106 insertions(+), 8 deletions(-) create mode 100644 src/ugui_slider.c3 diff --git a/src/main.c3 b/src/main.c3 index 350e05b..0d961d6 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -65,6 +65,10 @@ fn int main(String[] args) if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } + if (ctx.slider_hor("slider", ugui::Rect{0,0,100,30})!!.update) { + ugui::Elem* e = ctx.get_elem_by_label("slider")!!; + io::printfn("slider: %f", e.slider.value); + } |}; ctx.div_end()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 20d4d9e..87f9d0f 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -23,9 +23,6 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size) // TODO: 3. Fill the button specific fields } - // TODO: Check for interactions - // FIXME: this is not how normal buttons work, usually focus follows both - // mouse hover and mouse down c_elem.events = ctx.get_elem_events(c_elem); if (parent.flags.has_focus) { if (c_elem.events.mouse_hover) { diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 178be3b..7abf1e7 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -23,6 +23,7 @@ enum ElemType { ETYPE_NONE, ETYPE_DIV, ETYPE_BUTTON, + ETYPE_SLIDER, } bitstruct ElemFlags : uint { @@ -39,6 +40,7 @@ bitstruct ElemEvents : uint { bool mouse_press : 4; bool mouse_release : 5; bool mouse_hold : 6; + bool update : 7; } enum DivLayout { @@ -54,6 +56,12 @@ struct Div { Color color_bg; } +// slider element +struct Slider { + float value; + Rect handle; +} + // element structure struct Elem { Id id; @@ -63,6 +71,7 @@ struct Elem { ElemType type; union { Div div; + Slider slider; } } diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 27b9efe..da6e210 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -24,6 +24,14 @@ macro Ctx.get_elem(&ctx, Id id) return c_elem; } +// this searches an element in the cache by label, it does not create a new element +// if it does't find one +macro Ctx.get_elem_by_label(&ctx, String label) +{ + Id id = hash(label); + return ctx.cache.search(id); +} + fn void! Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 886865f..b68cbfc 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -83,11 +83,13 @@ macro Ctx.is_mouse_down(&ctx, MouseButtons btn) macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { // TODO: add the other events + // FIXME: active should be elsewhere + bool active = elem.events.mouse_hold || ctx.is_hovered(elem); ElemEvents ev = { .mouse_hover = ctx.is_hovered(elem), - .mouse_press = ctx.is_mouse_pressed(BTN_ANY), - .mouse_release = ctx.is_mouse_released(BTN_ANY), - .mouse_hold = ctx.is_mouse_down(BTN_ANY), + .mouse_press = active & ctx.is_mouse_pressed(BTN_ANY), + .mouse_release = active & ctx.is_mouse_released(BTN_ANY), + .mouse_hold = active & ctx.is_mouse_down(BTN_ANY), }; return ev; } @@ -101,8 +103,20 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) } // Mouse was moved, report absolute position -// TODO: implement this -fn void Ctx.input_mouse_abs(&ctx, short x, short y) { return; } +fn void Ctx.input_mouse_abs(&ctx, short x, short y) +{ + ctx.input.mouse.pos.x = clamp(x, 0u16, ctx.width); + ctx.input.mouse.pos.y = clamp(y, 0u16, ctx.height); + + short dx, dy; + dx = x - ctx.input.mouse.pos.x; + dy = y - ctx.input.mouse.pos.y; + + ctx.input.mouse.delta.x = dx; + ctx.input.mouse.delta.y = dy; + + ctx.input.events.mouse_move = true; +} // Mouse was moved, report relative motion fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 new file mode 100644 index 0000000..3fea39d --- /dev/null +++ b/src/ugui_slider.c3 @@ -0,0 +1,66 @@ +module ugui; + +fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) +{ + Id id = hash(label); + + Elem *parent = ctx.get_parent()!; + Elem *c_elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + c_elem.type = ETYPE_SLIDER; + + // if the element is new or the parent was updated then redo layout + if (c_elem.flags.is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + /* handle + * +----+-----+---------------------+ + * | |#####| | + * +----+-----+---------------------+ + */ + c_elem.slider.handle = Rect{ + .x = (short)(c_elem.rect.x + (int)(c_elem.rect.w * c_elem.slider.value)), + .y = c_elem.rect.y, + .w = (short)(c_elem.rect.w * 0.25), + .h = c_elem.rect.h, + }; + } + + c_elem.events = ctx.get_elem_events(c_elem); + if (parent.flags.has_focus && c_elem.events.mouse_hover) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { + short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.rect.x, c_elem.rect.x + c_elem.rect.w - c_elem.slider.handle.w); + float v = (float)(c_elem.slider.handle.x-c_elem.rect.x) / (float)(c_elem.rect.w-c_elem.slider.handle.w); + c_elem.slider.handle.x = x; + c_elem.slider.value = v; + c_elem.events.update = true; + } + } + + // Draw the button + Color bg_color = uint_to_rgba(0x0000ffff); + Color handle_color = uint_to_rgba(0x0ff000ff); + + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + cmd = Cmd{ + .type = CMD_RECT, + .rect = { + .rect = c_elem.slider.handle, + .color = handle_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} From 1cad13e59753ba63dd8b15f9ab8e523c4a38eccd Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 2 Nov 2024 09:41:37 +0100 Subject: [PATCH 020/250] vertical slider --- TODO | 6 ++++ src/main.c3 | 2 +- src/ugui_slider.c3 | 82 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 84 insertions(+), 6 deletions(-) create mode 100644 TODO diff --git a/TODO b/TODO new file mode 100644 index 0000000..6d2312a --- /dev/null +++ b/TODO @@ -0,0 +1,6 @@ +# TODOs, semi-random sorting +[ ] Implement glyph draw command +[ ] Implement div.view and scrollbars +[ ] Port font system from C to C3 (rewrite1) +[ ] Update ARCHITECTURE.md +[ ] Write a README.md diff --git a/src/main.c3 b/src/main.c3 index 0d961d6..8f9bd51 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -65,7 +65,7 @@ fn int main(String[] args) if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } - if (ctx.slider_hor("slider", ugui::Rect{0,0,100,30})!!.update) { + if (ctx.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { ugui::Elem* e = ctx.get_elem_by_label("slider")!!; io::printfn("slider: %f", e.slider.value); } diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 3fea39d..7984b2e 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -1,5 +1,10 @@ module ugui; +/* handle + * +----+-----+---------------------+ + * | |#####| | + * +----+-----+---------------------+ + */ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) { Id id = hash(label); @@ -16,11 +21,6 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) if (c_elem.flags.is_new || parent.flags.updated) { // 2. Layout c_elem.rect = ctx.position_element(parent, size, true); - /* handle - * +----+-----+---------------------+ - * | |#####| | - * +----+-----+---------------------+ - */ c_elem.slider.handle = Rect{ .x = (short)(c_elem.rect.x + (int)(c_elem.rect.w * c_elem.slider.value)), .y = c_elem.rect.y, @@ -64,3 +64,75 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) return c_elem.events; } + +/* + * +-+ + * | | + * | | + * +-+ + * |#| handle + * |#| + * +-+ + * | | + * | | + * +-+ + */ +fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) +{ + Id id = hash(label); + + Elem *parent = ctx.get_parent()!; + Elem *c_elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + c_elem.type = ETYPE_SLIDER; + + // if the element is new or the parent was updated then redo layout + if (c_elem.flags.is_new || parent.flags.updated) { + // 2. Layout + c_elem.rect = ctx.position_element(parent, size, true); + c_elem.slider.handle = Rect{ + .x = c_elem.rect.x, + .y = (short)(c_elem.rect.y + (int)(c_elem.rect.h * c_elem.slider.value)), + .w = c_elem.rect.w, + .h = (short)(c_elem.rect.h * 0.25), + }; + } + + c_elem.events = ctx.get_elem_events(c_elem); + if (parent.flags.has_focus && c_elem.events.mouse_hover) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { + short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.rect.y, c_elem.rect.y + c_elem.rect.h - c_elem.slider.handle.h); + float v = (float)(c_elem.slider.handle.y-c_elem.rect.y) / (float)(c_elem.rect.h-c_elem.slider.handle.h); + c_elem.slider.handle.y = y; + c_elem.slider.value = v; + c_elem.events.update = true; + } + } + + // Draw the button + Color bg_color = uint_to_rgba(0x0000ffff); + Color handle_color = uint_to_rgba(0x0ff000ff); + + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.rect, + .color = bg_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + cmd = Cmd{ + .type = CMD_RECT, + .rect = { + .rect = c_elem.slider.handle, + .color = handle_color, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + return c_elem.events; +} From 8bc38452b3c5ed05771fc16a4cb51b2fb996b6a4 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 2 Nov 2024 09:44:53 +0100 Subject: [PATCH 021/250] renamed elem.rect to elem.bounds --- src/ugui_button.c3 | 4 ++-- src/ugui_data.c3 | 2 +- src/ugui_impl.c3 | 18 +++++++++--------- src/ugui_layout.c3 | 16 ++++++++-------- src/ugui_slider.c3 | 32 ++++++++++++++++---------------- 5 files changed, 36 insertions(+), 36 deletions(-) diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 87f9d0f..d601813 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -18,7 +18,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size) // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { // 2. Layout - c_elem.rect = ctx.position_element(parent, size, true); + c_elem.bounds = ctx.position_element(parent, size, true); // TODO: 3. Fill the button specific fields } @@ -35,7 +35,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size) Cmd cmd = { .type = CMD_RECT, .rect = { - .rect = c_elem.rect, + .rect = c_elem.bounds, .color = bg_color, }, }; diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 7abf1e7..3d9d208 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -67,7 +67,7 @@ struct Elem { Id id; ElemFlags flags; ElemEvents events; - Rect rect; + Rect bounds; ElemType type; union { Div div; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index da6e210..57da04f 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -74,9 +74,9 @@ fn void! Ctx.frame_begin(&ctx) if (c_elem.flags.is_new || c_elem.flags.updated) { Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .rect = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .bounds = { .w = ctx.width, .h = ctx.height, }, @@ -135,20 +135,20 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) // do layout and update flags only if the element was updated if (c_elem.flags.is_new || parent.flags.updated) { // 2. layout the element - c_elem.rect = ctx.position_element(parent, size); + c_elem.bounds = ctx.position_element(parent, size); // 3. Mark the element as updated c_elem.flags.updated = true; // 4. Fill the div fields c_elem.div.layout = parent.div.layout; c_elem.div.origin_c = Point{ - .x = c_elem.rect.x, - .y = c_elem.rect.y, + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, }; c_elem.div.color_bg = uint_to_rgba(0xff0000ff); c_elem.div.origin_r = c_elem.div.origin_c; } else if (parent.flags.has_focus) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.rect)) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { c_elem.flags.has_focus = true; } } else { @@ -161,7 +161,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) Cmd cmd = { .type = CMD_RECT, .rect = { - .rect = c_elem.rect, + .rect = c_elem.bounds, .color = c_elem.div.color_bg, }, }; @@ -179,5 +179,5 @@ fn void! Ctx.div_end(&ctx) **/ fn bool Ctx.is_hovered(&ctx, Elem *elem) { - return point_in_rect(ctx.input.mouse.pos, elem.rect); + return point_in_rect(ctx.input.mouse.pos, elem.bounds); } diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 5c38c17..5b60602 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -51,7 +51,7 @@ fn void! Ctx.layout_next_row(&ctx) } parent.div.origin_r = Point{ - .x = parent.rect.x, + .x = parent.bounds.x, .y = parent.div.origin_c.y, }; parent.div.origin_c = parent.div.origin_r; @@ -69,7 +69,7 @@ fn void! Ctx.layout_next_column(&ctx) parent.div.origin_c = Point{ .x = parent.div.origin_r.x, - .y = parent.rect.y, + .y = parent.bounds.y, }; parent.div.origin_r = parent.div.origin_c; } @@ -98,8 +98,8 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // 3. Calculate width & height // TODO: what about negative values? // FIXME: account for origin offset!! - elem_rect.w = rect.w > 0 ? rect.w : parent.rect.w; - elem_rect.h = rect.h > 0 ? rect.h : parent.rect.h; + elem_rect.w = rect.w > 0 ? rect.w : parent.bounds.w; + elem_rect.h = rect.h > 0 ? rect.h : parent.bounds.h; // 4. Update the origins of the parent parent.div.origin_r = Point{ @@ -139,10 +139,10 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) /* printf( "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d - %d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.rect.x, - parent.rect.y, - parent.rect.w, - parent.rect.h, + %d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.bounds.x, + parent.bounds.y, + parent.bounds.w, + parent.bounds.h, elem_rect.x, elem_rect.y, elem_rect.w, diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 7984b2e..c88e002 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -20,20 +20,20 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { // 2. Layout - c_elem.rect = ctx.position_element(parent, size, true); + c_elem.bounds = ctx.position_element(parent, size, true); c_elem.slider.handle = Rect{ - .x = (short)(c_elem.rect.x + (int)(c_elem.rect.w * c_elem.slider.value)), - .y = c_elem.rect.y, - .w = (short)(c_elem.rect.w * 0.25), - .h = c_elem.rect.h, + .x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w * c_elem.slider.value)), + .y = c_elem.bounds.y, + .w = (short)(c_elem.bounds.w * 0.25), + .h = c_elem.bounds.h, }; } c_elem.events = ctx.get_elem_events(c_elem); if (parent.flags.has_focus && c_elem.events.mouse_hover) { if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.rect.x, c_elem.rect.x + c_elem.rect.w - c_elem.slider.handle.w); - float v = (float)(c_elem.slider.handle.x-c_elem.rect.x) / (float)(c_elem.rect.w-c_elem.slider.handle.w); + short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w); + float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w); c_elem.slider.handle.x = x; c_elem.slider.value = v; c_elem.events.update = true; @@ -47,7 +47,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) Cmd cmd = { .type = CMD_RECT, .rect = { - .rect = c_elem.rect, + .rect = c_elem.bounds, .color = bg_color, }, }; @@ -92,20 +92,20 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { // 2. Layout - c_elem.rect = ctx.position_element(parent, size, true); + c_elem.bounds = ctx.position_element(parent, size, true); c_elem.slider.handle = Rect{ - .x = c_elem.rect.x, - .y = (short)(c_elem.rect.y + (int)(c_elem.rect.h * c_elem.slider.value)), - .w = c_elem.rect.w, - .h = (short)(c_elem.rect.h * 0.25), + .x = c_elem.bounds.x, + .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h * c_elem.slider.value)), + .w = c_elem.bounds.w, + .h = (short)(c_elem.bounds.h * 0.25), }; } c_elem.events = ctx.get_elem_events(c_elem); if (parent.flags.has_focus && c_elem.events.mouse_hover) { if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.rect.y, c_elem.rect.y + c_elem.rect.h - c_elem.slider.handle.h); - float v = (float)(c_elem.slider.handle.y-c_elem.rect.y) / (float)(c_elem.rect.h-c_elem.slider.handle.h); + short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h); + float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h); c_elem.slider.handle.y = y; c_elem.slider.value = v; c_elem.events.update = true; @@ -119,7 +119,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) Cmd cmd = { .type = CMD_RECT, .rect = { - .rect = c_elem.rect, + .rect = c_elem.bounds, .color = bg_color, }, }; From 250a0fb3b5e85caeaea574757c5dec178f974667 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 7 Nov 2024 18:35:20 +0100 Subject: [PATCH 022/250] initial work on scrollable divs --- src/main.c3 | 13 ++++++++-- src/ugui_data.c3 | 16 ++++++++++++ src/ugui_div.c3 | 64 ++++++++++++++++++++++++++++++++++++++++++++++ src/ugui_impl.c3 | 56 +--------------------------------------- src/ugui_layout.c3 | 38 +++++++++------------------ 5 files changed, 104 insertions(+), 83 deletions(-) create mode 100644 src/ugui_div.c3 diff --git a/src/main.c3 b/src/main.c3 index 8f9bd51..93dfbb8 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -51,10 +51,10 @@ fn int main(String[] args) ctx.frame_begin()!!; // main div, fill the whole window - ctx.div_begin("main", ugui::DIV_FILL)!!; + ctx.div_begin("main", ugui::Rect{.w=ctx.width/2})!!; {| ctx.layout_set_column()!!; - if (ctx.button("button0", ugui::Rect{100,100,30,30})!!.mouse_press) { + if (ctx.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button0"); } ctx.layout_next_column()!!; @@ -72,6 +72,15 @@ fn int main(String[] args) |}; ctx.div_end()!!; + ctx.div_begin("second", ugui::DIV_FILL)!!; + {| + if (ctx.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { + ugui::Elem* e = ctx.get_elem_by_label("slider_other")!!; + io::printfn("other slider: %f", e.slider.value); + } + |}; + ctx.div_end()!!; + ctx.frame_end()!!; /* End UI Handling */ diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 3d9d208..834eeff 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -192,3 +192,19 @@ macro point_in_rect(Point p, Rect r) { return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); } + +// return true if rect a contains b +macro rect_contains(Rect a, Rect b) +{ + return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); +} + +macro rect_intersection(Rect a, Rect b) +{ + return Rect{ + .x = (short)max(a.x, b.x), + .y = (short)max(a.y, b.y), + .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), + .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), + }; +} diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 new file mode 100644 index 0000000..6980a47 --- /dev/null +++ b/src/ugui_div.c3 @@ -0,0 +1,64 @@ +module ugui; + + +fn void! Ctx.div_begin(&ctx, String label, Rect size) +{ + Id id = hash(label); + + Elem *parent = ctx.get_parent()!; + Elem* c_elem = ctx.get_elem(id)!; + isz div_node = ctx.tree.add(id, ctx.active_div)!; + ctx.active_div = div_node; + + // 1. Fill the element fields + c_elem.type = ETYPE_DIV; + + // do layout and update flags only if the element was updated + if (c_elem.flags.is_new || parent.flags.updated) { + // 2. layout the element + c_elem.bounds = ctx.position_element(parent, size); + + // 3. Mark the element as updated + c_elem.flags.updated = true; + // 4. Fill the div fields + c_elem.div.layout = parent.div.layout; + c_elem.div.origin_c = Point{ + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, + }; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.origin_r = c_elem.div.origin_c; + } + + // Add the background to the draw stack + Cmd cmd = { + .type = CMD_RECT, + .rect = { + .rect = c_elem.bounds, + .color = c_elem.div.color_bg, + }, + }; + ctx.cmd_queue.enqueue(&cmd)!; + + // TODO: check active + // TODO: check resizeable + + // TODO: check scrollbars + // if the bounds are outside of the view then allocate space for scrollbars + DivLayout old_layout = c_elem.div.layout; + c_elem.div.layout = LAYOUT_FLOATING; + c_elem.div.layout = old_layout; + + if (parent.flags.has_focus) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { + c_elem.flags.has_focus = true; + } + } + +} + +fn void! Ctx.div_end(&ctx) +{ + // the active_div returns to the parent of the current one + ctx.active_div = ctx.tree.parentof(ctx.active_div)!; +} diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 57da04f..5a84e42 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -81,7 +81,7 @@ fn void! Ctx.frame_begin(&ctx) .h = ctx.height, }, .div = { - .layout = LAYOUT_ROW, + .layout = LAYOUT_ROW, }, .flags = c_elem.flags, }; @@ -120,60 +120,6 @@ $if 1: $endif } -fn void! Ctx.div_begin(&ctx, String label, Rect size) -{ - Id id = hash(label); - - Elem *parent = ctx.get_parent()!; - Elem* c_elem = ctx.get_elem(id)!; - isz div_node = ctx.tree.add(id, ctx.active_div)!; - ctx.active_div = div_node; - - // 1. Fill the element fields - c_elem.type = ETYPE_DIV; - - // do layout and update flags only if the element was updated - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. layout the element - c_elem.bounds = ctx.position_element(parent, size); - - // 3. Mark the element as updated - c_elem.flags.updated = true; - // 4. Fill the div fields - c_elem.div.layout = parent.div.layout; - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - c_elem.div.color_bg = uint_to_rgba(0xff0000ff); - c_elem.div.origin_r = c_elem.div.origin_c; - } else if (parent.flags.has_focus) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { - c_elem.flags.has_focus = true; - } - } else { - // TODO: check active - // TODO: check scrollbars - // TODO: check resizeable - } - - // Add the background to the draw stack - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.bounds, - .color = c_elem.div.color_bg, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; -} - -fn void! Ctx.div_end(&ctx) -{ - // the active_div returns to the parent of the current one - ctx.active_div = ctx.tree.parentof(ctx.active_div)!; -} - /** * @ensure elem != null **/ diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 5b60602..c3416f6 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -77,7 +77,7 @@ fn void! Ctx.layout_next_column(&ctx) // position the rectangle inside the parent according to the layout fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { - Rect elem_rect; + Rect bounds; Point origin; // 1. Select the right origin @@ -92,30 +92,29 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) } // 2. Position the rect - elem_rect.x = origin.x + rect.x; - elem_rect.y = origin.y + rect.y; + bounds.x = (short)max(origin.x + rect.x, 0); + bounds.y = (short)max(origin.y + rect.y, 0); // 3. Calculate width & height - // TODO: what about negative values? // FIXME: account for origin offset!! - elem_rect.w = rect.w > 0 ? rect.w : parent.bounds.w; - elem_rect.h = rect.h > 0 ? rect.h : parent.bounds.h; + bounds.w = rect.w > 0 ? rect.w : parent.bounds.w; + bounds.h = rect.h > 0 ? rect.h : parent.bounds.h; // 4. Update the origins of the parent parent.div.origin_r = Point{ - .x = elem_rect.x + elem_rect.w, - .y = elem_rect.y, + .x = bounds.x + bounds.w, + .y = bounds.y, }; parent.div.origin_c = Point{ - .x = elem_rect.x, - .y = elem_rect.y + elem_rect.h, + .x = bounds.x, + .y = bounds.y + bounds.h, }; // if using the style then apply margins // FIXME: this does not work if (style && parent.div.layout != LAYOUT_FLOATING) { - elem_rect.x += ctx.style.margin.x; - elem_rect.y += ctx.style.margin.y; + bounds.x += ctx.style.margin.x; + bounds.y += ctx.style.margin.y; // total keep-out borders Rect margin_tot = { @@ -136,18 +135,5 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) parent.div.origin_c.y += margin_tot.y + margin_tot.h; } - /* - printf( - "positioning rect: %lx {%d %d %d %d}(%d %d %d %d) . {%d %d %d - %d}\n", parent.id, rect.x, rect.y, rect.w, rect.h, parent.bounds.x, - parent.bounds.y, - parent.bounds.w, - parent.bounds.h, - elem_rect.x, - elem_rect.y, - elem_rect.w, - elem_rect.h - ); - */ - return elem_rect; + return bounds; } From 763e9ba8d61b6a59ae2ac499d219eb0b0b9b8513 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 14 Nov 2024 23:42:20 +0100 Subject: [PATCH 023/250] correct placement and box model --- src/main.c3 | 6 +-- src/ugui_data.c3 | 11 ++++++ src/ugui_div.c3 | 7 ++++ src/ugui_impl.c3 | 6 +++ src/ugui_layout.c3 | 97 +++++++++++++++++++++++++++------------------- 5 files changed, 85 insertions(+), 42 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 93dfbb8..b64da0b 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -53,15 +53,15 @@ fn int main(String[] args) // main div, fill the whole window ctx.div_begin("main", ugui::Rect{.w=ctx.width/2})!!; {| - ctx.layout_set_column()!!; + ctx.layout_set_row()!!; if (ctx.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button0"); } - ctx.layout_next_column()!!; + //ctx.layout_next_column()!!; if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button1"); } - ctx.layout_next_column()!!; + //ctx.layout_next_column()!!; if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 834eeff..34bbb9a 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -52,6 +52,11 @@ enum DivLayout { // div element struct Div { DivLayout layout; + bool can_scroll_x; + bool can_scroll_y; + isz vertical_scroll_bar; + isz horizontal_scroll_bar; + Rect children_bounds; Point origin_r, origin_c; Color color_bg; } @@ -208,3 +213,9 @@ macro rect_intersection(Rect a, Rect b) .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), }; } + +// rect intersection not null +macro rect_collision(Rect a, Rect b) +{ + return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); +} diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 6980a47..1ecb386 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -17,6 +17,9 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) if (c_elem.flags.is_new || parent.flags.updated) { // 2. layout the element c_elem.bounds = ctx.position_element(parent, size); + if (c_elem.flags.is_new) { + c_elem.div.children_bounds = c_elem.bounds; + } // 3. Mark the element as updated c_elem.flags.updated = true; @@ -28,6 +31,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) }; c_elem.div.color_bg = uint_to_rgba(0xff0000ff); c_elem.div.origin_r = c_elem.div.origin_c; + c_elem.div.can_scroll_x = false; + c_elem.div.can_scroll_y = false; + c_elem.div.vertical_scroll_bar = -1; + c_elem.div.horizontal_scroll_bar = -1; } // Add the background to the draw stack diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 5a84e42..c916b40 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -32,6 +32,12 @@ macro Ctx.get_elem_by_label(&ctx, String label) return ctx.cache.search(id); } +macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private +{ + Id id = ctx.tree.get(ctx.active_div)!; + return ctx.cache.search(id); +} + fn void! Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index c3416f6..a8e47e5 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -75,9 +75,12 @@ fn void! Ctx.layout_next_column(&ctx) } // position the rectangle inside the parent according to the layout +// parent: parent div +// rect: the requested size +// style: apply style fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { - Rect bounds; + Rect placement; Point origin; // 1. Select the right origin @@ -91,49 +94,65 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // Error } - // 2. Position the rect - bounds.x = (short)max(origin.x + rect.x, 0); - bounds.y = (short)max(origin.y + rect.y, 0); + // the bottom-right border of the element box + Point pl_corner; - // 3. Calculate width & height - // FIXME: account for origin offset!! - bounds.w = rect.w > 0 ? rect.w : parent.bounds.w; - bounds.h = rect.h > 0 ? rect.h : parent.bounds.h; + // 2. Calculate the placement + placement.x = (short)max(origin.x + rect.x, 0); + placement.y = (short)max(origin.y + rect.y, 0); + placement.w = rect.w > 0 ? rect.w : parent.bounds.w - (placement.x - parent.bounds.x); + placement.h = rect.h > 0 ? rect.h : parent.bounds.h - (placement.y - parent.bounds.y); - // 4. Update the origins of the parent - parent.div.origin_r = Point{ - .x = bounds.x + bounds.w, - .y = bounds.y, - }; - parent.div.origin_c = Point{ - .x = bounds.x, - .y = bounds.y + bounds.h, - }; + pl_corner.x = placement.x + placement.w; + pl_corner.y = placement.y + placement.h; - // if using the style then apply margins - // FIXME: this does not work - if (style && parent.div.layout != LAYOUT_FLOATING) { - bounds.x += ctx.style.margin.x; - bounds.y += ctx.style.margin.y; + // 2.1 apply style, css box model + if (style) { + Rect margin = ctx.style.margin; + Rect border = ctx.style.border; + Rect padding = ctx.style.padding; - // total keep-out borders - Rect margin_tot = { - .x = ctx.style.padding.x + ctx.style.border.x + - ctx.style.margin.x, - .y = ctx.style.padding.y + ctx.style.border.y + - ctx.style.margin.y, - .w = ctx.style.padding.w + ctx.style.border.x + - ctx.style.margin.w, - .h = ctx.style.padding.h + ctx.style.border.x + - ctx.style.margin.h, - }; + placement.x += margin.x; + placement.y += margin.y; + if (rect.w != 0) { placement.w += margin.x+margin.w + padding.x+padding.w; } + if (rect.h != 0) { placement.h += margin.y+margin.h + padding.y+padding.h; } - parent.div.origin_r.x += margin_tot.x + margin_tot.w; - // parent.div.origin_r.y += margin_tot.h; - - // parent.div.origin_c.x += margin_tot.w; - parent.div.origin_c.y += margin_tot.y + margin_tot.h; + pl_corner.x = placement.x + placement.w + margin.w; + pl_corner.y = placement.y + placement.h + margin.h; } - return bounds; + // 3. Update the origins of the parent + parent.div.origin_r = Point{ + .x = pl_corner.x, + .y = origin.y, + }; + parent.div.origin_c = Point{ + .x = origin.x, + .y = pl_corner.y, + }; + + // 4. Calculate the "scrolled" view + Point off; + if (parent.div.can_scroll_x && parent.div.horizontal_scroll_bar != -1) { + Elem*! sx = ctx.get_elem_by_tree_idx(parent.div.horizontal_scroll_bar); + if (catch sx) { return Rect{}; } + // TODO: assert that the element is a slider + off.x = (short)(parent.div.children_bounds.w * sx.slider.value); + } + if (parent.div.can_scroll_y && parent.div.vertical_scroll_bar != -1) { + Elem*! sy = ctx.get_elem_by_tree_idx(parent.div.vertical_scroll_bar); + if (catch sy) { return Rect{}; } + // TODO: assert that the element is a slider + off.y = (short)(parent.div.children_bounds.h * sy.slider.value); + } + Rect view = { + .x = parent.bounds.x + off.x, + .y = parent.bounds.y + off.y, + .w = parent.bounds.w, + .h = parent.bounds.h, + }; + + // TODO: 5. check if the placement is inside the view + + return placement; } From 73bc933eb5095d7c14dfbdea0c1b740a7503b29f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 17 Nov 2024 21:36:46 +0100 Subject: [PATCH 024/250] semi-working vertical slider --- src/main.c3 | 74 +++++++++++++++++++++++--------------------- src/ugui_button.c3 | 2 ++ src/ugui_data.c3 | 32 +++++++++++-------- src/ugui_div.c3 | 76 +++++++++++++++++++++++++++++++++++++++++----- src/ugui_impl.c3 | 30 ++++++++++-------- src/ugui_layout.c3 | 57 ++++++++++++++++++++++------------ src/ugui_slider.c3 | 4 ++- 7 files changed, 188 insertions(+), 87 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index b64da0b..ed5d5b4 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -6,14 +6,15 @@ import rl; fn int main(String[] args) { - ugui::Ctx ctx; - ctx.init()!!; + ugui::Ctx ui; + ui.init()!!; short width = 800; short height = 450; rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); rl::init_window(width, height, "Ugui Test"); - ctx.input_window_size(width, height)!!; + ui.input_window_size(width, height)!!; + rl::set_target_fps(30); isz frame; @@ -27,61 +28,68 @@ fn int main(String[] args) if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); height = (short)rl::get_screen_height(); - ctx.input_window_size(width, height)!!; + ui.input_window_size(width, height)!!; } - ctx.input_changefocus(rl::is_window_focused()); + ui.input_changefocus(rl::is_window_focused()); - // FIXME: In raylib it doesn't seem to be a quick way to check if - // a mouse input event was received, so for now just use - // the delta information - rl::Vector2 mousedelta = rl::get_mouse_delta(); - if (mousedelta.x || mousedelta.y) { - ctx.input_mouse_delta((short)mousedelta.x, (short)mousedelta.y); - } + rl::Vector2 mpos = rl::get_mouse_position(); + ui.input_mouse_abs((short)mpos.x, (short)mpos.y); ugui::MouseButtons buttons; buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); - ctx.input_mouse_button(buttons); + ui.input_mouse_button(buttons); /* End Input Handling */ /* Start UI Handling */ - ctx.frame_begin()!!; + ui.frame_begin()!!; +/* // main div, fill the whole window - ctx.div_begin("main", ugui::Rect{.w=ctx.width/2})!!; + ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; {| - ctx.layout_set_row()!!; - if (ctx.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { + ui.layout_set_row()!!; + if (ui.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button0"); } - //ctx.layout_next_column()!!; - if (ctx.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { + //ui.layout_next_column()!!; + if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button1"); } - //ctx.layout_next_column()!!; - if (ctx.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + //ui.layout_next_column()!!; + if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } - if (ctx.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ctx.get_elem_by_label("slider")!!; + if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { + ugui::Elem* e = ui.get_elem_by_label("slider")!!; io::printfn("slider: %f", e.slider.value); } |}; - ctx.div_end()!!; - - ctx.div_begin("second", ugui::DIV_FILL)!!; + ui.div_end()!!; +*/ + ui.div_begin("second", ugui::DIV_FILL)!!; + ugui::Elem* de = ui.get_elem_by_label("second")!!; + de.div.scroll.can_y = true; {| - if (ctx.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ctx.get_elem_by_label("slider_other")!!; + ui.layout_set_column()!!; + if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { + ugui::Elem* e = ui.get_elem_by_label("slider_other")!!; io::printfn("other slider: %f", e.slider.value); } + ui.button("button10", ugui::Rect{0,0,50,50})!!; + ui.button("button11", ugui::Rect{0,0,50,50})!!; + ui.button("button12", ugui::Rect{0,0,50,50})!!; + ui.button("button13", ugui::Rect{0,0,50,50})!!; + ui.button("button14", ugui::Rect{0,0,50,50})!!; + ui.button("button15", ugui::Rect{0,0,50,50})!!; + ui.button("button16", ugui::Rect{0,0,50,50})!!; + ui.button("button17", ugui::Rect{0,0,50,50})!!; |}; - ctx.div_end()!!; + ui.div_end()!!; - ctx.frame_end()!!; + ui.frame_end()!!; /* End UI Handling */ /* Start UI Drawing */ @@ -89,7 +97,7 @@ fn int main(String[] args) // ClearBackground(BLACK); rl::Color c; - for (Cmd* cmd; (cmd = ctx.cmd_queue.dequeue() ?? null) != null;) { + for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: c = rl::Color{ @@ -111,13 +119,11 @@ fn int main(String[] args) } rl::end_drawing(); - - // TODO: throttle FPS } rl::close_window(); - ctx.free(); + ui.free(); return 0; } diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index d601813..8d7aac4 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -1,5 +1,7 @@ module ugui; +import std::io; + // draw a button, return the events on that button fn ElemEvents! Ctx.button(&ctx, String label, Rect size) { diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 34bbb9a..7fd9c90 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -1,9 +1,13 @@ module ugui; +import std::io; +import std::core::string; + import vtree; import cache; import fifo; + struct Rect { short x, y, w, h; } @@ -52,10 +56,14 @@ enum DivLayout { // div element struct Div { DivLayout layout; - bool can_scroll_x; - bool can_scroll_y; - isz vertical_scroll_bar; - isz horizontal_scroll_bar; + struct scroll { + bool can_x; + bool can_y; + bool on_x; + bool on_y; + float value_x; + float value_y; + } Rect children_bounds; Point origin_r, origin_c; Color color_bg; @@ -201,21 +209,21 @@ macro point_in_rect(Point p, Rect r) // return true if rect a contains b macro rect_contains(Rect a, Rect b) { - return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); + return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); } macro rect_intersection(Rect a, Rect b) { - return Rect{ - .x = (short)max(a.x, b.x), - .y = (short)max(a.y, b.y), - .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), - .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), - }; + return Rect{ + .x = (short)max(a.x, b.x), + .y = (short)max(a.y, b.y), + .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), + .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), + }; } // rect intersection not null macro rect_collision(Rect a, Rect b) { - return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); + return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); } diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 1ecb386..45d8387 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -1,5 +1,6 @@ module ugui; +import std::io; fn void! Ctx.div_begin(&ctx, String label, Rect size) { @@ -29,18 +30,18 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) .x = c_elem.bounds.x, .y = c_elem.bounds.y, }; - c_elem.div.color_bg = uint_to_rgba(0xff0000ff); c_elem.div.origin_r = c_elem.div.origin_c; - c_elem.div.can_scroll_x = false; - c_elem.div.can_scroll_y = false; - c_elem.div.vertical_scroll_bar = -1; - c_elem.div.horizontal_scroll_bar = -1; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.scroll.can_x = false; + c_elem.div.scroll.can_y = false; + c_elem.div.scroll.value_x = 0; + c_elem.div.scroll.value_y = 0; } // Add the background to the draw stack Cmd cmd = { - .type = CMD_RECT, - .rect = { + .type = CMD_RECT, + .rect = { .rect = c_elem.bounds, .color = c_elem.div.color_bg, }, @@ -50,7 +51,66 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) // TODO: check active // TODO: check resizeable - // TODO: check scrollbars + // check and draw scroll bars + if (!rect_contains(c_elem.bounds, c_elem.div.children_bounds)) { + Point cbc = { + .x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w, + .y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h, + }; + Point bc = { + .x = c_elem.bounds.x + c_elem.bounds.w, + .y = c_elem.bounds.y + c_elem.bounds.h, + }; + // vertical overflow, check and draw scroll bar + if (cbc.y > bc.y && c_elem.div.scroll.can_y) { + // set the scrollbar flag, is used in layout + c_elem.div.scroll.on_y = true; + + Rect vslider = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = c_elem.bounds.y, + .w = 10, + .h = c_elem.bounds.h, + }; + + float vh = max((float)bc.y / cbc.y, (float)0.15); + Rect vhandle = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1-vh) * c_elem.div.scroll.value_y)), + .w = 10, + .h = (short)(c_elem.bounds.h * vh), + }; + + c_elem.events = ctx.get_elem_events(c_elem); + if (parent.flags.has_focus && c_elem.events.mouse_hover && c_elem.events.mouse_hold && point_in_rect(ctx.input.mouse.pos, vhandle)) { + short y = (short)clamp(ctx.input.mouse.pos.y - vhandle.h/2, vslider.y, vslider.y + vslider.h - vhandle.h); + vhandle.y = y; + float v = (float)(vhandle.y-vslider.y) / (float)(vslider.h-vhandle.h); + c_elem.div.scroll.value_y = v; + c_elem.flags.updated = true; + c_elem.div.origin_c = Point{ + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, + }; + c_elem.div.origin_r = c_elem.div.origin_c; + } + + Cmd scrl = { + .type = CMD_RECT, + .rect.rect = vslider, + .rect.color = uint_to_rgba(0x999999ff), + }; + ctx.cmd_queue.enqueue(&scrl)!; + + scrl.rect.rect = vhandle; + scrl.rect.color = uint_to_rgba(0x9999ffff); + ctx.cmd_queue.enqueue(&scrl)!; + + } else { + c_elem.div.scroll.on_y = false; + } + } + // if the bounds are outside of the view then allocate space for scrollbars DivLayout old_layout = c_elem.div.layout; c_elem.div.layout = LAYOUT_FLOATING; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index c916b40..17a2b04 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -79,20 +79,24 @@ fn void! Ctx.frame_begin(&ctx) c_elem.flags.has_focus = ctx.has_focus; if (c_elem.flags.is_new || c_elem.flags.updated) { - Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .bounds = { - .w = ctx.width, - .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - }, - .flags = c_elem.flags, - }; + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .bounds = { + .w = ctx.width, + .h = ctx.height, + }, + .div = { + .layout = LAYOUT_ROW, + .children_bounds = { + .w = ctx.width, + .h = ctx.height, + } + }, + .flags = c_elem.flags, + }; - *c_elem = def_root; + *c_elem = def_root; } // 3. Push the root element into the element tree diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index a8e47e5..aec6b1e 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -78,17 +78,22 @@ fn void! Ctx.layout_next_column(&ctx) // parent: parent div // rect: the requested size // style: apply style +<* +@require ctx != null +@require parent.type == ETYPE_DIV +*> fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { Rect placement; Point origin; + Div* div = &parent.div; // 1. Select the right origin - switch (parent.div.layout) { + switch (div.layout) { case LAYOUT_ROW: - origin = parent.div.origin_r; + origin = div.origin_r; case LAYOUT_COLUMN: - origin = parent.div.origin_c; + origin = div.origin_c; case LAYOUT_FLOATING: // none default: // Error @@ -100,8 +105,8 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // 2. Calculate the placement placement.x = (short)max(origin.x + rect.x, 0); placement.y = (short)max(origin.y + rect.y, 0); - placement.w = rect.w > 0 ? rect.w : parent.bounds.w - (placement.x - parent.bounds.x); - placement.h = rect.h > 0 ? rect.h : parent.bounds.h - (placement.y - parent.bounds.y); + placement.w = rect.w > 0 ? rect.w : (short)max(parent.bounds.w - (placement.x - parent.bounds.x), 0); + placement.h = rect.h > 0 ? rect.h : (short)max(parent.bounds.h - (placement.y - parent.bounds.y), 0); pl_corner.x = placement.x + placement.w; pl_corner.y = placement.y + placement.h; @@ -122,28 +127,23 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) } // 3. Update the origins of the parent - parent.div.origin_r = Point{ + div.origin_r = Point{ .x = pl_corner.x, .y = origin.y, }; - parent.div.origin_c = Point{ + div.origin_c = Point{ .x = origin.x, .y = pl_corner.y, }; // 4. Calculate the "scrolled" view Point off; - if (parent.div.can_scroll_x && parent.div.horizontal_scroll_bar != -1) { - Elem*! sx = ctx.get_elem_by_tree_idx(parent.div.horizontal_scroll_bar); - if (catch sx) { return Rect{}; } - // TODO: assert that the element is a slider - off.x = (short)(parent.div.children_bounds.w * sx.slider.value); + Rect* cb = &div.children_bounds; + if (div.scroll.can_x && div.scroll.on_x) { + off.x = (short)(cb.w * div.scroll.value_x); } - if (parent.div.can_scroll_y && parent.div.vertical_scroll_bar != -1) { - Elem*! sy = ctx.get_elem_by_tree_idx(parent.div.vertical_scroll_bar); - if (catch sy) { return Rect{}; } - // TODO: assert that the element is a slider - off.y = (short)(parent.div.children_bounds.h * sy.slider.value); + if (div.scroll.can_y && div.scroll.on_y) { + off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y); } Rect view = { .x = parent.bounds.x + off.x, @@ -152,7 +152,26 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) .h = parent.bounds.h, }; - // TODO: 5. check if the placement is inside the view + // 5. check if the placement overflows the children ounds, if so update them + if (!point_in_rect(pl_corner, *cb)) { + if (pl_corner.y > cb.y+cb.h) { + cb.h = pl_corner.y - cb.y; + } + if (pl_corner.x > cb.x+cb.w) { + cb.w += pl_corner.x - (cb.x + cb.w); + } + } - return placement; + // 6. check if the placement is inside the view + if (rect_collision(placement, view)) { + return Rect{ + .x = placement.x - off.x, + .y = placement.y - off.y, + .w = placement.w, + .h = placement.h, + }; + } else { + return Rect{}; + } } + diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index c88e002..a32524a 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -1,5 +1,7 @@ module ugui; +import std::io; + /* handle * +----+-----+---------------------+ * | |#####| | @@ -79,7 +81,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) */ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = hash(label); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; From fa3362cc665eac427a81e0cc4b9939e2fbb6954d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 20 Nov 2024 17:12:01 +0100 Subject: [PATCH 025/250] wait for events to reduce cpu usage --- src/main.c3 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.c3 b/src/main.c3 index ed5d5b4..5440370 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -14,7 +14,8 @@ fn int main(String[] args) rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); rl::init_window(width, height, "Ugui Test"); ui.input_window_size(width, height)!!; - rl::set_target_fps(30); + rl::set_target_fps(60); + rl::enable_event_waiting(); isz frame; From 04dff26067e84106a2939a230590b9825a8df909 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 21 Nov 2024 00:45:50 +0100 Subject: [PATCH 026/250] reduced maximum elements to 1024 --- src/ugui_data.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 7fd9c90..3e043c5 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -93,7 +93,7 @@ struct Elem { def IdTree = vtree::VTree() @private; // elements themselves are kept in a cache -const uint MAX_ELEMENTS = 2048; +const uint MAX_ELEMENTS = 1024; def ElemCache = cache::Cache() @private; def CmdQueue = fifo::Fifo(); From bb1745a05d96af1c433eff74ce912d9830b90cbe Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 21 Nov 2024 00:46:27 +0100 Subject: [PATCH 027/250] use map::HashMap instead of map::Map and use clz() to find a free spot --- src/cache.c3 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/cache.c3 b/src/cache.c3 index 2123194..2b1b5f2 100644 --- a/src/cache.c3 +++ b/src/cache.c3 @@ -18,7 +18,7 @@ import std::collections::bitset; import std::collections::map; def BitArr = bitset::BitSet() @private; -def IdTable = map::Map() @private; +def IdTable = map::HashMap() @private; def IdTableEntry = map::Entry() @private; const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); @@ -44,7 +44,7 @@ macro Cache.cycle(&cache) @private { fn void! Cache.init(&cache) { - cache.table = map::new()(SIZE); + cache.table.new_init(capacity: SIZE); // FIXME: this shit is SLOW foreach (idx, bit : cache.used) { cache.used[idx] = false; } foreach (idx, bit : cache.present) { cache.present[idx] = false; } @@ -83,10 +83,10 @@ fn Value*! Cache.search(&cache, Key id) /* If there is no free space left then just return the first position */ fn usz Cache.get_free_spot(&cache) @private { - // FIXME: This shit is SLOW, especially when clz() exists - foreach (idx, bit: cache.present) { - if (bit == false) { - return idx; + const BITS = $typeof(cache.present.data[0]).sizeof*8; + foreach (idx, d: cache.present.data) { + if (d.clz() != BITS) { + return idx*BITS + BITS-d.clz(); } } return 0; From 9a785e0f063926ddf2a479a618dd2ae16cf2d5b1 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 21 Nov 2024 00:50:11 +0100 Subject: [PATCH 028/250] use builtin hash functions instead of rolling my own --- src/ugui_button.c3 | 2 +- src/ugui_data.c3 | 16 ---------------- src/ugui_div.c3 | 2 +- src/ugui_impl.c3 | 2 +- src/ugui_slider.c3 | 4 ++-- 5 files changed, 5 insertions(+), 21 deletions(-) diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 8d7aac4..ac90857 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -5,7 +5,7 @@ import std::io; // draw a button, return the events on that button fn ElemEvents! Ctx.button(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 3e043c5..945de02 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -104,22 +104,6 @@ fault UgError { UNEXPECTED_ELEMENT, } -fn Id fnv1a(String str) -{ - const ulong FNV_OFF = 0xcbf29ce484222325; - const ulong FNV_PRIME = 0x100000001b3; - - ulong hash = FNV_OFF; - foreach (c : str) { - hash ^= c; - hash *= FNV_PRIME; - } - - return hash; -} - -macro hash(String str) { return fnv1a(str); } - macro uint_to_rgba(uint u) { return Color{ .r = (char)((u >> 24) & 0xff), diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 45d8387..e17b278 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -4,7 +4,7 @@ import std::io; fn void! Ctx.div_begin(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem* c_elem = ctx.get_elem(id)!; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 17a2b04..89fb177 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -28,7 +28,7 @@ macro Ctx.get_elem(&ctx, Id id) // if it does't find one macro Ctx.get_elem_by_label(&ctx, String label) { - Id id = hash(label); + Id id = label.hash(); return ctx.cache.search(id); } diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index a32524a..6eae35b 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -9,7 +9,7 @@ import std::io; */ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; @@ -81,7 +81,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) */ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) { - Id id = hash(label); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; From f8e2c0b70ca00c9790b640c4df9963f3d02a04f9 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 21 Nov 2024 00:50:25 +0100 Subject: [PATCH 029/250] more TODO --- TODO | 1 + 1 file changed, 1 insertion(+) diff --git a/TODO b/TODO index 6d2312a..3b2f9cb 100644 --- a/TODO +++ b/TODO @@ -4,3 +4,4 @@ [ ] Port font system from C to C3 (rewrite1) [ ] Update ARCHITECTURE.md [ ] Write a README.md +[ ] Use an arena allocator for cache From 574a1f23dc374d9f72b14dfaf5560f0869f0755e Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 21 Nov 2024 00:50:42 +0100 Subject: [PATCH 030/250] some tests --- test/test_bittype.c3 | 10 ++ test/test_custom_hash.c3 | 14 +++ test/ugui_font.c3 | 226 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 test/test_bittype.c3 create mode 100644 test/test_custom_hash.c3 create mode 100644 test/ugui_font.c3 diff --git a/test/test_bittype.c3 b/test/test_bittype.c3 new file mode 100644 index 0000000..e9802c9 --- /dev/null +++ b/test/test_bittype.c3 @@ -0,0 +1,10 @@ +import std::io; +import std::collections::bitset; + +def Bits = bitset::BitSet(<128>); + +fn void main() +{ + Bits b; + io::printn($typeof(b.data[0]).sizeof); +} diff --git a/test/test_custom_hash.c3 b/test/test_custom_hash.c3 new file mode 100644 index 0000000..fc90f11 --- /dev/null +++ b/test/test_custom_hash.c3 @@ -0,0 +1,14 @@ +import std::collections::map; + +def Codepoint = uint; +fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash(); +def CodeMap = map::HashMap(); + + +fn int main() +{ + CodeMap m; + m.new_init(); + m.free(); + return 0; +} diff --git a/test/ugui_font.c3 b/test/ugui_font.c3 new file mode 100644 index 0000000..ec1fc03 --- /dev/null +++ b/test/ugui_font.c3 @@ -0,0 +1,226 @@ +module ugui; + +import cache; +//#include +//#include + +//#include "stb_truetype.h" +//#include "stbimage_write.h" + +// unicode code point, different type for a different hash +def Codepoint = uint; + +/* width and height of a glyph contain the kering advance + * (u,v) + * +-------------*---+ - + * | ^ | | ^ + * | |oy | | | + * | v | | | + * | .ii. | | | + * | @@@@@@. |<->| | + * | V@Mio@@o |adv| |h + * | :i. V@V | | | + * | :oM@@M | | | + * | :@@@MM@M | | | + * | @@o o@M | | | + * |<->:@@. M@M | | | + * |ox @@@o@@@@ | | | + * | :M@@V:@@.| | v + * +-------------*---+ - + * |<------------->| + * w + */ +struct Glyph { + Codepoint code; + uint u, v; + ushort w, h, a, x, y; +} + +def GlyphCache = cache::Cache(); + +// identity map the ASCII range +fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash(); + +struct FontAtlas { + uint width, height; + char* atlas; + uint glyph_max_w, glyph_max_h; + int size; + int file_size; + char *file; + void *priv; +} + + +macro is_utf8(char c) => c & 0x80; +const uint BDEPTH = 1; +const uint BORDER = 4; + +// FIXME: as of now only monospaced fonts look decent since no +// kerning information is stored + +struct Priv @private { + stbtt_fontinfo stb; + float scale; + int baseline; + unsigned char *bitmap; + struct cache c; +} +//#define PRIV(x) ((struct priv *)x->priv) + + +struct font_atlas * font_init(void) +{ + struct font_atlas *p = emalloc(sizeof(struct font_atlas)); + memset(p, 0, sizeof(struct font_atlas)); + p->priv = emalloc(sizeof(struct priv)); + memset(p->priv, 0, sizeof(struct priv)); + PRIV(p)->c = cache_init(); + return p; +} + + +// loads a font into memory, storing all the ASCII characters in the atlas, each font +// atlas structure holds glyphs of a specific size in pixels +// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall) +int font_load(struct font_atlas *atlas, const char *path, int size) +{ + if (!atlas || !path) + return -1; + + int err; + + dump_file(path, &(atlas->file), &(atlas->file_size)); + + err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0); + if (err == 0) return -1; + + int ascent, descent, linegap, baseline; + int x0,y0,x1,y1; + float scale; + stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap); + stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1); + scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size); + baseline = scale * -y0; + atlas->glyph_max_w = (scale*x1) - (scale*x0); + atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0); + atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); + memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); + PRIV(atlas)->baseline = atlas->glyph_max_h - baseline; + PRIV(atlas)->scale = scale; + PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); + // FIXME: make this a square atlas + atlas->width = atlas->glyph_max_w*CACHE_SIZE/4; + atlas->height = atlas->glyph_max_h*4; + atlas->size = size; + + // preallocate all ascii characters + for (char c = ' '; c <= '~'; c++) { + if (!font_get_glyph_texture(atlas, c, NULL)) + return -1; + } + + return 0; +} + + +int font_free(struct font_atlas *atlas) +{ + efree(atlas->atlas); + efree(atlas->file); + efree(PRIV(atlas)->bitmap); + cache_free(&PRIV(atlas)->c); + efree(atlas->priv); + efree(atlas); + return 0; +} + + +// TODO: time and take the median of the time it takes to generate the cache and +// the time it takes to draw the glyph +const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated) +{ + int _u = 0; + if (!updated) updated = &_u; + + const struct font_glyph *r; + if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) { + *updated = 0; + return r; + } + + *updated = 1; + // generate the sdf and put it into the cache + // TODO: generate the whole block at once + int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code); + int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base; + base = atlas->glyph_max_h - PRIV(atlas)->baseline; + stbtt_GetGlyphBitmapBoxSubpixel( + &PRIV(atlas)->stb, + idx, + PRIV(atlas)->scale, + PRIV(atlas)->scale, + 0,0, + &x0,&y0, + &x1, &y1); + gw = x1 - x0; + gh = y1 - y0; + stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l); + adv *= PRIV(atlas)->scale; + off_x = PRIV(atlas)->scale*l; + off_y = atlas->glyph_max_h+y0; + stbtt_MakeGlyphBitmapSubpixel( + &PRIV(atlas)->stb, + PRIV(atlas)->bitmap, + atlas->glyph_max_w, + atlas->glyph_max_h, + atlas->glyph_max_w, + PRIV(atlas)->scale, + PRIV(atlas)->scale, + 0, 0, + idx); + + // TODO: bounds check usign atlas height + // TODO: clear spot area in the atlas before writing on it + unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c); + unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h; + unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width; + unsigned int w = atlas->width; + + unsigned char *a = (void *)atlas->atlas; + + //printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y); + + for (int y = 0; y < gh; y++) { + for (int x = 0; x < gw; x++) { + int c, r; + r = (ty+y)*w; + c = tx+x; + a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x]; + } + } + + struct font_glyph g = { + .codepoint = code, + .u = tx, + .v = ty, + .w = gw, + .h = gh, + .x = off_x, + .y = off_y-base, + .a = adv, + }; + return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot); +} + + +void font_dump(const struct font_atlas *atlas, const char *path) +{ + stbi_write_png( + path, + atlas->width, + atlas->height, + BDEPTH, + atlas->atlas, + BDEPTH*atlas->width); +} From d5bea680586faf455048460bf71b03df87e54c73 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 1 Dec 2024 00:25:43 +0100 Subject: [PATCH 031/250] report timings --- src/main.c3 | 48 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 5440370..864b38a 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -3,6 +3,25 @@ import vtree; import cache; import ugui; import rl; +import std::time; +import std::collections::ringbuffer; + +def Times = ringbuffer::RingBuffer(); + +fn void Times.print_stats(×) +{ + time::NanoDuration min, max, avg, x; + min = times.get(0); + for (usz i = 0; i < times.written; i++) { + x = times.get(i); + if (x < min) { min = x; } + if (x > max) { max = x; } + avg += x; + } + avg = (NanoDuration)((ulong)avg/128.0); + + io::printfn("min=%s, max=%s, avg=%s", min, max, avg); +} fn int main(String[] args) { @@ -18,13 +37,15 @@ fn int main(String[] args) rl::enable_event_waiting(); isz frame; + bool toggle = true; + time::Clock clock; + Times ui_times; + Times draw_times; // Main loop while (!rl::window_should_close()) { - const int PARTIAL_INPUT = 0; - const int PARTIAL_LAYOUT = 1; - const int PARTIAL_DRAW = 2; - + clock.mark(); + /* Start Input Handling */ if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); @@ -47,21 +68,25 @@ fn int main(String[] args) /* Start UI Handling */ ui.frame_begin()!!; -/* // main div, fill the whole window ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; {| + ui.layout_set_row()!!; if (ui.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button0"); + toggle = !toggle; + ui.force_update()!!; } //ui.layout_next_column()!!; if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; - if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { - io::printn("release button2"); + if (toggle) { + if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + io::printn("release button2"); + } } if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { ugui::Elem* e = ui.get_elem_by_label("slider")!!; @@ -69,7 +94,7 @@ fn int main(String[] args) } |}; ui.div_end()!!; -*/ + ui.div_begin("second", ugui::DIV_FILL)!!; ugui::Elem* de = ui.get_elem_by_label("second")!!; de.div.scroll.can_y = true; @@ -92,6 +117,8 @@ fn int main(String[] args) ui.frame_end()!!; /* End UI Handling */ + ui_times.push(clock.mark()); + ui_times.print_stats(); /* Start UI Drawing */ rl::begin_drawing(); @@ -118,8 +145,11 @@ fn int main(String[] args) io::printfn("Unknown cmd type: %d", cmd.type); } } - + draw_times.push(clock.mark()); + draw_times.print_stats(); rl::end_drawing(); + /* End Drawing */ + } rl::close_window(); From 537acd47652daf9b49e3c83181781fbaaf419fc7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 1 Dec 2024 00:26:56 +0100 Subject: [PATCH 032/250] added a way to force a relayout --- src/ugui_impl.c3 | 51 +++++++++++++++++++++++++++-------------------- src/ugui_input.c3 | 17 ++++++++-------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 89fb177..975612d 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -28,13 +28,13 @@ macro Ctx.get_elem(&ctx, Id id) // if it does't find one macro Ctx.get_elem_by_label(&ctx, String label) { - Id id = label.hash(); - return ctx.cache.search(id); + Id id = label.hash(); + return ctx.cache.search(id); } macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private { - Id id = ctx.tree.get(ctx.active_div)!; + Id id = ctx.tree.get(ctx.active_div)!; return ctx.cache.search(id); } @@ -42,7 +42,6 @@ fn void! Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; defer catch { (void)ctx.tree.free(); } - //ug_fifo_init(&ctx.fifo, MAX_CMDS); ctx.cache.init()!; defer catch { (void)ctx.cache.free(); } @@ -72,31 +71,32 @@ fn void! Ctx.frame_begin(&ctx) // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree - c_elem.flags.updated = ctx.input.events.resize; + c_elem.flags.updated = ctx.input.events.resize | ctx.input.events.force_update; + ctx.input.events.force_update = false; // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff c_elem.flags.has_focus = ctx.has_focus; if (c_elem.flags.is_new || c_elem.flags.updated) { - Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .bounds = { - .w = ctx.width, - .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - .children_bounds = { + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .bounds = { .w = ctx.width, .h = ctx.height, - } - }, - .flags = c_elem.flags, - }; + }, + .div = { + .layout = LAYOUT_ROW, + .children_bounds = { + .w = ctx.width, + .h = ctx.height, + } + }, + .flags = c_elem.flags, + }; - *c_elem = def_root; + *c_elem = def_root; } // 3. Push the root element into the element tree @@ -106,13 +106,20 @@ fn void! Ctx.frame_begin(&ctx) // TODO: add a background color taken from a theme or config } +fn void! Ctx.force_update(&ctx) +{ + ctx.input.events.force_update = true; +} + fn void! Ctx.frame_end(&ctx) { // 1. clear the tree ctx.tree.prune(0)!; // 2. clear input fields + bool f = ctx.input.events.force_update; ctx.input.events = (InputEvents)0; + ctx.input.events.force_update = f; // draw mouse position $if 1: @@ -130,9 +137,9 @@ $if 1: $endif } -/** +<* * @ensure elem != null -**/ + *> fn bool Ctx.is_hovered(&ctx, Elem *elem) { return point_in_rect(ctx.input.mouse.pos, elem.bounds); diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index b68cbfc..fba777b 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -8,6 +8,7 @@ bitstruct InputEvents : uint { bool change_focus : 1; // window focus changed bool mouse_move : 2; // mouse was moved bool mouse_btn : 3; // mouse button pressed or released + bool force_update : 4; } // Window size was changed @@ -82,16 +83,16 @@ macro Ctx.is_mouse_down(&ctx, MouseButtons btn) macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { - // TODO: add the other events - // FIXME: active should be elsewhere - bool active = elem.events.mouse_hold || ctx.is_hovered(elem); - ElemEvents ev = { - .mouse_hover = ctx.is_hovered(elem), - .mouse_press = active & ctx.is_mouse_pressed(BTN_ANY), + // TODO: add the other events + // FIXME: active should be elsewhere + bool active = elem.events.mouse_hold || ctx.is_hovered(elem); + ElemEvents ev = { + .mouse_hover = ctx.is_hovered(elem), + .mouse_press = active & ctx.is_mouse_pressed(BTN_ANY), .mouse_release = active & ctx.is_mouse_released(BTN_ANY), .mouse_hold = active & ctx.is_mouse_down(BTN_ANY), - }; - return ev; + }; + return ev; } // Mouse Button moved From 7e18c7a3167fe0cb10689c37eaa9495130d92436 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 1 Dec 2024 00:28:08 +0100 Subject: [PATCH 033/250] only reset div elements to default if new element --- src/ugui_div.c3 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index e17b278..1d3f57f 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -20,22 +20,22 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) c_elem.bounds = ctx.position_element(parent, size); if (c_elem.flags.is_new) { c_elem.div.children_bounds = c_elem.bounds; + c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.scroll.can_x = false; + c_elem.div.scroll.can_y = false; + c_elem.div.scroll.value_x = 0; + c_elem.div.scroll.value_y = 0; } // 3. Mark the element as updated c_elem.flags.updated = true; // 4. Fill the div fields - c_elem.div.layout = parent.div.layout; c_elem.div.origin_c = Point{ .x = c_elem.bounds.x, .y = c_elem.bounds.y, }; c_elem.div.origin_r = c_elem.div.origin_c; - c_elem.div.color_bg = uint_to_rgba(0xff0000ff); - c_elem.div.scroll.can_x = false; - c_elem.div.scroll.can_y = false; - c_elem.div.scroll.value_x = 0; - c_elem.div.scroll.value_y = 0; + c_elem.div.layout = parent.div.layout; } // Add the background to the draw stack From f86a360f394ff7cd634f995632bea2e23112129d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 2 Dec 2024 18:48:30 +0100 Subject: [PATCH 034/250] correct slider handle placement --- src/ugui_slider.c3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 6eae35b..0184553 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -24,7 +24,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) // 2. Layout c_elem.bounds = ctx.position_element(parent, size, true); c_elem.slider.handle = Rect{ - .x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w * c_elem.slider.value)), + .x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w*(1.0-0.25) * c_elem.slider.value)), .y = c_elem.bounds.y, .w = (short)(c_elem.bounds.w * 0.25), .h = c_elem.bounds.h, @@ -97,7 +97,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) c_elem.bounds = ctx.position_element(parent, size, true); c_elem.slider.handle = Rect{ .x = c_elem.bounds.x, - .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h * c_elem.slider.value)), + .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1.0-0.25) * c_elem.slider.value)), .w = c_elem.bounds.w, .h = (short)(c_elem.bounds.h * 0.25), }; From dbe70eb4f4bf0d3016dbde26064c08eb44947a17 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 6 Dec 2024 22:03:35 +0100 Subject: [PATCH 035/250] added library schrift --- .gitignore | 1 + lib/libschrift.c3l/Makefile | 3 ++ lib/libschrift.c3l/libschrift.c3 | 56 ++++++++++++++++++++++ lib/libschrift.c3l/linux-x64/libschrift.a | Bin 0 -> 19078 bytes lib/libschrift.c3l/manifest.json | 9 ++++ lib/libschrift.c3l/project.json | 45 +++++++++++++++++ lib/libschrift.c3l/thirdparty/libschrift | 1 + project.json | 2 +- src/ugui_data.c3 | 1 + src/ugui_font.c3 | 18 +++++++ test/test_font.c3 | 6 +++ 11 files changed, 141 insertions(+), 1 deletion(-) create mode 100644 lib/libschrift.c3l/Makefile create mode 100644 lib/libschrift.c3l/libschrift.c3 create mode 100644 lib/libschrift.c3l/linux-x64/libschrift.a create mode 100644 lib/libschrift.c3l/manifest.json create mode 100644 lib/libschrift.c3l/project.json create mode 160000 lib/libschrift.c3l/thirdparty/libschrift create mode 100644 src/ugui_font.c3 create mode 100644 test/test_font.c3 diff --git a/.gitignore b/.gitignore index 228cff3..87b38e4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.o build/* +**/.ccls-cache diff --git a/lib/libschrift.c3l/Makefile b/lib/libschrift.c3l/Makefile new file mode 100644 index 0000000..ff62ec4 --- /dev/null +++ b/lib/libschrift.c3l/Makefile @@ -0,0 +1,3 @@ +all: + make -C thirdparty/libschrift + cp thirdparty/libschrift/libschrift.a linux-x64/libschrift.a diff --git a/lib/libschrift.c3l/libschrift.c3 b/lib/libschrift.c3l/libschrift.c3 new file mode 100644 index 0000000..1b0f24b --- /dev/null +++ b/lib/libschrift.c3l/libschrift.c3 @@ -0,0 +1,56 @@ +module schrift; + +def SftFont = void*; +def SftUChar = uint; +def SftGlyph = uint; + +struct Sft +{ + SftFont font; + double xScale; + double yScale; + double xOffset; + double yOffset; + int flags; +} + +struct SftLMetrics +{ + double ascender; + double descender; + double lineGap; +} + +struct SftGMetrics +{ + double advanceWidth; + double leftSideBearing; + int yOffset; + int minWidth; + int minHeight; +} + +struct SftKerning +{ + double xShift; + double yShift; +} + +struct SftImage +{ + void *pixels; + int width; + int height; +} + +extern fn char* sft_version() @extern("sft_version"); + +extern fn SftFont sft_loadmem(void* mem, usz size) @extern("sft_loadmem"); +extern fn SftFont sft_loadfile(char* filename) @extern("sft_loadfile"); +extern fn void sft_freefont(SftFont font) @extern("sft_freefont"); + +extern fn int sft_lmetrics(Sft* sft, SftLMetrics* metrics) @extern("sft_lmetrics"); +extern fn int sft_lookup(Sft* sft, SftUChar codepoint, SftGlyph* glyph) @extern("sft_lookup"); +extern fn int sft_gmetrics(Sft* sft, SftGlyph glyph, SftGMetrics* metrics) @extern("sft_gmetrics"); +extern fn int sft_kerning(Sft* sft, SftGlyph leftGlyph, SftGlyph rightGlyph, SftKerning* kerning) @extern("sft_kerning"); +extern fn int sft_render(Sft* sft, SftGlyph glyph, SftImage image) @extern("sft_render"); diff --git a/lib/libschrift.c3l/linux-x64/libschrift.a b/lib/libschrift.c3l/linux-x64/libschrift.a new file mode 100644 index 0000000000000000000000000000000000000000..e3a042393ad6f6b4e78e5d729a610b74dd03c1b7 GIT binary patch literal 19078 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 z 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 G|9=4{+1>vD literal 0 HcmV?d00001 diff --git a/lib/libschrift.c3l/manifest.json b/lib/libschrift.c3l/manifest.json new file mode 100644 index 0000000..efb7f23 --- /dev/null +++ b/lib/libschrift.c3l/manifest.json @@ -0,0 +1,9 @@ +{ + "provides" : "schrift", + "targets" : { + "linux-x64" : { + "dependencies" : [], + "linked-libraries" : ["schrift", "c"] + } + } +} diff --git a/lib/libschrift.c3l/project.json b/lib/libschrift.c3l/project.json new file mode 100644 index 0000000..573769f --- /dev/null +++ b/lib/libschrift.c3l/project.json @@ -0,0 +1,45 @@ +{ + // Language version of C3. + "langrev": "1", + // Warnings used for all targets. + "warnings": [ "no-unused" ], + // Directories where C3 library files may be found. + "dependency-search-paths": [ ".." ], + // Libraries to use for all targets. + "dependencies": [ "schrift" ], + // Authors, optionally with email. + "authors": [ "Alessandro Mauri Date: Wed, 11 Dec 2024 01:14:14 +0100 Subject: [PATCH 036/250] somewhat functional text rendering --- TODO | 18 ++- lib/libschrift.c3l/libschrift.c3 | 18 +-- src/main.c3 | 34 ++++- src/ugui_data.c3 | 24 ++++ src/ugui_font.c3 | 209 ++++++++++++++++++++++++++++++- src/ugui_text.c3 | 81 ++++++++++++ 6 files changed, 368 insertions(+), 16 deletions(-) create mode 100644 src/ugui_text.c3 diff --git a/TODO b/TODO index 3b2f9cb..837d2a6 100644 --- a/TODO +++ b/TODO @@ -1,7 +1,21 @@ # TODOs, semi-random sorting -[ ] Implement glyph draw command -[ ] Implement div.view and scrollbars +[x] Implement glyph draw command +[x] Implement div.view and scrollbars [ ] Port font system from C to C3 (rewrite1) [ ] Update ARCHITECTURE.md [ ] Write a README.md [ ] Use an arena allocator for cache + +## Commands +[ ] rect commads should have: + * border width + * border radius +[x] add a command to update an atlas + +## Atlases +[ ] Add an interface to create, destroy, update and get atlases based on their ids +[ ] Implement multiple font atlases + +## Fonts +[ ] Fix the missing alpha channel +[ ] Fix the allignment diff --git a/lib/libschrift.c3l/libschrift.c3 b/lib/libschrift.c3l/libschrift.c3 index 1b0f24b..9cf8f31 100644 --- a/lib/libschrift.c3l/libschrift.c3 +++ b/lib/libschrift.c3l/libschrift.c3 @@ -4,6 +4,8 @@ def SftFont = void*; def SftUChar = uint; def SftGlyph = uint; +const int SFT_DOWNWARD_Y = 0x01; + struct Sft { SftFont font; @@ -45,12 +47,12 @@ struct SftImage extern fn char* sft_version() @extern("sft_version"); -extern fn SftFont sft_loadmem(void* mem, usz size) @extern("sft_loadmem"); -extern fn SftFont sft_loadfile(char* filename) @extern("sft_loadfile"); -extern fn void sft_freefont(SftFont font) @extern("sft_freefont"); +extern fn SftFont loadmem(void* mem, usz size) @extern("sft_loadmem"); +extern fn SftFont loadfile(char* filename) @extern("sft_loadfile"); +extern fn void freefont(SftFont font) @extern("sft_freefont"); -extern fn int sft_lmetrics(Sft* sft, SftLMetrics* metrics) @extern("sft_lmetrics"); -extern fn int sft_lookup(Sft* sft, SftUChar codepoint, SftGlyph* glyph) @extern("sft_lookup"); -extern fn int sft_gmetrics(Sft* sft, SftGlyph glyph, SftGMetrics* metrics) @extern("sft_gmetrics"); -extern fn int sft_kerning(Sft* sft, SftGlyph leftGlyph, SftGlyph rightGlyph, SftKerning* kerning) @extern("sft_kerning"); -extern fn int sft_render(Sft* sft, SftGlyph glyph, SftImage image) @extern("sft_render"); +extern fn int lmetrics(Sft* sft, SftLMetrics* metrics) @extern("sft_lmetrics"); +extern fn int lookup(Sft* sft, SftUChar codepoint, SftGlyph* glyph) @extern("sft_lookup"); +extern fn int gmetrics(Sft* sft, SftGlyph glyph, SftGMetrics* metrics) @extern("sft_gmetrics"); +extern fn int kerning(Sft* sft, SftGlyph leftGlyph, SftGlyph rightGlyph, SftKerning* kerning) @extern("sft_kerning"); +extern fn int render(Sft* sft, SftGlyph glyph, SftImage image) @extern("sft_render"); diff --git a/src/main.c3 b/src/main.c3 index 864b38a..dc833e0 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,6 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; + ui.font.load("/usr/share/fonts/TTF/FreeSans.ttf", 16)!!; short width = 800; short height = 450; @@ -92,6 +93,8 @@ fn int main(String[] args) ugui::Elem* e = ui.get_elem_by_label("slider")!!; io::printfn("slider: %f", e.slider.value); } + + ui.text_unbounded("text1", "Ciao Mamma")!!; |}; ui.div_end()!!; @@ -125,6 +128,8 @@ fn int main(String[] args) // ClearBackground(BLACK); rl::Color c; + static rl::Image font_atlas; + static rl::Texture2D font_texture; for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: @@ -141,8 +146,34 @@ fn int main(String[] args) cmd.rect.rect.h, c ); + case ugui::CmdType.CMD_UPDATE_ATLAS: + rl::unload_image(font_atlas); + font_atlas.data = cmd.update_atlas.raw_buffer; + font_atlas.width = cmd.update_atlas.width; + font_atlas.height = cmd.update_atlas.height; + font_atlas.mipmaps = 1; + //font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + font_atlas.format = 1; + if (rl::is_texture_ready(font_texture)) { + rl::unload_texture(font_texture); + } + font_texture = rl::load_texture_from_image(font_atlas); + //rl::draw_texture(font_texture, 0, 0, rl::WHITE); + case ugui::CmdType.CMD_SPRITE: + rl::Rectangle source = { + .x = cmd.sprite.texture_rect.x, + .y = cmd.sprite.texture_rect.y, + .width = cmd.sprite.texture_rect.w, + .height = cmd.sprite.texture_rect.h, + }; + rl::Vector2 position = { + .x = cmd.sprite.rect.x, + .y = (float)cmd.sprite.rect.y - cmd.sprite.rect.h, + }; + + rl::draw_texture_rec(font_texture, source, position, rl::WHITE); default: - io::printfn("Unknown cmd type: %d", cmd.type); + io::printfn("Unknown cmd type: %s", cmd.type); } } draw_times.push(clock.mark()); @@ -154,6 +185,7 @@ fn int main(String[] args) rl::close_window(); + ui.font.free(); ui.free(); return 0; } diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index c7e0895..33f0395 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -28,6 +28,7 @@ enum ElemType { ETYPE_DIV, ETYPE_BUTTON, ETYPE_SLIDER, + ETYPE_TEXT, } bitstruct ElemFlags : uint { @@ -75,6 +76,10 @@ struct Slider { Rect handle; } +struct Text { + char* str; +} + // element structure struct Elem { Id id; @@ -85,6 +90,7 @@ struct Elem { union { Div div; Slider slider; + Text text; } } @@ -126,6 +132,8 @@ const uint ROOT_ID = 1; // command type enum CmdType { CMD_RECT, + CMD_UPDATE_ATLAS, + CMD_SPRITE, } // command to draw a rect @@ -134,11 +142,27 @@ struct CmdRect { Color color; } +// FIXME: For now only support black and white atlas, so PIXELFORMAT_UNCOMPRESSED_GRAYSCALE +struct CmdUpdateAtlas { + char* raw_buffer; + short width, height; +} + +// TODO: +// 1. Add atlases as a data type +// 2. Each atlas has an id +struct CmdSprite { + Rect rect; + Rect texture_rect; +} + // command structure struct Cmd { CmdType type; union { CmdRect rect; + CmdUpdateAtlas update_atlas; + CmdSprite sprite; } } diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 10ab764..350f7ca 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -1,18 +1,217 @@ module ugui; import schrift; +import std::collections::map; +import std::core::mem; -struct Font @private { +// unicode code point, different type for a different hash +def Codepoint = uint; + + +/* width and height of a glyph contain the kering advance + * (u,v) + * +-------------*---+ - + * | ^ | | ^ + * | |oy | | | + * | v | | | + * | .ii. | | | + * | @@@@@@. |<->| | + * | V@Mio@@o |adv| |h + * | :i. V@V | | | + * | :oM@@M | | | + * | :@@@MM@M | | | + * | @@o o@M | | | + * |<->:@@. M@M | | | + * |ox @@@o@@@@ | | | + * | :M@@V:@@.| | v + * +-------------*---+ - + * |<------------->| + * w + */ +struct Glyph { + Codepoint code; + ushort u, v; + ushort w, h; + short adv, ox, oy; + short idx; // atlas index +} + +const uint FONT_CACHED = 512; +def GlyphTable = map::HashMap() @private; + +fault UgFontError { + TTF_LOAD_FAILED, + MISSING_GLYPH, + BAD_GLYPH_METRICS, + RENDER_ERROR, +} + +fault UgAtlasError { + CANNOT_PLACE, +} + +// black and white atlas +struct AtlasBW { + ushort width, height; + char[] buffer; + + Point row; + ushort row_h; +} + +struct Font { schrift::Sft sft; String path; + GlyphTable table; + + float size; + float ascender, descender, linegap; // Line Metrics + AtlasBW[] atlas; } -fn void! Ctx.font_load(&ctx, String path) +fn void! AtlasBW.new(&atlas, ushort width, ushort height) { - + atlas.width = width; + atlas.height = height; + atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height); } -fn void Ctx.font_free(&ctx) +fn void AtlasBW.free(&atlas) { - + free(atlas.buffer); +} + +// place a rect inside the atlas +// uses a row first algorithm +// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ +fn Point! AtlasBW.place(&atlas, char[] pixels, ushort w, ushort h) +{ + Point p; + + if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { + p = atlas.row; + } else { + atlas.row.x = 0; + atlas.row.y = atlas.row.y + atlas.row_h; + atlas.row_h = 0; + if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { + p = atlas.row; + } else { + return UgAtlasError.CANNOT_PLACE?; + } + } + + for (usz y = 0; y < h; y++) { + for (usz x = 0; x < w; x++) { + atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x]; + } + } + + atlas.row.x += w; + if (h > atlas.row_h) { + atlas.row_h = h; + } + + return p; +} + +fn void! Font.load(&font, String path, uint height, float scale = 1) +{ + font.table.new_init(capacity: FONT_CACHED); + + font.size = height*scale; + + font.sft = schrift::Sft{ + .xScale = (double)font.size, + .yScale = (double)font.size, + .flags = schrift::SFT_DOWNWARD_Y, + }; + + font.sft.font = schrift::loadfile(path); + if (font.sft.font == null) { + return UgFontError.TTF_LOAD_FAILED?; + } + + schrift::SftLMetrics lmetrics; + schrift::lmetrics(&font.sft, &lmetrics); + font.ascender = (float)lmetrics.ascender; + font.descender = (float)lmetrics.descender; + font.linegap = (float)lmetrics.lineGap; + + // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter + // like the letter 'A' + font.atlas = mem::new_array(AtlasBW, 1); + ushort size = (ushort)font.size*512; + font.atlas[0].new(size, size)!; + + // preallocate the ASCII range +// for (char c = ' '; c < '~'; c++) { +// font.get_glyph((Codepoint)c)!; +// } +} + +fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) +{ + Glyph*! gp; + gp = font.table.get_ref(code); + + if (catch excuse = gp) { + if (excuse != SearchResult.MISSING) { + return excuse?; + } + } else { + if (is_new) { *is_new = false; } + return gp; + } + + // missing glyph, render and place into an atlas + Glyph glyph; + + schrift::SftGlyph gid; + schrift::SftGMetrics gmtx; + + if (schrift::lookup(&font.sft, code, &gid) < 0) { + return UgFontError.MISSING_GLYPH?; + } + + if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) { + return UgFontError.BAD_GLYPH_METRICS?; + } + + schrift::SftImage img = { + .width = gmtx.minWidth, + .height = gmtx.minHeight, + }; + char[] pixels = mem::new_array(char, (usz)img.width * img.height); + img.pixels = pixels; + if (schrift::render(&font.sft, gid, img) < 0) { + return UgFontError.RENDER_ERROR?; + } + + glyph.code = code; + glyph.w = (ushort)img.width; + glyph.h = (ushort)img.height; + glyph.ox = (short)-gmtx.leftSideBearing; + glyph.oy = (short)-gmtx.yOffset; + glyph.adv = (short)gmtx.advanceWidth; + + Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!; + glyph.idx = 0; + glyph.u = uv.x; + glyph.v = uv.y; + + mem::free(pixels); + + font.table.set(code, glyph); + + if (is_new) { *is_new = true; } + return font.table.get_ref(code); +} + +fn void Font.free(&font) +{ + foreach (atlas: font.atlas) { + atlas.free(); + } + schrift::freefont(font.sft.font); } diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 new file mode 100644 index 0000000..1e6cb29 --- /dev/null +++ b/src/ugui_text.c3 @@ -0,0 +1,81 @@ +module ugui; + +import std::io; + +fn void! Ctx.text_unbounded(&ctx, String label, String text) +{ + Id id = label.hash(); + + Elem *parent = ctx.get_parent()!; + Elem *c_elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + // this resets the flags + c_elem.type = ETYPE_TEXT; + + bool update_atlas; + // if the element is new or the parent was updated then redo layout + if (c_elem.flags.is_new || parent.flags.updated) { + Rect text_size; + Glyph* gp; + + // FIXME: newlines are not counted + foreach (c: text) { + Codepoint cp = (Codepoint)c; + bool n; + gp = ctx.font.get_glyph(cp, &n)!; + text_size.w += gp.w + gp.ox + gp.adv; + text_size.h += gp.h + gp.oy; + if (n) { update_atlas = true; } + } + + // 2. Layout + c_elem.bounds = ctx.position_element(parent, text_size, true); + + // 3. Fill the button specific fields + c_elem.text.str = text; + } + + if (update_atlas) { + // FIXME: atlas here is hardcoded, look at the todo in ugui_data + Cmd up = { + .type = CMD_UPDATE_ATLAS, + .update_atlas = { + .raw_buffer = ctx.font.atlas[0].buffer, + .width = ctx.font.atlas[0].width, + .height = ctx.font.atlas[0].height, + }, + }; + ctx.cmd_queue.enqueue(&up)!; + } + + Point orig = { + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, + }; + foreach (c: text) { + Glyph* gp; + Codepoint cp = (Codepoint)c; + gp = ctx.font.get_glyph(cp)!; + + Cmd cmd = { + .type = CMD_SPRITE, + .sprite.rect = { + .x = orig.x + gp.ox, + .y = orig.y + gp.oy, + .w = gp.w, + .h = gp.h, + }, + .sprite.texture_rect = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, + }, + }; + orig.x += gp.w + gp.ox; + ctx.cmd_queue.enqueue(&cmd)!; + } +} From 61556d0a2c325ef07d232b1b3f990d3bc481f9c8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 11 Dec 2024 20:39:06 +0100 Subject: [PATCH 037/250] forgot the baseline --- src/main.c3 | 10 ++++++++-- src/ugui_font.c3 | 20 ++++++++++++-------- src/ugui_text.c3 | 10 ++++++---- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index dc833e0..7dbc648 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/TTF/FreeSans.ttf", 16)!!; + ui.font.load("/usr/share/fonts/TTF/FreeSans.ttf", 16, scale: 1.5)!!; short width = 800; short height = 450; @@ -168,10 +168,16 @@ fn int main(String[] args) }; rl::Vector2 position = { .x = cmd.sprite.rect.x, - .y = (float)cmd.sprite.rect.y - cmd.sprite.rect.h, + .y = cmd.sprite.rect.y, }; rl::draw_texture_rec(font_texture, source, position, rl::WHITE); + //rl::draw_rectangle(cmd.sprite.rect.x, + // cmd.sprite.rect.y, + // cmd.sprite.rect.w, + // cmd.sprite.rect.h, + // rl::WHITE + //); default: io::printfn("Unknown cmd type: %s", cmd.type); } diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 350f7ca..f188fbb 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -3,6 +3,7 @@ module ugui; import schrift; import std::collections::map; import std::core::mem; +import std::io; // unicode code point, different type for a different hash def Codepoint = uint; @@ -15,9 +16,9 @@ def Codepoint = uint; * | |oy | | | * | v | | | * | .ii. | | | - * | @@@@@@. |<->| | - * | V@Mio@@o |adv| |h - * | :i. V@V | | | + * | @@@@@@. | | | + * | V@Mio@@o | | | + * | :i. V@V | | h * | :oM@@M | | | * | :@@@MM@M | | | * | @@o o@M | | | @@ -25,8 +26,8 @@ def Codepoint = uint; * |ox @@@o@@@@ | | | * | :M@@V:@@.| | v * +-------------*---+ - - * |<------------->| - * w + * |<---- w ---->| + * |<------ adv ---->| */ struct Glyph { Codepoint code; @@ -137,11 +138,12 @@ fn void! Font.load(&font, String path, uint height, float scale = 1) font.ascender = (float)lmetrics.ascender; font.descender = (float)lmetrics.descender; font.linegap = (float)lmetrics.lineGap; + //io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap); // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter // like the letter 'A' font.atlas = mem::new_array(AtlasBW, 1); - ushort size = (ushort)font.size*512; + ushort size = (ushort)font.size*256; font.atlas[0].new(size, size)!; // preallocate the ASCII range @@ -191,9 +193,11 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) glyph.code = code; glyph.w = (ushort)img.width; glyph.h = (ushort)img.height; - glyph.ox = (short)-gmtx.leftSideBearing; - glyph.oy = (short)-gmtx.yOffset; + glyph.ox = (short)gmtx.leftSideBearing; + glyph.oy = (short)gmtx.yOffset; glyph.adv = (short)gmtx.advanceWidth; + //io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d", + // glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv); Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!; glyph.idx = 0; diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 1e6cb29..f60eff5 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -15,6 +15,8 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // this resets the flags c_elem.type = ETYPE_TEXT; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short baseline = (short)ctx.font.ascender; bool update_atlas; // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { @@ -26,8 +28,8 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) Codepoint cp = (Codepoint)c; bool n; gp = ctx.font.get_glyph(cp, &n)!; - text_size.w += gp.w + gp.ox + gp.adv; - text_size.h += gp.h + gp.oy; + text_size.w += gp.adv; + text_size.h += line_height; if (n) { update_atlas = true; } } @@ -64,7 +66,7 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) .type = CMD_SPRITE, .sprite.rect = { .x = orig.x + gp.ox, - .y = orig.y + gp.oy, + .y = orig.y + gp.oy + baseline, .w = gp.w, .h = gp.h, }, @@ -75,7 +77,7 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) .h = gp.h, }, }; - orig.x += gp.w + gp.ox; + orig.x += gp.adv; ctx.cmd_queue.enqueue(&cmd)!; } } From f0aa59ef0b1361b0c34dcee10cb908d3fceb439c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 11 Dec 2024 22:25:53 +0100 Subject: [PATCH 038/250] even better text rendering --- src/main.c3 | 2 +- src/ugui_text.c3 | 95 ++++++++++++++++++++++++++++++++---------------- 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 7dbc648..269e837 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -94,7 +94,7 @@ fn int main(String[] args) io::printfn("slider: %f", e.slider.value); } - ui.text_unbounded("text1", "Ciao Mamma")!!; + ui.text_unbounded("text1", "Ciao Mamma\n Sono a Casa")!!; |}; ui.div_end()!!; diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index f60eff5..e88593e 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -2,6 +2,35 @@ module ugui; import std::io; +fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) +{ + Rect text_bounds; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; + text_bounds.h = line_height; + Glyph* gp; + + // TODO: account for unicode codepoints + short line_len; + foreach (c: text) { + Codepoint cp = (Codepoint)c; + bool n; + if (cp != '\n') { + gp = ctx.font.get_glyph(cp, &n)!; + line_len += gp.adv; + if (n) { *update_atlas = true; } + } else { + text_bounds.h += line_height + line_gap; + if (line_len > text_bounds.w) { + text_bounds.w = line_len; + } + line_len = 0; + } + } + + return text_bounds; +} + fn void! Ctx.text_unbounded(&ctx, String label, String text) { Id id = label.hash(); @@ -15,24 +44,14 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // this resets the flags c_elem.type = ETYPE_TEXT; - short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short baseline = (short)ctx.font.ascender; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; bool update_atlas; // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { - Rect text_size; - Glyph* gp; + Rect text_size = ctx.get_text_bounds(text, &update_atlas)!; - // FIXME: newlines are not counted - foreach (c: text) { - Codepoint cp = (Codepoint)c; - bool n; - gp = ctx.font.get_glyph(cp, &n)!; - text_size.w += gp.adv; - text_size.h += line_height; - if (n) { update_atlas = true; } - } - // 2. Layout c_elem.bounds = ctx.position_element(parent, text_size, true); @@ -53,31 +72,43 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) ctx.cmd_queue.enqueue(&up)!; } + Cmd bounds = { + .type = CMD_RECT, + .rect.rect = c_elem.bounds, + .rect.color = uint_to_rgba(0x000000ff), + }; + ctx.cmd_queue.enqueue(&bounds)!; + Point orig = { .x = c_elem.bounds.x, .y = c_elem.bounds.y, }; + short line_len; foreach (c: text) { Glyph* gp; Codepoint cp = (Codepoint)c; - gp = ctx.font.get_glyph(cp)!; - - Cmd cmd = { - .type = CMD_SPRITE, - .sprite.rect = { - .x = orig.x + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }, - .sprite.texture_rect = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, - }, - }; - orig.x += gp.adv; - ctx.cmd_queue.enqueue(&cmd)!; + if (cp != '\n') { + gp = ctx.font.get_glyph(cp)!; + Cmd cmd = { + .type = CMD_SPRITE, + .sprite.rect = { + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }, + .sprite.texture_rect = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, + }, + }; + line_len += gp.adv; + ctx.cmd_queue.enqueue(&cmd)!; + } else { + orig.y += line_height + line_gap; + line_len = 0; + } } } From 328cac871a16854e9ce51f80d204eaafe34a3a77 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Dec 2024 15:46:24 +0100 Subject: [PATCH 039/250] started work on border radius --- src/main.c3 | 17 ++++++++++------- src/ugui_button.c3 | 9 +-------- src/ugui_cmd.c3 | 34 ++++++++++++++++++++++++++++++++++ src/ugui_data.c3 | 5 ++++- src/ugui_div.c3 | 2 +- src/ugui_impl.c3 | 8 +++++++- src/ugui_layout.c3 | 8 ++++---- 7 files changed, 61 insertions(+), 22 deletions(-) create mode 100644 src/ugui_cmd.c3 diff --git a/src/main.c3 b/src/main.c3 index 269e837..67eb227 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -128,6 +128,7 @@ fn int main(String[] args) // ClearBackground(BLACK); rl::Color c; + rl::Rectangle r; static rl::Image font_atlas; static rl::Texture2D font_texture; for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { @@ -139,13 +140,15 @@ fn int main(String[] args) .b = cmd.rect.color.b, .a = cmd.rect.color.a, }; - rl::draw_rectangle( - cmd.rect.rect.x, - cmd.rect.rect.y, - cmd.rect.rect.w, - cmd.rect.rect.h, - c - ); + r = rl::Rectangle{ + .x = cmd.rect.rect.x, + .y = cmd.rect.rect.y, + .height = cmd.rect.rect.h, + .width = cmd.rect.rect.w, + }; + // TODO: find a way to do real pixel-perfec rounding + float round = cmd.rect.radius ? 0.2 : 0; + rl::draw_rectangle_rounded(r, round, 2, c); case ugui::CmdType.CMD_UPDATE_ATLAS: rl::unload_image(font_atlas); font_atlas.data = cmd.update_atlas.raw_buffer; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index ac90857..d9abc64 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -34,14 +34,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size) } // Draw the button - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.bounds, - .color = bg_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_rect(c_elem.bounds, bg_color, do_border: true, do_radius: true)!; return c_elem.events; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 new file mode 100644 index 0000000..14919ca --- /dev/null +++ b/src/ugui_cmd.c3 @@ -0,0 +1,34 @@ +module ugui; + +// FIXME: is this really the best solution? +// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) +fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) +{ + Rect border = ctx.style.border; + Rect padding = ctx.style.padding; + ushort radius = ctx.style.radius; + Color border_color = ctx.style.brcolor; + + if (do_border) { + Cmd cmd = { + .type = CMD_RECT, + .rect.rect = rect, + .rect.color = border_color, + .rect.radius = do_radius ? radius : 0, + }; + ctx.cmd_queue.enqueue(&cmd)!; + } + + Cmd cmd = { + .type = CMD_RECT, + .rect.rect = { + .x = rect.x + (do_border ? border.x : 0) + (do_padding ? padding.x : 0), + .y = rect.y + (do_border ? border.y : 0) + (do_padding ? padding.y : 0), + .w = rect.w - (do_border ? border.x+border.w : 0) - (do_padding ? padding.x+padding.w : 0), + .h = rect.h - (do_border ? border.y+border.h : 0) - (do_padding ? padding.y+padding.h : 0), + }, + .rect.color = color, + .rect.radius = do_radius ? radius : 0, + }; + ctx.cmd_queue.enqueue(&cmd)!; +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 33f0395..e61b16f 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -137,8 +137,10 @@ enum CmdType { } // command to draw a rect +// TODO: implement radius struct CmdRect { Rect rect; + ushort radius; Color color; } @@ -179,7 +181,8 @@ struct Style { // css box model Rect margin; Color bgcolor; // background color Color fgcolor; // foreground color - Color bcolor; // border color + Color brcolor; // border color + ushort radius; } diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 1d3f57f..952a4c7 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -20,7 +20,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) c_elem.bounds = ctx.position_element(parent, size); if (c_elem.flags.is_new) { c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.color_bg = uint_to_rgba(0xff0000ff); + c_elem.div.color_bg = ctx.style.bgcolor; c_elem.div.scroll.can_x = false; c_elem.div.scroll.can_y = false; c_elem.div.scroll.value_x = 0; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 975612d..736877e 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -53,7 +53,13 @@ fn void! Ctx.init(&ctx) ctx.active_div = 0; // TODO: add style config - ctx.style.margin = Rect{1, 1, 1, 1}; + ctx.style.margin = Rect{2, 2, 2, 2}; + ctx.style.border = Rect{2, 2, 2, 2}; + ctx.style.padding = Rect{1, 1, 1, 1}; + ctx.style.radius = 4; + ctx.style.bgcolor = uint_to_rgba(0x282828ff); + ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff); + ctx.style.brcolor = uint_to_rgba(0xd79921ff); } fn void Ctx.free(&ctx) diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index aec6b1e..d403051 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -113,14 +113,14 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // 2.1 apply style, css box model if (style) { - Rect margin = ctx.style.margin; - Rect border = ctx.style.border; + Rect margin = ctx.style.margin; + Rect border = ctx.style.border; Rect padding = ctx.style.padding; placement.x += margin.x; placement.y += margin.y; - if (rect.w != 0) { placement.w += margin.x+margin.w + padding.x+padding.w; } - if (rect.h != 0) { placement.h += margin.y+margin.h + padding.y+padding.h; } + if (rect.w != 0) { placement.w += border.x+border.w + padding.x+padding.w; } + if (rect.h != 0) { placement.h += border.y+border.h + padding.y+padding.h; } pl_corner.x = placement.x + placement.w + margin.w; pl_corner.y = placement.y + placement.h + margin.h; From fb177c03f75b4e7bbc2d74bbb758291f6a2280da Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Dec 2024 15:46:40 +0100 Subject: [PATCH 040/250] some todos --- TODO | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 837d2a6..8ffaabc 100644 --- a/TODO +++ b/TODO @@ -5,9 +5,10 @@ [ ] Update ARCHITECTURE.md [ ] Write a README.md [ ] Use an arena allocator for cache +[ ] Do not redraw if there was no update (no layout and no draw) ## Commands -[ ] rect commads should have: +[x] rect commads should have: * border width * border radius [x] add a command to update an atlas @@ -19,3 +20,11 @@ ## Fonts [ ] Fix the missing alpha channel [ ] Fix the allignment + +## Raylib +[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: +[ ] Implement pixel radius rounding for border radius + +## Widgets +[ ] Dynamic text box to implement an fps counter +[ ] Button with label From 089140e1edf0c0c698ee6fdaa8cb05b11f7fc2b1 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 13:51:23 +0100 Subject: [PATCH 041/250] substitute enqueue() with push_rect() --- src/ugui_div.c3 | 22 +++------------------- src/ugui_slider.c3 | 42 +++++------------------------------------- 2 files changed, 8 insertions(+), 56 deletions(-) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 952a4c7..833b6ec 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -39,14 +39,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) } // Add the background to the draw stack - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.bounds, - .color = c_elem.div.color_bg, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_rect(c_elem.bounds, c_elem.div.color_bg)!; // TODO: check active // TODO: check resizeable @@ -95,17 +88,8 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) c_elem.div.origin_r = c_elem.div.origin_c; } - Cmd scrl = { - .type = CMD_RECT, - .rect.rect = vslider, - .rect.color = uint_to_rgba(0x999999ff), - }; - ctx.cmd_queue.enqueue(&scrl)!; - - scrl.rect.rect = vhandle; - scrl.rect.color = uint_to_rgba(0x9999ffff); - ctx.cmd_queue.enqueue(&scrl)!; - + ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; + ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; } else { c_elem.div.scroll.on_y = false; } diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 0184553..a32ffc5 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -42,27 +42,11 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) } } - // Draw the button + // Draw the slider background and handle Color bg_color = uint_to_rgba(0x0000ffff); Color handle_color = uint_to_rgba(0x0ff000ff); - - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.bounds, - .color = bg_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; - - cmd = Cmd{ - .type = CMD_RECT, - .rect = { - .rect = c_elem.slider.handle, - .color = handle_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_rect(c_elem.bounds, bg_color)!; + ctx.push_rect(c_elem.slider.handle, handle_color)!; return c_elem.events; } @@ -117,24 +101,8 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) // Draw the button Color bg_color = uint_to_rgba(0x0000ffff); Color handle_color = uint_to_rgba(0x0ff000ff); - - Cmd cmd = { - .type = CMD_RECT, - .rect = { - .rect = c_elem.bounds, - .color = bg_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; - - cmd = Cmd{ - .type = CMD_RECT, - .rect = { - .rect = c_elem.slider.handle, - .color = handle_color, - }, - }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_rect(c_elem.bounds, bg_color)!; + ctx.push_rect(c_elem.slider.handle, handle_color)!; return c_elem.events; } From c880c2b26e354effde746b417659c4ed2f1e7409 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 13:56:02 +0100 Subject: [PATCH 042/250] correct text bounds --- src/ugui_text.c3 | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index e88593e..15bcd28 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -21,11 +21,11 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) if (n) { *update_atlas = true; } } else { text_bounds.h += line_height + line_gap; - if (line_len > text_bounds.w) { - text_bounds.w = line_len; - } line_len = 0; } + if (line_len > text_bounds.w) { + text_bounds.w = line_len; + } } return text_bounds; @@ -78,7 +78,7 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) .rect.color = uint_to_rgba(0x000000ff), }; ctx.cmd_queue.enqueue(&bounds)!; - + Point orig = { .x = c_elem.bounds.x, .y = c_elem.bounds.y, From 5c687bd24e358b27686f1b9639422e93b508ef7f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 14:05:07 +0100 Subject: [PATCH 043/250] add push_sprite() --- src/ugui_cmd.c3 | 13 ++++++++++++- src/ugui_text.c3 | 27 ++++++++++++--------------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 14919ca..d3ce0c9 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -18,7 +18,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo }; ctx.cmd_queue.enqueue(&cmd)!; } - + Cmd cmd = { .type = CMD_RECT, .rect.rect = { @@ -32,3 +32,14 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo }; ctx.cmd_queue.enqueue(&cmd)!; } + +// TODO: add texture id +fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture) +{ + Cmd cmd = { + .type = CMD_SPRITE, + .sprite.rect = bounds, + .sprite.texture_rect = texture, + }; + ctx.cmd_queue.enqueue(&cmd)!; +} diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 15bcd28..97efbae 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -89,23 +89,20 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) Codepoint cp = (Codepoint)c; if (cp != '\n') { gp = ctx.font.get_glyph(cp)!; - Cmd cmd = { - .type = CMD_SPRITE, - .sprite.rect = { - .x = orig.x + line_len + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }, - .sprite.texture_rect = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, - }, + Rect gb = { + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, }; + Rect gt = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, + }; + ctx.push_sprite(gb, gt)!; line_len += gp.adv; - ctx.cmd_queue.enqueue(&cmd)!; } else { orig.y += line_height + line_gap; line_len = 0; From 3070fac9f52fd68ceb5ad4ccb9cac31774ed7004 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 14:41:54 +0100 Subject: [PATCH 044/250] cull zero area rects --- src/ugui_cmd.c3 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index d3ce0c9..8857c5a 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -4,6 +4,11 @@ module ugui; // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { + // FIXME: this should be culled higher up, maybe + if (rect.w <= 0 || rect.h <= 0) { + return; + } + Rect border = ctx.style.border; Rect padding = ctx.style.padding; ushort radius = ctx.style.radius; From 373243d1389a078d0988fe93dfe74430bdae7004 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 14:42:15 +0100 Subject: [PATCH 045/250] fix rect roundness --- src/main.c3 | 15 ++++++++------- src/ugui_impl.c3 | 2 +- src/ugui_slider.c3 | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 67eb227..49876a3 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/TTF/FreeSans.ttf", 16, scale: 1.5)!!; + ui.font.load("/usr/share/fonts/TTF/Hack-Regular.ttf", 16, scale: 1.5)!!; short width = 800; short height = 450; @@ -46,7 +46,7 @@ fn int main(String[] args) // Main loop while (!rl::window_should_close()) { clock.mark(); - + /* Start Input Handling */ if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); @@ -94,7 +94,7 @@ fn int main(String[] args) io::printfn("slider: %f", e.slider.value); } - ui.text_unbounded("text1", "Ciao Mamma\n Sono a Casa")!!; + ui.text_unbounded("text1", "Ciao Mamma\nSono a Casa")!!; |}; ui.div_end()!!; @@ -146,9 +146,10 @@ fn int main(String[] args) .height = cmd.rect.rect.h, .width = cmd.rect.rect.w, }; - // TODO: find a way to do real pixel-perfec rounding - float round = cmd.rect.radius ? 0.2 : 0; - rl::draw_rectangle_rounded(r, round, 2, c); + float rad = cmd.rect.radius; + // for some weird-ass reason the straight forward inverse formula does not work + float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width; + rl::draw_rectangle_rounded(r, roundness, 0, c); case ugui::CmdType.CMD_UPDATE_ATLAS: rl::unload_image(font_atlas); font_atlas.data = cmd.update_atlas.raw_buffer; @@ -173,7 +174,7 @@ fn int main(String[] args) .x = cmd.sprite.rect.x, .y = cmd.sprite.rect.y, }; - + rl::draw_texture_rec(font_texture, source, position, rl::WHITE); //rl::draw_rectangle(cmd.sprite.rect.x, // cmd.sprite.rect.y, diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 736877e..be172c1 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -56,7 +56,7 @@ fn void! Ctx.init(&ctx) ctx.style.margin = Rect{2, 2, 2, 2}; ctx.style.border = Rect{2, 2, 2, 2}; ctx.style.padding = Rect{1, 1, 1, 1}; - ctx.style.radius = 4; + ctx.style.radius = 5; ctx.style.bgcolor = uint_to_rgba(0x282828ff); ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff); ctx.style.brcolor = uint_to_rgba(0xd79921ff); diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index a32ffc5..ddabc4e 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -98,7 +98,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) } } - // Draw the button + // Draw the slider background and handle Color bg_color = uint_to_rgba(0x0000ffff); Color handle_color = uint_to_rgba(0x0ff000ff); ctx.push_rect(c_elem.bounds, bg_color)!; From 8cf3881b6b62d30ab1f92db7e7077ac8a2fc6f39 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 13 Dec 2024 16:44:07 +0100 Subject: [PATCH 046/250] added libgrapheme --- .gitignore | 1 + TODO | 6 ++- lib/libgrapheme.c3l/Makefile | 3 ++ lib/libgrapheme.c3l/libgrapheme.c3i | 46 ++++++++++++++++++++++ lib/libgrapheme.c3l/manifest.json | 9 +++++ lib/libgrapheme.c3l/project.json | 44 +++++++++++++++++++++ lib/libgrapheme.c3l/thirdparty/libgrapheme | 1 + 7 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 lib/libgrapheme.c3l/Makefile create mode 100644 lib/libgrapheme.c3l/libgrapheme.c3i create mode 100644 lib/libgrapheme.c3l/manifest.json create mode 100644 lib/libgrapheme.c3l/project.json create mode 160000 lib/libgrapheme.c3l/thirdparty/libgrapheme diff --git a/.gitignore b/.gitignore index 87b38e4..3df608d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ *.o +*.a build/* **/.ccls-cache diff --git a/TODO b/TODO index 8ffaabc..aa82908 100644 --- a/TODO +++ b/TODO @@ -6,6 +6,8 @@ [ ] Write a README.md [ ] Use an arena allocator for cache [ ] Do not redraw if there was no update (no layout and no draw) +[ ] Better handling of the active and focused widgets, try + to maintain focus until mouse release (fix scroll bars) ## Commands [x] rect commads should have: @@ -19,11 +21,11 @@ ## Fonts [ ] Fix the missing alpha channel -[ ] Fix the allignment +[x] Fix the alignment ## Raylib [ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: -[ ] Implement pixel radius rounding for border radius +[x] Implement pixel radius rounding for border radius ## Widgets [ ] Dynamic text box to implement an fps counter diff --git a/lib/libgrapheme.c3l/Makefile b/lib/libgrapheme.c3l/Makefile new file mode 100644 index 0000000..f753fc0 --- /dev/null +++ b/lib/libgrapheme.c3l/Makefile @@ -0,0 +1,3 @@ +all: + make -C thirdparty/libgrapheme + cp thirdparty/libgrapheme/libgrapheme.a linux-x64/libgrapheme.a diff --git a/lib/libgrapheme.c3l/libgrapheme.c3i b/lib/libgrapheme.c3l/libgrapheme.c3i new file mode 100644 index 0000000..076fb4b --- /dev/null +++ b/lib/libgrapheme.c3l/libgrapheme.c3i @@ -0,0 +1,46 @@ +module grapheme; + +const uint GRAPHEME_INVALID_CODEPOINT UINT32_C = 0xFFFD; + +enum BidirectionalDirection { + GRAPHEME_BIDIRECTIONAL_DIRECTION_NEUTRAL, + GRAPHEME_BIDIRECTIONAL_DIRECTION_LTR, + GRAPHEME_BIDIRECTIONAL_DIRECTION_RTL, +} + +fn isz bidirectional_get_line_embedding_levels(uint *, isz, ichar *, isz) @extern("grapheme_bidirectional_get_line_embedding_levels"); + +fn isz bidirectional_preprocess_paragraph(uint *, isz, BidirectionalDirection, uint *, isz, BidirectionalDirection *) @extern("grapheme_bidirectional_preprocess_paragraph"); + +fn isz bidirectional_reorder_line(uint *, uint *, isz, uint *, isz) @extern("grapheme_bidirectional_reorder_line"); + +fn isz decode_utf8(char *, isz, uint *) @extern("grapheme_decode_utf8"); +fn isz encode_utf8(uint, char *, isz) @extern("grapheme_encode_utf8"); + +fn bool is_character_break(uint, uint, ushort *) @extern("grapheme_is_character_break"); + +fn bool is_lowercase(uint *, isz, isz *) @extern("grapheme_is_lowercase"); +fn bool is_titlecase(uint *, isz, isz *) @extern("grapheme_is_titlecase"); +fn bool is_uppercase(uint *, isz, isz *) @extern("grapheme_is_uppercase"); + +fn bool is_lowercase_utf8(char *, isz, isz *) @extern("grapheme_is_lowercase_utf8"); +fn bool is_titlecase_utf8(char *, isz, isz *) @extern("grapheme_is_titlecase_utf8"); +fn bool is_uppercase_utf8(char *, isz, isz *) @extern("grapheme_is_uppercase_utf8"); + +fn isz next_character_break(uint *, isz) @extern("grapheme_next_character_break"); +fn isz next_line_break(uint *, isz) @extern("grapheme_next_line_break"); +fn isz next_sentence_break(uint *, isz) @extern("grapheme_next_sentence_break"); +fn isz next_word_break(uint *, isz) @extern("grapheme_next_word_break"); + +fn isz next_character_break_utf8(char *, isz) @extern("grapheme_next_character_break_utf8"); +fn isz next_line_break_utf8(char *, isz) @extern("grapheme_next_line_break_utf8"); +fn isz next_sentence_break_utf8(char *, isz) @extern("grapheme_next_sentence_break_utf8"); +fn isz next_word_break_utf8(char *, isz) @extern("grapheme_next_word_break_utf8"); + +fn isz to_lowercase(uint *, isz, uint *, isz) @extern("grapheme_to_lowercase"); +fn isz to_titlecase(uint *, isz, uint *, isz) @extern("grapheme_to_titlecase"); +fn isz to_uppercase(uint *, isz, uint *, isz) @extern("grapheme_to_uppercase"); + +fn isz to_lowercase_utf8(char *, isz, char *, isz) @extern("grapheme_to_lowercase_utf8"); +fn isz to_titlecase_utf8(char *, isz, char *, isz) @extern("grapheme_to_titlecase_utf8"); +fn isz to_uppercase_utf8(char *, isz, char *, isz) @extern("grapheme_to_uppercase_utf8"); diff --git a/lib/libgrapheme.c3l/manifest.json b/lib/libgrapheme.c3l/manifest.json new file mode 100644 index 0000000..97f04d0 --- /dev/null +++ b/lib/libgrapheme.c3l/manifest.json @@ -0,0 +1,9 @@ +{ + "provides": "grapheme", + "targets": { + "linux-x64": { + "dependencies": [], + "linked-libraries": ["grapheme", "c"] + } + } +} diff --git a/lib/libgrapheme.c3l/project.json b/lib/libgrapheme.c3l/project.json new file mode 100644 index 0000000..876ca0c --- /dev/null +++ b/lib/libgrapheme.c3l/project.json @@ -0,0 +1,44 @@ +{ + // Language version of C3. + "langrev": "1", + // Warnings used for all targets. + "warnings": ["no-unused"], + // Directories where C3 library files may be found. + "dependency-search-paths": [".."], + // Libraries to use for all targets. + "dependencies": ["grapheme"], + // Authors, optionally with email. + "authors": ["Alessandro Mauri Date: Sat, 14 Dec 2024 01:06:58 +0100 Subject: [PATCH 047/250] use libgrapheme to interpret utf8 encoded strings --- lib/libgrapheme.c3l/libgrapheme.c3i | 2 +- project.json | 7 ++-- src/main.c3 | 4 +- src/ugui_cmd.c3 | 57 ++++++++++++++++++++++++++-- src/ugui_data.c3 | 1 - src/ugui_text.c3 | 58 +++++++++++++---------------- 6 files changed, 84 insertions(+), 45 deletions(-) diff --git a/lib/libgrapheme.c3l/libgrapheme.c3i b/lib/libgrapheme.c3l/libgrapheme.c3i index 076fb4b..6b1c7fc 100644 --- a/lib/libgrapheme.c3l/libgrapheme.c3i +++ b/lib/libgrapheme.c3l/libgrapheme.c3i @@ -1,6 +1,6 @@ module grapheme; -const uint GRAPHEME_INVALID_CODEPOINT UINT32_C = 0xFFFD; +const uint GRAPHEME_INVALID_CODEPOINT = 0xFFFD; enum BidirectionalDirection { GRAPHEME_BIDIRECTIONAL_DIRECTION_NEUTRAL, diff --git a/project.json b/project.json index 0e48c97..6c11f55 100644 --- a/project.json +++ b/project.json @@ -6,7 +6,7 @@ // Directories where C3 library files may be found. "dependency-search-paths": [ "lib" ], // Libraries to use for all targets. - "dependencies": [ "raylib", "schrift" ], + "dependencies": [ "raylib", "schrift", "grapheme" ], "features": [ // See rcore.c3 //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", @@ -17,13 +17,12 @@ //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", //"SUPPORT_TEXT_C_STRING_MANAGEMENT", //"SUPPORT_RANDOM_GENERATION", - - "SUPPORT_RAYGUI", + //"SUPPORT_RAYGUI", //"RAYGUI_NO_ICONS", //"RAYGUI_CUSTOM_ICONS", ], // Authors, optionally with email. - "authors": [ "John Doe " ], + "authors": [ "John Doe " ], // Version using semantic versioning. "version": "0.1.0", // Sources compiled for all targets. diff --git a/src/main.c3 b/src/main.c3 index 49876a3..cb37cbf 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/TTF/Hack-Regular.ttf", 16, scale: 1.5)!!; + ui.font.load("/usr/share/fonts/NerdFonts/ttf/HackNerdFont-Regular.ttf", 16, scale: 1.5)!!; short width = 800; short height = 450; @@ -94,7 +94,7 @@ fn int main(String[] args) io::printfn("slider: %f", e.slider.value); } - ui.text_unbounded("text1", "Ciao Mamma\nSono a Casa")!!; + ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; |}; ui.div_end()!!; diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 8857c5a..d882121 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -1,13 +1,15 @@ module ugui; +import std::ascii; + // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { - // FIXME: this should be culled higher up, maybe - if (rect.w <= 0 || rect.h <= 0) { - return; - } + // FIXME: this should be culled higher up, maybe + if (rect.w <= 0 || rect.h <= 0) { + return; + } Rect border = ctx.style.border; Rect padding = ctx.style.padding; @@ -48,3 +50,50 @@ fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture) }; ctx.cmd_queue.enqueue(&cmd)!; } + +fn void! Ctx.push_string(&ctx, Rect bounds, String text) +{ + if (text.len == 0) { + return; + } + + short baseline = (short)ctx.font.ascender; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; + Point orig = { + .x = bounds.x, + .y = bounds.y, + }; + + short line_len; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; + Glyph* gp; + if (!ascii::is_cntrl((char)cp)) { + gp = ctx.font.get_glyph(cp)!; + Rect gb = { + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, + }; + Rect gt = { + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, + }; + if (rect_collision(gb, bounds)) { + ctx.push_sprite(gb, gt)!; + } + line_len += gp.adv; + } else if (cp == '\n'){ + orig.y += line_height + line_gap; + line_len = 0; + } else { + continue; + } + } +} diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index e61b16f..bcfad9c 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -1,6 +1,5 @@ module ugui; -import std::io; import std::core::string; import vtree; diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 97efbae..cd5ba97 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -1,6 +1,22 @@ module ugui; import std::io; +import std::ascii; +import grapheme; + +<* +@require off != null +*> +fn Codepoint str_to_codepoint(char[] str, usz* off) +{ + Codepoint cp; + isz b = grapheme::decode_utf8(str, str.len, &cp); + if (b == 0 || b > str.len) { + return 0; + } + *off = b; + return cp; +} fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) { @@ -12,16 +28,20 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) // TODO: account for unicode codepoints short line_len; - foreach (c: text) { - Codepoint cp = (Codepoint)c; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; bool n; - if (cp != '\n') { + if (!ascii::is_cntrl((char)cp)) { gp = ctx.font.get_glyph(cp, &n)!; line_len += gp.adv; if (n) { *update_atlas = true; } - } else { + } else if (cp == '\n'){ text_bounds.h += line_height + line_gap; line_len = 0; + } else { + continue; } if (line_len > text_bounds.w) { text_bounds.w = line_len; @@ -79,33 +99,5 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) }; ctx.cmd_queue.enqueue(&bounds)!; - Point orig = { - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - short line_len; - foreach (c: text) { - Glyph* gp; - Codepoint cp = (Codepoint)c; - if (cp != '\n') { - gp = ctx.font.get_glyph(cp)!; - Rect gb = { - .x = orig.x + line_len + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, - }; - Rect gt = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, - }; - ctx.push_sprite(gb, gt)!; - line_len += gp.adv; - } else { - orig.y += line_height + line_gap; - line_len = 0; - } - } + ctx.push_string(c_elem.bounds, text)!; } From 5a89e9ec7d5f71337af4ebf094ba3d37ab2b9d11 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 14 Dec 2024 13:29:45 +0100 Subject: [PATCH 048/250] fixed formatting fuckups offered by the zed team --- src/cache.c3 | 60 +++++++++++++++++++++++----------------------- src/main.c3 | 22 ++++++++--------- src/ugui_cmd.c3 | 20 ++++++++-------- src/ugui_data.c3 | 4 ++-- src/ugui_div.c3 | 6 ++--- src/ugui_impl.c3 | 12 +++++----- src/ugui_input.c3 | 34 +++++--------------------- src/ugui_layout.c3 | 18 +++++++------- src/ugui_slider.c3 | 4 ++-- src/ugui_text.c3 | 2 +- src/vtree.c3 | 30 +++++++++++------------ 11 files changed, 95 insertions(+), 117 deletions(-) diff --git a/src/cache.c3 b/src/cache.c3 index 2b1b5f2..5b37d29 100644 --- a/src/cache.c3 +++ b/src/cache.c3 @@ -24,10 +24,10 @@ def IdTableEntry = map::Entry() @private; const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); struct Cache { - BitArr present, used; - IdTable table; - Value[] pool; - usz cycle_count; + BitArr present, used; + IdTable table; + Value[] pool; + usz cycle_count; } // Every CACHE_CYCLES operations mark as not-present the unused elements @@ -44,32 +44,32 @@ macro Cache.cycle(&cache) @private { fn void! Cache.init(&cache) { - cache.table.new_init(capacity: SIZE); - // FIXME: this shit is SLOW - foreach (idx, bit : cache.used) { cache.used[idx] = false; } - foreach (idx, bit : cache.present) { cache.present[idx] = false; } - cache.pool = mem::new_array(Value, SIZE); + cache.table.new_init(capacity: SIZE); + // FIXME: this shit is SLOW + foreach (idx, bit : cache.used) { cache.used[idx] = false; } + foreach (idx, bit : cache.present) { cache.present[idx] = false; } + cache.pool = mem::new_array(Value, SIZE); } fn void Cache.free(&cache) { - (void)cache.table.free(); - (void)mem::free(cache.pool); + (void)cache.table.free(); + (void)mem::free(cache.pool); } fn Value*! Cache.search(&cache, Key id) { - // get_entry() faults on miss + // get_entry() faults on miss IdTableEntry* entry = cache.table.get_entry(id)!; /* MISS */ if (entry.key != id) { - return SearchResult.MISSING?; + return SearchResult.MISSING?; } /* MISS, the data is not valid (not present) */ if (!cache.present[entry.value]) { - // if the data is not present but it is still in the table, remove it + // if the data is not present but it is still in the table, remove it cache.table.remove(id)!; return SearchResult.MISSING?; } @@ -83,8 +83,8 @@ fn Value*! Cache.search(&cache, Key id) /* If there is no free space left then just return the first position */ fn usz Cache.get_free_spot(&cache) @private { - const BITS = $typeof(cache.present.data[0]).sizeof*8; - foreach (idx, d: cache.present.data) { + const BITS = $typeof(cache.present.data[0]).sizeof*8; + foreach (idx, d: cache.present.data) { if (d.clz() != BITS) { return idx*BITS + BITS-d.clz(); } @@ -94,7 +94,7 @@ fn usz Cache.get_free_spot(&cache) @private fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private { - // TODO: verify index, g and id + // TODO: verify index, g and id Value* spot; /* Set used and present */ @@ -117,17 +117,17 @@ fn Value*! Cache.insert_new(&cache, Value* g, Key id) fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) { - Value*! c = cache.search(id); - if (catch e = c) { - if (e != SearchResult.MISSING) { - return e?; - } else { - // if the element is new (inserted) set the is_new flag - if (is_new) *is_new = true; - return cache.insert_new(g, id); - } - } else { - if (is_new) *is_new = false; - return c; - } + Value*! c = cache.search(id); + if (catch e = c) { + if (e != SearchResult.MISSING) { + return e?; + } else { + // if the element is new (inserted) set the is_new flag + if (is_new) *is_new = true; + return cache.insert_new(g, id); + } + } else { + if (is_new) *is_new = false; + return c; + } } diff --git a/src/main.c3 b/src/main.c3 index cb37cbf..d9bef14 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/NerdFonts/ttf/HackNerdFont-Regular.ttf", 16, scale: 1.5)!!; + ui.font.load("/usr/share/fonts/TTF/HackNerdFontMono-Regular.ttf", 16)!!; short width = 800; short height = 450; @@ -203,19 +203,19 @@ fn int main(String[] args) /* fn void! test_vtree() @test { - vtree::VTree() vt; - vt.init(10)!!; - defer vt.free(); + vtree::VTree() vt; + vt.init(10)!!; + defer vt.free(); - assert(vt.size() == 10, "Size is incorrect"); + assert(vt.size() == 10, "Size is incorrect"); - isz ref = vt.add("Ciao Mamma", 0)!!; - String s = vt.get(ref)!!; - assert(s == "Ciao Mamma", "String is incorrect"); - isz par = vt.parentof(0)!!; - assert(ref == par, "Not Root"); + isz ref = vt.add("Ciao Mamma", 0)!!; + String s = vt.get(ref)!!; + assert(s == "Ciao Mamma", "String is incorrect"); + isz par = vt.parentof(0)!!; + assert(ref == par, "Not Root"); - vt.print(); + vt.print(); } def StrCache = cache::Cache(); diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index d882121..13dfb3e 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -43,7 +43,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo // TODO: add texture id fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture) { - Cmd cmd = { + Cmd cmd = { .type = CMD_SPRITE, .sprite.rect = bounds, .sprite.texture_rect = texture, @@ -56,7 +56,7 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) if (text.len == 0) { return; } - + short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_gap = (short)ctx.font.linegap; @@ -74,16 +74,16 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) if (!ascii::is_cntrl((char)cp)) { gp = ctx.font.get_glyph(cp)!; Rect gb = { - .x = orig.x + line_len + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, }; Rect gt = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, }; if (rect_collision(gb, bounds)) { ctx.push_sprite(gb, gt)!; diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index bcfad9c..03b80da 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -71,8 +71,8 @@ struct Div { // slider element struct Slider { - float value; - Rect handle; + float value; + Rect handle; } struct Text { diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 833b6ec..657babe 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -12,7 +12,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) ctx.active_div = div_node; // 1. Fill the element fields - c_elem.type = ETYPE_DIV; + c_elem.type = ETYPE_DIV; // do layout and update flags only if the element was updated if (c_elem.flags.is_new || parent.flags.updated) { @@ -31,8 +31,8 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) c_elem.flags.updated = true; // 4. Fill the div fields c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, }; c_elem.div.origin_r = c_elem.div.origin_c; c_elem.div.layout = parent.div.layout; diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index be172c1..e49c8d4 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -130,12 +130,12 @@ fn void! Ctx.frame_end(&ctx) // draw mouse position $if 1: Cmd cmd = { - .type = CMD_RECT, - .rect.rect = { - .x = ctx.input.mouse.pos.x - 2, - .y = ctx.input.mouse.pos.y - 2, - .w = 4, - .h = 4, + .type = CMD_RECT, + .rect.rect = { + .x = ctx.input.mouse.pos.x - 2, + .y = ctx.input.mouse.pos.y - 2, + .w = 4, + .h = 4, }, .rect.color = uint_to_rgba(0xff00ffff) }; diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index fba777b..1b49ae5 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -41,20 +41,9 @@ bitstruct MouseButtons : uint { bool btn_5 : 4; } -macro Ctx.mouse_pressed(&ctx) -{ - return ctx.input.mouse.updated & ctx.input.mouse.down; -} - -macro Ctx.mouse_released(&ctx) -{ - return ctx.input.mouse.updated & ~ctx.input.mouse.down; -} - -macro Ctx.mouse_down(&ctx) -{ - return ctx.input.mouse.down; -} +macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down; +macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down; +macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down; const MouseButtons BTN_NONE = (MouseButtons)0u; const MouseButtons BTN_ANY = (MouseButtons)(uint.max); @@ -66,20 +55,9 @@ const MouseButtons BTN_5 = {.btn_5 = true}; // FIXME: hthis compairson could be done with a cast using MouseButtons.inner // property but I could not figure out how -macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) -{ - return (ctx.mouse_pressed() & btn) != BTN_NONE; -} - -macro Ctx.is_mouse_released(&ctx, MouseButtons btn) -{ - return (ctx.mouse_released() & btn) != BTN_NONE; -} - -macro Ctx.is_mouse_down(&ctx, MouseButtons btn) -{ - return (ctx.mouse_down() & btn) != BTN_NONE; -} +macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE; +macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE; +macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE; macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index d403051..5b56913 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -51,8 +51,8 @@ fn void! Ctx.layout_next_row(&ctx) } parent.div.origin_r = Point{ - .x = parent.bounds.x, - .y = parent.div.origin_c.y, + .x = parent.bounds.x, + .y = parent.div.origin_c.y, }; parent.div.origin_c = parent.div.origin_r; } @@ -68,8 +68,8 @@ fn void! Ctx.layout_next_column(&ctx) } parent.div.origin_c = Point{ - .x = parent.div.origin_r.x, - .y = parent.bounds.y, + .x = parent.div.origin_r.x, + .y = parent.bounds.y, }; parent.div.origin_r = parent.div.origin_c; } @@ -84,7 +84,7 @@ fn void! Ctx.layout_next_column(&ctx) *> fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { - Rect placement; + Rect placement; Point origin; Div* div = &parent.div; @@ -128,12 +128,12 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // 3. Update the origins of the parent div.origin_r = Point{ - .x = pl_corner.x, - .y = origin.y, + .x = pl_corner.x, + .y = origin.y, }; div.origin_c = Point{ - .x = origin.x, - .y = pl_corner.y, + .x = origin.x, + .y = pl_corner.y, }; // 4. Calculate the "scrolled" view diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index ddabc4e..59ef66b 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -33,7 +33,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) c_elem.events = ctx.get_elem_events(c_elem); if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w); float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w); c_elem.slider.handle.x = x; @@ -89,7 +89,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) c_elem.events = ctx.get_elem_events(c_elem); if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h); float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h); c_elem.slider.handle.y = y; diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index cd5ba97..e39c82a 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -62,7 +62,7 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // 1. Fill the element fields // this resets the flags - c_elem.type = ETYPE_TEXT; + c_elem.type = ETYPE_TEXT; short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; diff --git a/src/vtree.c3 b/src/vtree.c3 index 8e5d7b1..42b5d19 100644 --- a/src/vtree.c3 +++ b/src/vtree.c3 @@ -10,11 +10,11 @@ struct VTree { } fault VTreeError { - CANNOT_SHRINK, - INVALID_REFERENCE, - TREE_FULL, - REFERENCE_NOT_PRESENT, - INVALID_ARGUMENT, + CANNOT_SHRINK, + INVALID_REFERENCE, + TREE_FULL, + REFERENCE_NOT_PRESENT, + INVALID_ARGUMENT, } macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } @@ -116,8 +116,8 @@ fn void! VTree.resize(&tree, usz newsize) defer catch { (void)mem::free(tree.ordered_refs); } if (newsize > tree.size()) { - tree.vector[old_size..newsize-1] = @zero(); - tree.refs[old_size..newsize-1] = -1; + tree.vector[old_size..newsize-1] = @zero(); + tree.refs[old_size..newsize-1] = -1; } } @@ -131,13 +131,13 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent) // no space left if (tree.elements >= tree.size()) { - return VTreeError.TREE_FULL?; + return VTreeError.TREE_FULL?; } // check if the parent exists // if there are no elements in the tree the first add will set the root if (!tree.ref_is_present(parent) && tree.elements != 0) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return VTreeError.REFERENCE_NOT_PRESENT?; } // get the first free spot @@ -164,7 +164,7 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent) // returns the number of pruned elements fn usz! VTree.prune(&tree, isz ref) { - if (!tree.ref_is_valid(ref)) { + if (!tree.ref_is_valid(ref)) { return VTreeError.INVALID_REFERENCE?; } @@ -189,7 +189,7 @@ fn usz! VTree.prune(&tree, isz ref) // find the size of the subtree starting from ref fn usz! VTree.subtree_size(&tree, isz ref) { - if (!tree.ref_is_valid(ref)) { + if (!tree.ref_is_valid(ref)) { return VTreeError.INVALID_REFERENCE?; } @@ -250,7 +250,7 @@ fn isz! VTree.children_it(&tree, isz parent, isz *cursor) fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) { if (cursor == null) { - return VTreeError.INVALID_ARGUMENT?; + return VTreeError.INVALID_ARGUMENT?; } isz[] queue = tree.ordered_refs; @@ -301,7 +301,7 @@ fn isz! VTree.parentof(&tree, isz ref) } if (!tree.ref_is_present(ref)) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return VTreeError.REFERENCE_NOT_PRESENT?; } return tree.refs[ref]; @@ -309,12 +309,12 @@ fn isz! VTree.parentof(&tree, isz ref) fn ElemType! VTree.get(&tree, isz ref) { - if (!tree.ref_is_valid(ref)) { + if (!tree.ref_is_valid(ref)) { return VTreeError.INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return VTreeError.REFERENCE_NOT_PRESENT?; } return tree.vector[ref]; From 6d8300f9d913c5e65e349596ecd51d02d1b6892b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Dec 2024 21:39:26 +0100 Subject: [PATCH 049/250] better font atlas implementation --- src/main.c3 | 3 +- src/ugui_atlas.c3 | 85 ++++++++++++++++++++++++++++++++++++++++++ src/ugui_cmd.c3 | 54 +++++++++++++++++++++++++++ src/ugui_data.c3 | 39 ------------------- src/ugui_font.c3 | 95 ++++++++++------------------------------------- src/ugui_impl.c3 | 9 ++++- src/ugui_text.c3 | 28 ++------------ 7 files changed, 170 insertions(+), 143 deletions(-) create mode 100644 src/ugui_atlas.c3 diff --git a/src/main.c3 b/src/main.c3 index d9bef14..2a9b439 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -27,7 +27,7 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; - ui.font.load("/usr/share/fonts/TTF/HackNerdFontMono-Regular.ttf", 16)!!; + ui.load_font("font1", "/usr/share/fonts/TTF/HackNerdFontMono-Regular.ttf", 16)!!; short width = 800; short height = 450; @@ -195,7 +195,6 @@ fn int main(String[] args) rl::close_window(); - ui.font.free(); ui.free(); return 0; } diff --git a/src/ugui_atlas.c3 b/src/ugui_atlas.c3 new file mode 100644 index 0000000..0f04c08 --- /dev/null +++ b/src/ugui_atlas.c3 @@ -0,0 +1,85 @@ +module ugui; + +import std::io; + +fault UgAtlasError { + CANNOT_PLACE, + INVALID_TYPE, +} + +enum AtlasType { + ATLAS_GRAYSCALE, +} + +// black and white atlas +struct Atlas { + AtlasType type; + Id id; + + ushort width, height; + char[] buffer; + + Point row; + ushort row_h; +} + +// bytes per pixel +macro usz AtlasType.bpp(type) +{ + switch (type) { + case ATLAS_GRAYSCALE: return 1; + } +} + +fn void! Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height) +{ + atlas.id = id; + atlas.type = type; + atlas.width = width; + atlas.height = height; + + atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height*type.bpp()); +} + +fn void Atlas.free(&atlas) +{ + free(atlas.buffer); +} + +// place a rect inside the atlas +// uses a row first algorithm +// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ +fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h) +{ + Point p; + + if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { + p = atlas.row; + } else { + atlas.row.x = 0; + atlas.row.y = atlas.row.y + atlas.row_h; + atlas.row_h = 0; + if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { + p = atlas.row; + } else { + return UgAtlasError.CANNOT_PLACE?; + } + } + + for (usz y = 0; y < h; y++) { + for (usz x = 0; x < w; x++) { + char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x) ..]; + char[] pix = pixels[y*w + x ..]; + usz bpp = atlas.type.bpp(); + + buf[0..bpp-1] = pix[0..bpp-1]; + } + } + + atlas.row.x += w; + if (h > atlas.row_h) { + atlas.row_h = h; + } + + return p; +} diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 13dfb3e..e021ad1 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -2,6 +2,45 @@ module ugui; import std::ascii; +// command type +enum CmdType { + CMD_RECT, + CMD_UPDATE_ATLAS, + CMD_SPRITE, +} + +// command to draw a rect +struct CmdRect { + Rect rect; + ushort radius; + Color color; +} + +// FIXME: For now only support black and white atlas, so PIXELFORMAT_UNCOMPRESSED_GRAYSCALE +struct CmdUpdateAtlas { + Id id; + char* raw_buffer; + short width, height, bpp; +} + +// TODO: +// 1. Add atlases as a data type +// 2. Each atlas has an id +struct CmdSprite { + Rect rect; + Rect texture_rect; +} + +// command structure +struct Cmd { + CmdType type; + union { + CmdRect rect; + CmdUpdateAtlas update_atlas; + CmdSprite sprite; + } +} + // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) @@ -97,3 +136,18 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) } } } + +fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) +{ + Cmd up = { + .type = CMD_UPDATE_ATLAS, + .update_atlas = { + .id = atlas.id, + .raw_buffer = atlas.buffer, + .width = atlas.width, + .height = atlas.height, + .bpp = (ushort)atlas.type.bpp(), + }, + }; + ctx.cmd_queue.enqueue(&up)!; +} \ No newline at end of file diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 03b80da..1ec0539 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -128,45 +128,6 @@ const uint MAX_ELEMS = 128; const uint MAX_CMDS = 256; const uint ROOT_ID = 1; -// command type -enum CmdType { - CMD_RECT, - CMD_UPDATE_ATLAS, - CMD_SPRITE, -} - -// command to draw a rect -// TODO: implement radius -struct CmdRect { - Rect rect; - ushort radius; - Color color; -} - -// FIXME: For now only support black and white atlas, so PIXELFORMAT_UNCOMPRESSED_GRAYSCALE -struct CmdUpdateAtlas { - char* raw_buffer; - short width, height; -} - -// TODO: -// 1. Add atlases as a data type -// 2. Each atlas has an id -struct CmdSprite { - Rect rect; - Rect texture_rect; -} - -// command structure -struct Cmd { - CmdType type; - union { - CmdRect rect; - CmdUpdateAtlas update_atlas; - CmdSprite sprite; - } -} - enum Layout { ROW, COLUMN, diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index f188fbb..68d1be8 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -34,7 +34,6 @@ struct Glyph { ushort u, v; ushort w, h; short adv, ox, oy; - short idx; // atlas index } const uint FONT_CACHED = 512; @@ -47,79 +46,23 @@ fault UgFontError { RENDER_ERROR, } -fault UgAtlasError { - CANNOT_PLACE, -} - -// black and white atlas -struct AtlasBW { - ushort width, height; - char[] buffer; - - Point row; - ushort row_h; -} - struct Font { schrift::Sft sft; String path; + Id id; // font id, same as atlas id GlyphTable table; float size; float ascender, descender, linegap; // Line Metrics - AtlasBW[] atlas; + Atlas atlas; + bool should_update; // should send update_atlas command, resets at frame_end() } -fn void! AtlasBW.new(&atlas, ushort width, ushort height) -{ - atlas.width = width; - atlas.height = height; - atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height); -} - -fn void AtlasBW.free(&atlas) -{ - free(atlas.buffer); -} - -// place a rect inside the atlas -// uses a row first algorithm -// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ -fn Point! AtlasBW.place(&atlas, char[] pixels, ushort w, ushort h) -{ - Point p; - - if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { - p = atlas.row; - } else { - atlas.row.x = 0; - atlas.row.y = atlas.row.y + atlas.row_h; - atlas.row_h = 0; - if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { - p = atlas.row; - } else { - return UgAtlasError.CANNOT_PLACE?; - } - } - - for (usz y = 0; y < h; y++) { - for (usz x = 0; x < w; x++) { - atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x]; - } - } - - atlas.row.x += w; - if (h > atlas.row_h) { - atlas.row_h = h; - } - - return p; -} - -fn void! Font.load(&font, String path, uint height, float scale = 1) +fn void! Font.load(&font, String name, String path, uint height, float scale) { font.table.new_init(capacity: FONT_CACHED); - + font.id = name.hash(); + font.size = height*scale; font.sft = schrift::Sft{ @@ -142,27 +85,25 @@ fn void! Font.load(&font, String path, uint height, float scale = 1) // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter // like the letter 'A' - font.atlas = mem::new_array(AtlasBW, 1); ushort size = (ushort)font.size*256; - font.atlas[0].new(size, size)!; - + font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!; + // preallocate the ASCII range // for (char c = ' '; c < '~'; c++) { // font.get_glyph((Codepoint)c)!; // } } -fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) +fn Glyph*! Font.get_glyph(&font, Codepoint code) { Glyph*! gp; gp = font.table.get_ref(code); - + if (catch excuse = gp) { if (excuse != SearchResult.MISSING) { return excuse?; } } else { - if (is_new) { *is_new = false; } return gp; } @@ -171,7 +112,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) schrift::SftGlyph gid; schrift::SftGMetrics gmtx; - + if (schrift::lookup(&font.sft, code, &gid) < 0) { return UgFontError.MISSING_GLYPH?; } @@ -199,8 +140,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) //io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d", // glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv); - Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!; - glyph.idx = 0; + Point uv = font.atlas.place(pixels, glyph.w, glyph.h)!; glyph.u = uv.x; glyph.v = uv.y; @@ -208,14 +148,17 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null) font.table.set(code, glyph); - if (is_new) { *is_new = true; } + font.should_update = true; return font.table.get_ref(code); } fn void Font.free(&font) { - foreach (atlas: font.atlas) { - atlas.free(); - } + font.atlas.free(); schrift::freefont(font.sft.font); } + +fn void! Ctx.load_font(&ctx, String name, String path, uint height, float scale = 1.0) +{ + return ctx.font.load(name, path, height, scale); +} \ No newline at end of file diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index e49c8d4..541dfb7 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -67,6 +67,7 @@ fn void Ctx.free(&ctx) (void)ctx.tree.free(); (void)ctx.cache.free(); (void)ctx.cmd_queue.free(); + (void)ctx.font.free(); } fn void! Ctx.frame_begin(&ctx) @@ -127,8 +128,14 @@ fn void! Ctx.frame_end(&ctx) ctx.input.events = (InputEvents)0; ctx.input.events.force_update = f; - // draw mouse position + // send atlas updates + if (ctx.font.should_update) { + ctx.push_update_atlas(&ctx.font.atlas)!; + ctx.font.should_update = false; + } + $if 1: + // draw mouse position Cmd cmd = { .type = CMD_RECT, .rect.rect = { diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index e39c82a..4b25ad8 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -18,7 +18,7 @@ fn Codepoint str_to_codepoint(char[] str, usz* off) return cp; } -fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) +fn Rect! Ctx.get_text_bounds(&ctx, String text) { Rect text_bounds; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; @@ -34,9 +34,8 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas) off += x; bool n; if (!ascii::is_cntrl((char)cp)) { - gp = ctx.font.get_glyph(cp, &n)!; + gp = ctx.font.get_glyph(cp)!; line_len += gp.adv; - if (n) { *update_atlas = true; } } else if (cp == '\n'){ text_bounds.h += line_height + line_gap; line_len = 0; @@ -67,10 +66,9 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_gap = (short)ctx.font.linegap; - bool update_atlas; // if the element is new or the parent was updated then redo layout if (c_elem.flags.is_new || parent.flags.updated) { - Rect text_size = ctx.get_text_bounds(text, &update_atlas)!; + Rect text_size = ctx.get_text_bounds(text)!; // 2. Layout c_elem.bounds = ctx.position_element(parent, text_size, true); @@ -79,25 +77,5 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) c_elem.text.str = text; } - if (update_atlas) { - // FIXME: atlas here is hardcoded, look at the todo in ugui_data - Cmd up = { - .type = CMD_UPDATE_ATLAS, - .update_atlas = { - .raw_buffer = ctx.font.atlas[0].buffer, - .width = ctx.font.atlas[0].width, - .height = ctx.font.atlas[0].height, - }, - }; - ctx.cmd_queue.enqueue(&up)!; - } - - Cmd bounds = { - .type = CMD_RECT, - .rect.rect = c_elem.bounds, - .rect.color = uint_to_rgba(0x000000ff), - }; - ctx.cmd_queue.enqueue(&bounds)!; - ctx.push_string(c_elem.bounds, text)!; } From bca29c537cfdf818577f79cc5a279d1412b9dcda Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Dec 2024 22:29:07 +0100 Subject: [PATCH 050/250] scissor command --- src/main.c3 | 13 ++++++------ src/ugui_cmd.c3 | 22 ++++++++++++++++++++- src/ugui_data.c3 | 12 ++++++------ src/ugui_font.c3 | 49 ++++++++++++++++++++++++++++++++++++++++++++++ src/ugui_layout.c3 | 2 +- src/ugui_text.c3 | 48 --------------------------------------------- 6 files changed, 83 insertions(+), 63 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 2a9b439..5acebe9 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -162,7 +162,6 @@ fn int main(String[] args) rl::unload_texture(font_texture); } font_texture = rl::load_texture_from_image(font_atlas); - //rl::draw_texture(font_texture, 0, 0, rl::WHITE); case ugui::CmdType.CMD_SPRITE: rl::Rectangle source = { .x = cmd.sprite.texture_rect.x, @@ -176,12 +175,12 @@ fn int main(String[] args) }; rl::draw_texture_rec(font_texture, source, position, rl::WHITE); - //rl::draw_rectangle(cmd.sprite.rect.x, - // cmd.sprite.rect.y, - // cmd.sprite.rect.w, - // cmd.sprite.rect.h, - // rl::WHITE - //); + case ugui::CmdType.CMD_SCISSOR: + if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { + rl::end_scissor_mode(); + } else { + rl::begin_scissor_mode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h); + } default: io::printfn("Unknown cmd type: %s", cmd.type); } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index e021ad1..16b49e5 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -7,6 +7,7 @@ enum CmdType { CMD_RECT, CMD_UPDATE_ATLAS, CMD_SPRITE, + CMD_SCISSOR, } // command to draw a rect @@ -16,7 +17,6 @@ struct CmdRect { Color color; } -// FIXME: For now only support black and white atlas, so PIXELFORMAT_UNCOMPRESSED_GRAYSCALE struct CmdUpdateAtlas { Id id; char* raw_buffer; @@ -31,6 +31,11 @@ struct CmdSprite { Rect texture_rect; } +// if rect is zero Rect{0} then reset the scissor +struct CmdScissor { + Rect rect; +} + // command structure struct Cmd { CmdType type; @@ -38,6 +43,7 @@ struct Cmd { CmdRect rect; CmdUpdateAtlas update_atlas; CmdSprite sprite; + CmdScissor scissor; } } @@ -96,6 +102,8 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) return; } + ctx.push_scissor(bounds)!; + short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_gap = (short)ctx.font.linegap; @@ -135,6 +143,9 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) continue; } } + + // FIXME: we never get here if an error was thrown before + ctx.push_scissor(Rect{})!; } fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) @@ -150,4 +161,13 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) }, }; ctx.cmd_queue.enqueue(&up)!; +} + +fn void! Ctx.push_scissor(&ctx, Rect rect) +{ + Cmd sc = { + .type = CMD_SCISSOR, + .scissor.rect = rect, + }; + ctx.cmd_queue.enqueue(&sc)!; } \ No newline at end of file diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index 1ec0539..7cd39da 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -54,7 +54,7 @@ enum DivLayout { } // div element -struct Div { +struct ElemDiv { DivLayout layout; struct scroll { bool can_x; @@ -70,12 +70,12 @@ struct Div { } // slider element -struct Slider { +struct ElemSlider { float value; Rect handle; } -struct Text { +struct ElemText { char* str; } @@ -87,9 +87,9 @@ struct Elem { Rect bounds; ElemType type; union { - Div div; - Slider slider; - Text text; + ElemDiv div; + ElemSlider slider; + ElemText text; } } diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 68d1be8..f465650 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -1,9 +1,12 @@ module ugui; import schrift; +import grapheme; import std::collections::map; import std::core::mem; import std::io; +import std::ascii; + // unicode code point, different type for a different hash def Codepoint = uint; @@ -161,4 +164,50 @@ fn void Font.free(&font) fn void! Ctx.load_font(&ctx, String name, String path, uint height, float scale = 1.0) { return ctx.font.load(name, path, height, scale); +} + +<* +@require off != null +*> +fn Codepoint str_to_codepoint(char[] str, usz* off) +{ + Codepoint cp; + isz b = grapheme::decode_utf8(str, str.len, &cp); + if (b == 0 || b > str.len) { + return 0; + } + *off = b; + return cp; +} + +fn Rect! Ctx.get_text_bounds(&ctx, String text) +{ + Rect text_bounds; + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; + text_bounds.h = line_height; + Glyph* gp; + + // TODO: account for unicode codepoints + short line_len; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; + bool n; + if (!ascii::is_cntrl((char)cp)) { + gp = ctx.font.get_glyph(cp)!; + line_len += gp.adv; + } else if (cp == '\n'){ + text_bounds.h += line_height + line_gap; + line_len = 0; + } else { + continue; + } + if (line_len > text_bounds.w) { + text_bounds.w = line_len; + } + } + + return text_bounds; } \ No newline at end of file diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 5b56913..dcf63f4 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -86,7 +86,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { Rect placement; Point origin; - Div* div = &parent.div; + ElemDiv* div = &parent.div; // 1. Select the right origin switch (div.layout) { diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 4b25ad8..88fb581 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -1,54 +1,6 @@ module ugui; import std::io; -import std::ascii; -import grapheme; - -<* -@require off != null -*> -fn Codepoint str_to_codepoint(char[] str, usz* off) -{ - Codepoint cp; - isz b = grapheme::decode_utf8(str, str.len, &cp); - if (b == 0 || b > str.len) { - return 0; - } - *off = b; - return cp; -} - -fn Rect! Ctx.get_text_bounds(&ctx, String text) -{ - Rect text_bounds; - short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; - short line_gap = (short)ctx.font.linegap; - text_bounds.h = line_height; - Glyph* gp; - - // TODO: account for unicode codepoints - short line_len; - Codepoint cp; - usz off, x; - while ((cp = str_to_codepoint(text[off..], &x)) != 0) { - off += x; - bool n; - if (!ascii::is_cntrl((char)cp)) { - gp = ctx.font.get_glyph(cp)!; - line_len += gp.adv; - } else if (cp == '\n'){ - text_bounds.h += line_height + line_gap; - line_len = 0; - } else { - continue; - } - if (line_len > text_bounds.w) { - text_bounds.w = line_len; - } - } - - return text_bounds; -} fn void! Ctx.text_unbounded(&ctx, String label, String text) { From 2e60e4c5b8281fcd792f6de244cae5994c817823 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 13:54:53 +0100 Subject: [PATCH 051/250] fix font alpha channel --- TODO | 16 +++++++++++----- src/main.c3 | 26 +++++++++++++++++++++++++- src/ugui_font.c3 | 4 ++-- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/TODO b/TODO index aa82908..dd8d4d6 100644 --- a/TODO +++ b/TODO @@ -1,32 +1,38 @@ # TODOs, semi-random sorting + [x] Implement glyph draw command [x] Implement div.view and scrollbars -[ ] Port font system from C to C3 (rewrite1) +[x] Port font system from C to C3 (rewrite1) [ ] Update ARCHITECTURE.md [ ] Write a README.md [ ] Use an arena allocator for cache [ ] Do not redraw if there was no update (no layout and no draw) [ ] Better handling of the active and focused widgets, try - to maintain focus until mouse release (fix scroll bars) +to maintain focus until mouse release (fix scroll bars) ## Commands + [x] rect commads should have: - * border width - * border radius +_ border width +_ border radius [x] add a command to update an atlas ## Atlases + [ ] Add an interface to create, destroy, update and get atlases based on their ids [ ] Implement multiple font atlases ## Fonts -[ ] Fix the missing alpha channel + +[x] Fix the missing alpha channel [x] Fix the alignment ## Raylib + [ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: [x] Implement pixel radius rounding for border radius ## Widgets + [ ] Dynamic text box to implement an fps counter [ ] Button with label diff --git a/src/main.c3 b/src/main.c3 index 5acebe9..c447cda 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -23,6 +23,28 @@ fn void Times.print_stats(×) io::printfn("min=%s, max=%s, avg=%s", min, max, avg); } +const ZString FONT_FS = ` +#version 330 + +// Input vertex attributes (from vertex shader) +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +void main() +{ + vec4 alpha = texture(texture0, fragTexCoord); + finalColor = colDiffuse*fragColor; + finalColor.a *= alpha.r; +} +`; + fn int main(String[] args) { ugui::Ctx ui; @@ -42,6 +64,7 @@ fn int main(String[] args) time::Clock clock; Times ui_times; Times draw_times; + rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); // Main loop while (!rl::window_should_close()) { @@ -173,8 +196,9 @@ fn int main(String[] args) .x = cmd.sprite.rect.x, .y = cmd.sprite.rect.y, }; - + rl::begin_shader_mode(font_shader); rl::draw_texture_rec(font_texture, source, position, rl::WHITE); + rl::end_shader_mode(); case ugui::CmdType.CMD_SCISSOR: if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { rl::end_scissor_mode(); diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index f465650..6b5ecde 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -61,7 +61,7 @@ struct Font { bool should_update; // should send update_atlas command, resets at frame_end() } -fn void! Font.load(&font, String name, String path, uint height, float scale) +fn void! Font.load(&font, String name, ZString path, uint height, float scale) { font.table.new_init(capacity: FONT_CACHED); font.id = name.hash(); @@ -161,7 +161,7 @@ fn void Font.free(&font) schrift::freefont(font.sft.font); } -fn void! Ctx.load_font(&ctx, String name, String path, uint height, float scale = 1.0) +fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0) { return ctx.font.load(name, path, height, scale); } From 7b7aac8df4184b0e8de2bde64735fcf7c4d066fb Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 14:08:08 +0100 Subject: [PATCH 052/250] schrift use ZString where necessary --- lib/libschrift.c3l/libschrift.c3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/libschrift.c3l/libschrift.c3 b/lib/libschrift.c3l/libschrift.c3 index 9cf8f31..dde82a5 100644 --- a/lib/libschrift.c3l/libschrift.c3 +++ b/lib/libschrift.c3l/libschrift.c3 @@ -45,10 +45,10 @@ struct SftImage int height; } -extern fn char* sft_version() @extern("sft_version"); +extern fn ZString sft_version() @extern("sft_version"); extern fn SftFont loadmem(void* mem, usz size) @extern("sft_loadmem"); -extern fn SftFont loadfile(char* filename) @extern("sft_loadfile"); +extern fn SftFont loadfile(ZString filename) @extern("sft_loadfile"); extern fn void freefont(SftFont font) @extern("sft_freefont"); extern fn int lmetrics(Sft* sft, SftLMetrics* metrics) @extern("sft_lmetrics"); From 3a7655a3f0393a60bf19fe5eb06873a5e345eef8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 17:05:44 +0100 Subject: [PATCH 053/250] specify texture id in the sprite command --- src/main.c3 | 11 ++++++++--- src/ugui_cmd.c3 | 23 +++++++++++++---------- src/ugui_font.c3 | 6 ++++++ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index c447cda..667ac73 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -64,7 +64,12 @@ fn int main(String[] args) time::Clock clock; Times ui_times; Times draw_times; + + // font stuff rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); +rl::Image font_atlas; + rl::Texture2D font_texture; + ugui::Id font_id = ui.get_font_id("font1"); // Main loop while (!rl::window_should_close()) { @@ -152,8 +157,6 @@ fn int main(String[] args) rl::Color c; rl::Rectangle r; - static rl::Image font_atlas; - static rl::Texture2D font_texture; for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: @@ -174,7 +177,8 @@ fn int main(String[] args) float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width; rl::draw_rectangle_rounded(r, roundness, 0, c); case ugui::CmdType.CMD_UPDATE_ATLAS: - rl::unload_image(font_atlas); +if (cmd.update_atlas.id != font_id) { break; } + //rl::unload_image(font_atlas); font_atlas.data = cmd.update_atlas.raw_buffer; font_atlas.width = cmd.update_atlas.width; font_atlas.height = cmd.update_atlas.height; @@ -186,6 +190,7 @@ fn int main(String[] args) } font_texture = rl::load_texture_from_image(font_atlas); case ugui::CmdType.CMD_SPRITE: + if (cmd.sprite.texture_id != font_id) { break; } rl::Rectangle source = { .x = cmd.sprite.texture_rect.x, .y = cmd.sprite.texture_rect.y, diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 16b49e5..259bb48 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -29,6 +29,7 @@ struct CmdUpdateAtlas { struct CmdSprite { Rect rect; Rect texture_rect; + Id texture_id; } // if rect is zero Rect{0} then reset the scissor @@ -86,12 +87,13 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo } // TODO: add texture id -fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture) +fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id) { Cmd cmd = { .type = CMD_SPRITE, .sprite.rect = bounds, .sprite.texture_rect = texture, + .sprite.texture_id = texture_id, }; ctx.cmd_queue.enqueue(&cmd)!; } @@ -107,6 +109,7 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_gap = (short)ctx.font.linegap; + Id texture_id = ctx.font.id; // or ctx.font.atlas.id Point orig = { .x = bounds.x, .y = bounds.y, @@ -121,19 +124,19 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) if (!ascii::is_cntrl((char)cp)) { gp = ctx.font.get_glyph(cp)!; Rect gb = { - .x = orig.x + line_len + gp.ox, - .y = orig.y + gp.oy + baseline, - .w = gp.w, - .h = gp.h, + .x = orig.x + line_len + gp.ox, + .y = orig.y + gp.oy + baseline, + .w = gp.w, + .h = gp.h, }; Rect gt = { - .x = gp.u, - .y = gp.v, - .w = gp.w, - .h = gp.h, + .x = gp.u, + .y = gp.v, + .w = gp.w, + .h = gp.h, }; if (rect_collision(gb, bounds)) { - ctx.push_sprite(gb, gt)!; + ctx.push_sprite(gb, gt, texture_id)!; } line_len += gp.adv; } else if (cp == '\n'){ diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 6b5ecde..75316fe 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -210,4 +210,10 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text) } return text_bounds; +} + +// TODO: check if the font is present in the context +fn Id Ctx.get_font_id(&ctx, String label) +{ + return (Id)label.hash(); } \ No newline at end of file From 2e0c6333d38a0fb886017ded1bc42793d610cf90 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 17:06:16 +0100 Subject: [PATCH 054/250] draw border around floating divs --- src/ugui_div.c3 | 3 ++- src/ugui_impl.c3 | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 657babe..3706c34 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -39,7 +39,8 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) } // Add the background to the draw stack - ctx.push_rect(c_elem.bounds, c_elem.div.color_bg)!; + bool do_border = parent.div.layout == LAYOUT_FLOATING; + ctx.push_rect(c_elem.bounds, c_elem.div.color_bg, do_border: do_border)!; // TODO: check active // TODO: check resizeable diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 541dfb7..e2da44a 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -120,6 +120,9 @@ fn void! Ctx.force_update(&ctx) fn void! Ctx.frame_end(&ctx) { + Elem* root = ctx.get_elem_by_tree_idx(0)!; + root.div.layout = LAYOUT_ROW; + // 1. clear the tree ctx.tree.prune(0)!; From 7d9a8a13630316b25e4725f66b8e1b7352d9e840 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 17:06:33 +0100 Subject: [PATCH 055/250] pre-cache ascii range in font atlas --- src/ugui_font.c3 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 75316fe..6843da0 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -92,9 +92,9 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale) font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!; // preallocate the ASCII range -// for (char c = ' '; c < '~'; c++) { -// font.get_glyph((Codepoint)c)!; -// } + for (char c = ' '; c < '~'; c++) { + font.get_glyph((Codepoint)c)!; + } } fn Glyph*! Font.get_glyph(&font, Codepoint code) From c1a7b4fcdb71c72f08c9a5df4c7e121f8ae5c15d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 16 Dec 2024 17:06:46 +0100 Subject: [PATCH 056/250] display timing statistics --- src/main.c3 | 54 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 667ac73..a2ad397 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -5,6 +5,7 @@ import ugui; import rl; import std::time; import std::collections::ringbuffer; +import std::core::string; def Times = ringbuffer::RingBuffer(); @@ -23,6 +24,26 @@ fn void Times.print_stats(×) io::printfn("min=%s, max=%s, avg=%s", min, max, avg); } +struct TimeStats { + time::NanoDuration min, max, avg; +} + +fn TimeStats Times.get_stats(×) +{ + time::NanoDuration min, max, avg, x; + min = times.get(0); + for (usz i = 0; i < times.written; i++) { + x = times.get(i); + if (x < min) { min = x; } + if (x > max) { max = x; } + avg += x; + } + avg = (NanoDuration)((ulong)avg/128.0); + + return TimeStats{.min = min, .max = max, .avg = avg}; +} + + const ZString FONT_FS = ` #version 330 @@ -67,7 +88,7 @@ fn int main(String[] args) // font stuff rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); -rl::Image font_atlas; + rl::Image font_atlas; rl::Texture2D font_texture; ugui::Id font_id = ui.get_font_id("font1"); @@ -146,6 +167,23 @@ rl::Image font_atlas; |}; ui.div_end()!!; + // Timings counter + TimeStats dts = draw_times.get_stats(); + TimeStats uts = ui_times.get_stats(); + + ui.layout_set_floating()!!; + ui.div_begin("fps", ugui::Rect{0, ui.height-50, 200, 50})!!; + {| + ui.layout_set_row()!!; + ui.text_unbounded("ui avg", string::tformat("ui avg: %s", uts.avg))!!; + + ui.layout_next_row()!!; + ui.text_unbounded("draw avg", string::tformat("draw avg: %s", dts.avg))!!; + + //ui.force_update()!!; + |}; + ui.div_end()!!; + ui.frame_end()!!; /* End UI Handling */ ui_times.push(clock.mark()); @@ -161,10 +199,10 @@ rl::Image font_atlas; switch (cmd.type) { case ugui::CmdType.CMD_RECT: c = rl::Color{ - .r = cmd.rect.color.r, - .g = cmd.rect.color.g, - .b = cmd.rect.color.b, - .a = cmd.rect.color.a, + .r = cmd.rect.color.r, + .g = cmd.rect.color.g, + .b = cmd.rect.color.b, + .a = cmd.rect.color.a, }; r = rl::Rectangle{ .x = cmd.rect.rect.x, @@ -172,12 +210,12 @@ rl::Image font_atlas; .height = cmd.rect.rect.h, .width = cmd.rect.rect.w, }; - float rad = cmd.rect.radius; + float rad = cmd.rect.radius; // for some weird-ass reason the straight forward inverse formula does not work - float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width; + float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width; rl::draw_rectangle_rounded(r, roundness, 0, c); case ugui::CmdType.CMD_UPDATE_ATLAS: -if (cmd.update_atlas.id != font_id) { break; } + if (cmd.update_atlas.id != font_id) { break; } //rl::unload_image(font_atlas); font_atlas.data = cmd.update_atlas.raw_buffer; font_atlas.width = cmd.update_atlas.width; From c0e9565bf6f55832b613595205c07f942c746622 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 17 Dec 2024 11:26:59 +0100 Subject: [PATCH 057/250] idk some stuff --- .gitignore | 1 + TODO | 1 + project.json | 2 +- resources/hack-nerd.ttf | Bin 0 -> 2618892 bytes src/main.c3 | 30 +++++++++---------- src/ugui_button.c3 | 53 +++++++++++++++++++++++---------- src/ugui_cmd.c3 | 5 ++-- src/ugui_data.c3 | 42 ++++++++------------------- src/ugui_div.c3 | 63 ++++++++++++++++++++++++++++++---------- src/ugui_impl.c3 | 2 +- src/ugui_layout.c3 | 2 +- src/vtree.c3 | 11 ++++++- 12 files changed, 131 insertions(+), 81 deletions(-) create mode 100644 resources/hack-nerd.ttf diff --git a/.gitignore b/.gitignore index 3df608d..0af3826 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.a build/* **/.ccls-cache +perf.data* diff --git a/TODO b/TODO index dd8d4d6..757c3af 100644 --- a/TODO +++ b/TODO @@ -9,6 +9,7 @@ [ ] Do not redraw if there was no update (no layout and no draw) [ ] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars) +[ ] Clip element bounds to parent div, specifically text ## Commands diff --git a/project.json b/project.json index 6c11f55..8922226 100644 --- a/project.json +++ b/project.json @@ -22,7 +22,7 @@ //"RAYGUI_CUSTOM_ICONS", ], // Authors, optionally with email. - "authors": [ "John Doe " ], + "authors": [ "Alessandro Mauri " ], // Version using semantic versioning. "version": "0.1.0", // Sources compiled for all targets. diff --git a/resources/hack-nerd.ttf b/resources/hack-nerd.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f397f203107d58fc931ae62d43e61def23763295 GIT binary patch literal 2618892 zcmeEv37l5b|NptO+tSopxsPTGi{P0gph=25keF~sD$uTC?p}- zLiP}?2-zxoHUIbLKF@vJx$`_T&!oP;@9*{h&G-B6^Eu~y&SyL4o^$WHcLw7aV>bRt zW1*hiy7xGyEPQgG*6v)^pI1yx-n5|3v$E#?ntYV^GuP zIm>gdWsF;a_QL+-x{s-{^ON3?|B$hiwxcH%P5vZz!3M_GZ)dE*4Wloe5_+p~t;viX zT!%jDxXI^CYQ1)GYsS7B!HnQTaQPDc4zLLD5F2CJATrXsh`ZuMth3L*mECFykK^yA7d?=O}^mbDb2Uc|CQCfdm2j}bl2pI#!kNX z{yEziI{^RLc1F2MWt#@x+iL$vqr!=j;!%te;d)$1M zj=Z->+k*045t_1vjZ3i+vBFEKDfmfS#`vF1w(f7pga9^ZU{}U%WURP2;)5`W+Y@5k z{X0H^(!%tQAQED!Y45S|Bn=t`w;2`(qMQP305X8KY44?&sBZv#iSi5}pF9J72lN%t zalrM!eqa%B2S9bV091b?a2;?p?OcT;39x*Qe9KvcAyYXbTaK=D%B7=Y~e1I7by0mwrN{Nd8U4+hZAZ3TTF zAm85x;48_n*U;7(lw@?!GXTneR4>;Nd``434}1^cbCj0>6Toie7izNP{g zPs)=!_sXNQAR7i~UeY`zJv3LJ1L%5Ll7M0^7ypJfnxhme*$z^g3p9SzMq^C6gR~dw zXpZ6-D2>x~K#+QNlcnSd|yaB!$>gn1%fP8NPu>Ksy zxD;cZEj}H54(O?ocGvgi;Az|_4w_TsJLSDT@J5uUwle|pjoQ31SqXkBK)z-Imjbk& zP#$qj1N0F9ko?gT7L;^g&7*rvzwg{i)Kx*<3{XE0dG09c4@H4; zF^wt7k?mpNGox4r`W8TaIsz&{wlVMAGS(fg8Bo)QZQu!U>X-~&R{|p=I^Ao1Qk17Q z0&KBHz^?%1nE0kZZGhJG2>{K#Y=C5GK9~T>(6~qd)%O8vlmWSnHtLrbiNTE>vBb(W zul<0WyEhep61;vs>bCpJUT(blQk3SX-v;r|0bPIrQ8Wi7+djKgPQjRyeov=c#(LtG zDVNlT;PD>y>V4GHQ-a=uzJB=RJ>4bby>?n3XwFgDn|nb@ZL|&&JlQh*b9Iqk>PM93 z%O$`DV09FIFu(tbz@01CqCN<;yZ2g)KyLx^qZkLe9Qn)vaILxfZ2Kss@n{H4kHCFS zTngTgYf#4Z;?CFSLDxgiK2YMx27Ghx8$El+LeGrq3ihRX(oYDs`ODqQXs30RWQqC_ z&!)dU5g$*NC+qjAEdDz9LI}!?Kpg>VVTBRYtoG7mUfV<1!fM+9sb$^@?; z#YDUpn@7ENk9zeUCEss@jp#b;QPP#dp=}!4s`-F9pYj{Zpe5=dlY;e=>Qb;~Gb=(} z9&2Taf;yt4lh#Urdo@Cf_ai1Ch3veJx(*Q}DpS4JM)F>Nk5;subok-v`ERAc{E(mA z)M!pXiTC^N=Bfhq{=SKL(&@Fi{39rPh>m5-_=ry=NBO0)H(q|~&4**9DY!<0ebZR@1skJ9}c?MGBkJlPacm-gHtKntJ|K+li)fM<)w z<_rM)wtGK7{pcRMeiU@?1bMm#cXLi_Jo>ojNDV+|MZjJ}Jy5*(A8^Qri}`4y7|9md zCXl~(`pSrlJ2?XPUN@RYmyfo^9T!@YUyA7Q_5hEv&rnb8<$${ZGA{$j4ObnM#tJ%% z*Mj=`mMAww`zwL=IOXC$BQnK5g4TupbrG6!;XnP0_m$M++4SmTcFc$z?Nu=un#b_b zozGE9&+;xpKDlop^AiyDhwlH2zy7D))qyc8{v9YTS?{(Tiq=nzwrwjdt=nB%Pw{uC3zw|-;&XkWx#Sy{QvA;N?Ztn?{vT*Nzhrwct#MI( z9CGH$Q2Dig>ghSmJ=0!-y1l^ANL%p{pA7mG9|b5*jJLaPMdOX-o%9;fJ~JT`C)b`Y zK}z$X_^W^HQEutpB<3&a_MvzO+9+nsk>Vt;1%H1=xr*xZ{NyGlw&%*y`rz~7xbZbk zklw1tsJDITe%!;DBb5Of-=@Cye&Fjw%A5sCeft70M(ScY>W#iFr&ldvnBI@jVgh`r~ge$G$f=Vm>1Wv6#x~>z7OA(%vfp^i3t8K4mR{>ox`Z688mg zJ3#wm1@!-?WQ(y@max?sWkS0s{!4t0tx3&FUemO0(|j%d6_k7(6s11D-S=-FK*rlo zqHT13P$G8J$MPYUZcFj)Wz^5zqhQzDGlIJ4-H2z?op(_`y)`hnr*KF;>kmfS-2JDm&(6(J9`^up^Kxd#Y`QcqBC44_Q+8=vU@tP=4`#jyp{2eXx z@omIn!*$89PP=-Sfu0$VUl*}Wd2#1mynVNIppVagckDFu#W?zX_g|AG>S?@j4@Pxv z+<|@!_KWF0&S%5Va~K1EIUeth5ykUyY1z1WUSCqq9}CG-Ian9ZZZdW5d*%eOx&6mQ zeDUA=y6*rwM(g8UYl-ybMf-UB1?8NsnbRWmet*3+&tFG;?EaqajeK^Bzeij2p4Xf2 zZl35m@yW&bf`97Wxt2iA-Q&n^a9&W|o_}=2^e%`~N3zGTHn=(`#L<@;sY|%`1>p1BMthI?^^89tzy5PD+dOTl!ewDCwMx1;`uZ7t9>FevB zfB4Hu@}A9jaTNcMAUDum!UuZpNYb7wcX@pI5;}eQy!qj`QKIhJh(333@vRs1KHfif z{rx@O-D_}e;ht4dT;8?qyB{gFXD*HJzo*ls=wF`g67s%y-Foa>?sMHWumz-`otqwk z%hNL-wLRkVr4T&DQU$2)tEafI=9OiS!+hk1Lq>=rM>>Q4kxrb!BwP=^i_=DXIO(Yc z{1inTy|H)8@!F)Qj(BZJ)Wyb^WQ{U(A!drri_g1GVtGJ(F+O0oco*8dGcB}nUniC& z9kF>oy0l!ZEoh?*`>tOJ#?15CyHD}r^Xg+XJK~p@FOSk3jL8IiDzBbmNocPEx;$Tk z_JV5|cQtYk1m(&tBX&>5T~j`T-+)&aqm<)lp1JFz?L~3ql8KG3<#=*FU#Q)^r=$D2 zD7-%I-kDQcM@92pQF+h4pC+2Kei@JV?)Ay8-?m?ee_Z|bo{r-}ePc=XJd~xIn_4>G z!Ry0|kM#KCC`)gNwv$TRDA68o&LmMs>zTKQ9?L#e{Ee@F@IEYo{b2kt`+@#${X)b& z!WVma&x%RfERnl5KK=3f2Xl{daP5gbW5nx^x_RL zEzhR=ocsGfc}gVj%^}w|&ZhnIJ{g~rkts8-IM0@GYCg3rnR z_8?#UbIE*q=idIklKPMjo*&+Ll+s0GL+_1z`3UOw+B{ue*`x0KJiVk2uPq*pH+S4M zBvJkY_3m|u{Wz{}-!)(2x{1aYuP&aRWa|>?bMNxOSnBHP}z?!V6b zK6$(wt1mYk?h|6?aAmCD{;~9UFCS&jIiD?(bA9#K`R63vM<&Q!qPgMe^x~@sO{CNH zF?wztPoEdJ*XGS%uN&8JfGiRL}dK@*Ne0vpNdPD-vS?8ApLWrKbM%M=^x+_m}^@Pq{M9&3HP z9C`CU-WYpro;`1kpt8GOUU{3 z2kV2r`P;oUkY%yp%WpzivgO;G;>o#tY&p+&u|0?GbLbsV8TXd1jZpmYi1$t^raMvm zST}=y2k#4l?G-K4xR&Y@J?nM9mrZaFSrI>Iyu7)daJ@;eZj|yZNx4{Dl*1tWI`BEeRjVK2<)ZBbe0y-<=@}jZwS5n7T-1R zU(15mp!dy(H>W-QK{>C!BGg@rasNksyxbRmTQUYW&Zw{PY?ktCd&&M4@h2!t-|cyR zxjt58O$>}fPW(*LM3BMO>nK!Hv+Cs@gI5m zU3IFDwRtk2^i6b1<9}q!@~5n}V0@&Pi+-OG?8j{^ZO{GPc=QR%mMRByax0=ant?b% z#}ykN4-c8tMuDI*)IFdcyiZyQj;eo_sPiPAnYh zr~0TJu7Y~qZ{FkRjT@8C$JjHouP)vm#|3n!1j^;v@!I{*XI|ZZic+jD=o}<^cI8qv zFt+|&c;nz7f3I#s8U6e^D!}{SmH2-TtwfF%A4j`hECC-m`o<@jF)mMr+T$TqMhppj z^5?*tk1pLG(dE4}E%h!T5Sw4mvGQcYvlI6kO2+1SrE?HFABmTPc)5-zmyGQS$hhY! zfpeau=EjNejdI|v>G5LlZ2GCk`{lhk8LbO`e{fvR1>=pE--ohztT!CuBzSv5(sR|b z8;CL1-)k?+ezI+pTcE`C=#|S_SCiHM-zt|MxOVZoEV2A1>2rnmLYfP7Zxd%em-U{p zqJG7bch@`mt}Bl1nEZm|a^SvuIG%Gb$$ZEBOeDWAIsX&tIi4~3?=7Nwy?d5~_7lZb zf%@3{nhNx(h(E`qzpOcz$WQ81PQPP%2X;KiIOse5-c1~zz5AM^_S~S(6IqVqYpJ=g zBFSqik*-Ab<;8U($EiHqUi(hOPVma)(|_rEqL`2HwZvYs4?Z{W#o#?#dojhM-k5nb zAmjZWpx@?k=aU?N|Mvjy@96*Fi#6c)vH0lS7X{yw9*eGceO&#%SiNVGc(SoNl2U-u*K0=~ z`n|!pF_DZVh`X;mX`fY$u`Z^8tt7tC#>u9iV z+}{V;7mbPRQZ5o98NcG?Et!6aWa8_IwCX@B6e;@?_7;MW9b9*_MzkPo})~@@xg; z50<0)%Y0{*zz6@ko!EODPd`zQPtf1p2ZAyb&)^E?#lHsyY$bg65RfY|4rS;oA?MdY zag;X}e))gH`|KpT*Nben)bB5QdWd?wzy06xZ~l`z_rFEk z;Gc2{pJ6-r`#bKrr3rc8--B>tf^1oFdG{B=wYb#ZhWj^b%8AHTf?T`t+>1OAyypjx zCg@Wlw?P?-JDF?g-{i+{vm@-G9QQez#+PI}M5({u|MCC&K9%N9>{%gBKHa|&mS8T^ zeP)@!{P4HaGq}H;h!4t@IalNO{68UI{89Y*>D}kLu_h6}-*@jm$X|CN`Tvpozyx!v zRGf+Ayn2rw!+Z$tE2V5F(CPlYF#J8vxbpi@55%smX($tX^|7`D`s3<7j9gcUYYv&`9ykfkLUUq9Akgk&jTqI3b~IR3U_ z((4Az+2b&8V`q&%T_x^!;>AXF(R%M3q0H-rz6Xk)rIjfcFK6Dm=TUDT@@NO>#+-D& zJ0hQGepUuZ*L1)?o*o~ioQ8J9&5^FP5x#_f@nVnXBkIb0R`=z$g#HA&yxh97_--=U z=aZy#C$d>aeZqT`Xk5Yb0@@dn-Wx7J-s9P(ag0|Vtc#ahZ*Bx-y>h%h)aLzNiAG7< z_VmT0ga6Ty@Ltk4zvvyP`ySM*^JqYK@b`6+%27Z^h58gm;)wZ1ekJhD(_8A>zC`)< z`91bd7Ic^D=M1AAcdw$f6n;Y(b&wIx|Wv~8!ivF83(NaDK z*PB4T-M>4Jzf0$xqm=3&kUwGV$&A^5%9mv9(ERb>mk-Wuk_nDMujHf$VN6%{n_q>7L70|f@_OKGvt4~CuXBBQA;ypR9-YbK~Vk^TZxBsDttml{i zcZ-70n91l&N(Om|T{mU;mz1uu`jZ`M-w>rVv<0%zh<^({DFf zo}QKcD7T;YE#7hTA)d{oa+4_kiRw?V&++0&x=xME(S+w$$J=+`_p(X*_%D1&`rhCe zY?L_v^pAac*Y7dd`cLHQxY+Raz7r9nKgY*Yf3o{5**^h3(0}*ggx&kZ$&>di;Oz-C z?q#lfh}k_4tKhs&^xv~6cb+9x&P1R{(map37@Hab11g%Mc-Wp%bxGS`b1?<{(n2A_u6saZU1jONAr#K|MoHY zAN8O1JZ}#!YYr5av9^@e?^wo({PBDOJ?{5Q|AYHH{jN>$_if609xL$<{aA8Yfj+_W z&7|g~H%=9>layYJQJK$kC(173pug>q@b5bW;!Nrc*w@GX{+s5XR~MX%$&|gm{|jmC zeNiyZII*~A$Vr|z$MP9R&;O=2y8lSVU+?>xAWf!kGBRa7BgSn@Vmy7MS z7F5)qg!0G3NBVx6K<6$5FXvH8-}WT+{pJa{M(Fw9L&6-K0K3QA7Ts%P* zFJ^BH%afa!gf7yN;C$=9VlDMzO(rkVd>pUe6^QLS<>mQ5)b5Rk`=0i2l4Dyz-{^1L zc<&fb#93y!bGw56CZmtrid?-E0oNJveGb$CE(h8~z-oZvT?f|`|1CTG{sq?&$OrNO z{O*j~Mtyq&)Ycwo#v(uIAgMqukj7Y@t&F3F`qNNLTrQ~W)~D53it>IogYj%4IE_u` zuI6szmU3b48?HE|M#^g`U!Cm$o zRWj;l49FOn@kPeBnNDWq%ur@lX5-9`ndfHSn7K4_UFPP@_cFiD{3%PxGP5$Wp2#{{ zueg4*`eW-)sQ*Cr(CkaH7i2$~y&-#Z_Rj3@vwv?myWygS-#7fb5o^@6(Rn#qPW7Dp zCe@nMYtm$=yz`-*pX`2Q&z`T>?%T7!_D}B|yyn*yM~jP#5i3O-VoMP167EUvYwl=D zwUo^%+ftbu?ab7hQy)q_l*ZCVrCpRZKkdP^N7J55+nDxR+PmpydiC^^(+kpz(&r%B zt+i*=USIn_?LR^+WQLlC+J(ABqn#3(8(I=ti)b@5`ezKvn4hsT<9kF~713sAw#+;= zb5iC_nX5Bj&U`!bGenz)XhT`|Wc^)_`Jx?U}d-m7azc!rJa3P}o%Z+wI zPI^uiH`+RgmhD`!GrXJaVPF0A4g0R}2lg{N#N24P;$MsR6u(>?W~_K&@u=c{#VwD{ zJ$eOWM+Y8l$JmjNk9>rGmmgVrVD|g?s`|jPHP$%r>cYVC;!(AWj zdVkk@yWZWkW!L6iuk6~m>qW+PJ-q9IU3cwT0PgZ#6L$WxbJ>oiJErXDvZKQe{>y=1 zp893hm!U7~(BOP=q{t_)z*jcJ3Sn+1C0_9C{>b$De9>boAaq*ZH45O0p=RUNoVC&h- zoS8BztwH*O(s*^RJ>#TnsR!4U>*mt#Ts~KT@-XgfZUi?HTp?G)jRGCbEycgfQgTvg zHDD=?Q<|jKOs$0lqIPPX)KF?h>JX|&d1%_0w6SU9($1k$+W54YM8RF3c0<~%wAtxy z{?d1(?@ZqnX-VJB3&imvFYz+3@EWi425<5fZ}SdciLcC8;j8l1_>=hRd=0)PUyHBJ z*Wp8a2A|1i@pbune0@HfZ@@R?8}T`OF5j4M!Z+oc@h9`m`4)UjXl@0x=G*XX`F8v% ze0#nF${m4Dd}qE3e=48HcjdeB-T5ATPreu5oA1N-<@@oc@u%}=@csD#{6KyXKbRlF z59QC~^Z5dP7=IQ&oIjf%!H?t%`67N4KbjxIkLAbl=kVkCbNLDUdHh6v5`R8_0Y90) zkiUq(n4iL5!e7c?#!uxh=da+e6QOk>AL_ z#J|kH!f)bVO@mu(}`FHqt`K|nW{QLX|{D=HU{Kxz!{5Jkm{xkk_ zemnmK|0TbJ-^uUdck_Grz5G}F*Zep9KK@(&JN|oqKmP;&BmWcsGye;JfIr9|;(z6T z;Q!?R;{WCk^GEoje6hd;PDl|_g)|{u-~~Yt1xb(vMNkDz&;>&<1xv66N2nxJ z7ODtUg=)e{LUo~rP*bQS)E4RpAt6J^6taZ6LOr3rkS#P28VZes93fX|EHn|C3eAL* zh2}yFp{3ADXf3o6+6wK2Q-txI8T@;OcKr)E)XUQ7YY{%7YkE_ON2{>%Y>=I<-!%hmBKXPDq*^CwJ<}tMz~hE zPM9fNFWex^5@ri?gt@|v!aU(7;b!3$VZLyyaGP+uut2y&xKmgtEE4V#?iTJ577O_*(cz*e84|d?$P_>=%9z zeiVKZeinWa4hRQ@L&C4ZZ^G}wAHtu)U&7zQVd02yR45ji$cZUps+cCGi@Yd^q9}>7 zsEDeliMnWrrf7+_=!liX%3>9>zd&JBgjeF5;O>h&WU{Q_L3 `uD;&Ab7afCQh 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�bBt9rK25@7{0s0EwAb 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�aT}O!C*pJM-(%vI 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Ðk%?Vi&FvbB7Qahn9F^`jhA);_NLg6v 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≪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ྪSlXed?{%1KQ^zdM$AW2*wF61{%@DkOe?C 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�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

RO{@On|?Ap^soB7sU0brP%~#wyvMw^rr=#ZU)? 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 Fbn)potF7O;$hm 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 z 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 24) & 0xff), - .g = (char)((u >> 16) & 0xff), - .b = (char)((u >> 8) & 0xff), - .a = (char)((u >> 0) & 0xff) + .r = (char)(($u >> 24) & 0xff), + .g = (char)(($u >> 16) & 0xff), + .b = (char)(($u >> 8) & 0xff), + .a = (char)(($u >> 0) & 0xff) }; } @@ -179,12 +159,12 @@ macro point_in_rect(Point p, Rect r) } // return true if rect a contains b -macro rect_contains(Rect a, Rect b) +macro bool Rect.contains(Rect a, Rect b) { return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); } -macro rect_intersection(Rect a, Rect b) +macro Rect Rect.intersection(Rect a, Rect b) { return Rect{ .x = (short)max(a.x, b.x), @@ -195,7 +175,9 @@ macro rect_intersection(Rect a, Rect b) } // rect intersection not null -macro rect_collision(Rect a, Rect b) +macro bool Rect.collides(Rect a, Rect b) { return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); } + +macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 3706c34..970b722 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -2,7 +2,39 @@ module ugui; import std::io; -fn void! Ctx.div_begin(&ctx, String label, Rect size) +enum DivLayout { + LAYOUT_ROW, + LAYOUT_COLUMN, + LAYOUT_FLOATING, +} + +// div element +struct ElemDiv { + DivLayout layout; + struct scroll { + bool can_x; + bool can_y; + bool on_x; + bool on_y; + float value_x; + float value_y; + } + Rect children_bounds; + Point origin_r, origin_c; + Color bgcolor; +} + +const Elem DIV_DEFAULTS = { + .type = ETYPE_DIV, + .div = { + .scroll.can_x = false, + .scroll.can_y = false, + .scroll.value_x = 0, + .scroll.value_y = 0, + }, +}; + +fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) { Id id = label.hash(); @@ -11,21 +43,22 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; - // 1. Fill the element fields - c_elem.type = ETYPE_DIV; + bool needs_layout = c_elem.flags.is_new || parent.flags.updated; + + if (c_elem.flags.is_new) { + *c_elem = DIV_DEFAULTS; + } else if (c_elem.type != ETYPE_DIV) { + return UgError.WRONG_ELEMENT_TYPE?; + } + c_elem.div.scroll.can_x = scroll_x; + c_elem.div.scroll.can_y = scroll_y; // do layout and update flags only if the element was updated - if (c_elem.flags.is_new || parent.flags.updated) { + if (needs_layout) { // 2. layout the element c_elem.bounds = ctx.position_element(parent, size); - if (c_elem.flags.is_new) { - c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.color_bg = ctx.style.bgcolor; - c_elem.div.scroll.can_x = false; - c_elem.div.scroll.can_y = false; - c_elem.div.scroll.value_x = 0; - c_elem.div.scroll.value_y = 0; - } + c_elem.div.children_bounds = c_elem.bounds; + c_elem.div.bgcolor = ctx.style.bgcolor; // 3. Mark the element as updated c_elem.flags.updated = true; @@ -39,14 +72,14 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size) } // Add the background to the draw stack - bool do_border = parent.div.layout == LAYOUT_FLOATING; - ctx.push_rect(c_elem.bounds, c_elem.div.color_bg, do_border: do_border)!; + bool do_border = parent.div.layout == LAYOUT_FLOATING; + ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!; // TODO: check active // TODO: check resizeable // check and draw scroll bars - if (!rect_contains(c_elem.bounds, c_elem.div.children_bounds)) { + if (!c_elem.bounds.contains(c_elem.div.children_bounds)) { Point cbc = { .x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w, .y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h, diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index e2da44a..9b7d824 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -124,7 +124,7 @@ fn void! Ctx.frame_end(&ctx) root.div.layout = LAYOUT_ROW; // 1. clear the tree - ctx.tree.prune(0)!; + ctx.tree.nuke(); // 2. clear input fields bool f = ctx.input.events.force_update; diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index dcf63f4..423f127 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -163,7 +163,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) } // 6. check if the placement is inside the view - if (rect_collision(placement, view)) { + if (placement.collides(view)) { return Rect{ .x = placement.x - off.x, .y = placement.y - off.y, diff --git a/src/vtree.c3 b/src/vtree.c3 index 42b5d19..8cdcff7 100644 --- a/src/vtree.c3 +++ b/src/vtree.c3 @@ -27,7 +27,7 @@ macro @zero() $if $assignable(0, ElemType): return 0; $else - return ElemType{0}; + return ElemType{}; $endif } @@ -186,6 +186,15 @@ fn usz! VTree.prune(&tree, isz ref) return count; } +fn usz VTree.nuke(&tree) +{ + tree.vector[0..] = @zero(); + tree.refs[0..] = -1; + usz x = tree.elements; + tree.elements = 0; + return x; +} + // find the size of the subtree starting from ref fn usz! VTree.subtree_size(&tree, isz ref) { From 8d4b353e88f66e6703f4872163f926eb6dac4aa7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 18 Dec 2024 14:58:40 +0100 Subject: [PATCH 058/250] a lot of work on sliders --- src/main.c3 | 19 ++++-- src/ugui_button.c3 | 5 +- src/ugui_data.c3 | 9 --- src/ugui_div.c3 | 160 ++++++++++++++++++++++++++------------------- src/ugui_impl.c3 | 32 +++++---- src/ugui_input.c3 | 9 +-- src/ugui_layout.c3 | 4 +- src/ugui_slider.c3 | 111 +++++++++++++++++++------------ src/ugui_text.c3 | 17 ++--- 9 files changed, 200 insertions(+), 166 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index ee322f4..f1f0d25 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -138,21 +138,21 @@ fn int main(String[] args) io::printn("release button2"); } } - if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ui.get_elem_by_label("slider")!!; - io::printfn("slider: %f", e.slider.value); + static float slider1; + if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider1)!!.update) { + io::printfn("slider: %f", slider1); } ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; |}; ui.div_end()!!; - ui.div_begin("second", ugui::DIV_FILL, scroll_y: true)!!; + ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; {| ui.layout_set_column()!!; - if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) { - ugui::Elem* e = ui.get_elem_by_label("slider_other")!!; - io::printfn("other slider: %f", e.slider.value); + static float slider2 = 0.5; + if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100}, &slider2)!!.update) { + io::printfn("other slider: %f", slider2); } ui.button("button10", ugui::Rect{0,0,50,50})!!; ui.button("button11", ugui::Rect{0,0,50,50})!!; @@ -164,6 +164,11 @@ fn int main(String[] args) ui.button("button16", ugui::Rect{0,0,50,50})!!; ui.button("button17", ugui::Rect{0,0,50,50})!!; } + ui.layout_next_column()!!; + ui.layout_set_row()!!; + static float f1, f2; + ui.slider_hor("hs1", ugui::Rect{0,0,100,30}, &f1)!!; + ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!; |}; ui.div_end()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 362826a..7db15c1 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -33,10 +33,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) return UgError.WRONG_ELEMENT_TYPE?; } - // if the element is new or the parent was updated then redo layout - if (needs_layout) { - c_elem.bounds = ctx.position_element(parent, size, true); - } + c_elem.bounds = ctx.position_element(parent, size, true); // if the bounds are null the element is outside the div view, // no interaction should occur so just return diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 index c65269a..a10b1f3 100644 --- a/src/ugui_data.c3 +++ b/src/ugui_data.c3 @@ -47,12 +47,6 @@ bitstruct ElemEvents : uint { bool update : 7; } -// slider element -struct ElemSlider { - float value; - Rect handle; -} - struct ElemText { char* str; } @@ -100,9 +94,6 @@ macro Color uint_to_rgba(uint $u) { const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; -macro abs(a) { return a < 0 ? -a : a; } -macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); } - const uint STACK_STEP = 10; const uint MAX_ELEMS = 128; const uint MAX_CMDS = 256; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 970b722..fd43738 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -1,6 +1,7 @@ module ugui; import std::io; +import std::math; enum DivLayout { LAYOUT_ROW, @@ -19,7 +20,8 @@ struct ElemDiv { float value_x; float value_y; } - Rect children_bounds; + Rect children_bounds; // current frame children bounds + Rect pcb; // previous frame children bounds Point origin_r, origin_c; Color bgcolor; } @@ -38,12 +40,12 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo { Id id = label.hash(); - Elem *parent = ctx.get_parent()!; + Elem* parent = ctx.get_parent()!; Elem* c_elem = ctx.get_elem(id)!; isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; - bool needs_layout = c_elem.flags.is_new || parent.flags.updated; + bool is_new = c_elem.flags.is_new; if (c_elem.flags.is_new) { *c_elem = DIV_DEFAULTS; @@ -53,86 +55,104 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo c_elem.div.scroll.can_x = scroll_x; c_elem.div.scroll.can_y = scroll_y; - // do layout and update flags only if the element was updated - if (needs_layout) { - // 2. layout the element - c_elem.bounds = ctx.position_element(parent, size); - c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.bgcolor = ctx.style.bgcolor; + // 2. layout the element + c_elem.bounds = ctx.position_element(parent, size); + c_elem.div.children_bounds = c_elem.bounds; + c_elem.div.bgcolor = ctx.style.bgcolor; - // 3. Mark the element as updated - c_elem.flags.updated = true; - // 4. Fill the div fields - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - c_elem.div.origin_r = c_elem.div.origin_c; - c_elem.div.layout = parent.div.layout; - } + // 4. Fill the div fields + c_elem.div.origin_c = Point{ + .x = c_elem.bounds.x, + .y = c_elem.bounds.y, + }; + c_elem.div.origin_r = c_elem.div.origin_c; + c_elem.div.layout = parent.div.layout; // Add the background to the draw stack bool do_border = parent.div.layout == LAYOUT_FLOATING; ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!; + c_elem.events = ctx.get_elem_events(c_elem); + // TODO: check active // TODO: check resizeable - // check and draw scroll bars - if (!c_elem.bounds.contains(c_elem.div.children_bounds)) { - Point cbc = { - .x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w, - .y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h, + // FIXME: we cannot use slider elements as scrollbars because of id management + // scrollbars + Rect cb = c_elem.div.pcb; + // children bounds bottom-right corner + Point cbc = { + .x = cb.x + cb.w, + .y = cb.y + cb.h, + }; + // div bounds bottom-right corner + Point bc = { + .x = c_elem.bounds.x + c_elem.bounds.w, + .y = c_elem.bounds.y + c_elem.bounds.h, + }; + + + // set the scrollbar flag, is used in layout + // vertical overflow + c_elem.div.scroll.on_y = cbc.y > bc.y && c_elem.div.scroll.can_y; + // horizontal overflow + c_elem.div.scroll.on_x = cbc.x > bc.x && c_elem.div.scroll.can_x; + + if (c_elem.div.scroll.on_y) { + Rect vslider = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = c_elem.bounds.y, + .w = 10, + .h = c_elem.bounds.h, }; - Point bc = { - .x = c_elem.bounds.x + c_elem.bounds.w, - .y = c_elem.bounds.y + c_elem.bounds.h, + + short hh = (short)(max((float)bc.y / cbc.y, (float)0.15) * c_elem.bounds.h); + Rect vhandle = { + .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y), + .w = 10, + .h = hh, }; - // vertical overflow, check and draw scroll bar - if (cbc.y > bc.y && c_elem.div.scroll.can_y) { - // set the scrollbar flag, is used in layout - c_elem.div.scroll.on_y = true; - Rect vslider = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, - .y = c_elem.bounds.y, - .w = 10, - .h = c_elem.bounds.h, - }; - - float vh = max((float)bc.y / cbc.y, (float)0.15); - Rect vhandle = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, - .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1-vh) * c_elem.div.scroll.value_y)), - .w = 10, - .h = (short)(c_elem.bounds.h * vh), - }; - - c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover && c_elem.events.mouse_hold && point_in_rect(ctx.input.mouse.pos, vhandle)) { - short y = (short)clamp(ctx.input.mouse.pos.y - vhandle.h/2, vslider.y, vslider.y + vslider.h - vhandle.h); - vhandle.y = y; - float v = (float)(vhandle.y-vslider.y) / (float)(vslider.h-vhandle.h); - c_elem.div.scroll.value_y = v; - c_elem.flags.updated = true; - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, - }; - c_elem.div.origin_r = c_elem.div.origin_c; - } - - ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; - ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; - } else { - c_elem.div.scroll.on_y = false; + Point m = ctx.input.mouse.pos; + if (parent.flags.has_focus && c_elem.events.mouse_hover && + c_elem.events.mouse_hold && point_in_rect(m, vhandle)) { + vhandle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y); + c_elem.div.scroll.value_y = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); + c_elem.flags.updated = true; } + + ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; + ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; } - // if the bounds are outside of the view then allocate space for scrollbars - DivLayout old_layout = c_elem.div.layout; - c_elem.div.layout = LAYOUT_FLOATING; - c_elem.div.layout = old_layout; + if (c_elem.div.scroll.on_x) { + Rect hslider = { + .x = c_elem.bounds.x, + .y = c_elem.bounds.y + c_elem.bounds.h - 10, + .w = c_elem.bounds.w, + .h = 10, + }; + + short hw = (short)(max((float)bc.x / cbc.x, (float)0.15) * c_elem.bounds.w); + Rect hhandle = { + .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x), + .y = c_elem.bounds.y + c_elem.bounds.h - 10, + .w = hw, + .h = 10, + }; + + Point m = ctx.input.mouse.pos; + if (parent.flags.has_focus && c_elem.events.mouse_hover && + c_elem.events.mouse_hold && point_in_rect(m, hhandle)) { + hhandle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x); + c_elem.div.scroll.value_x = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); + c_elem.flags.updated = true; + } + + ctx.push_rect(hslider, uint_to_rgba(0x999999ff))!; + ctx.push_rect(hhandle, uint_to_rgba(0x9999ffff))!; + } if (parent.flags.has_focus) { if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { @@ -144,6 +164,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo fn void! Ctx.div_end(&ctx) { + // swap the children bounds + Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; + c_elem.div.pcb = c_elem.div.children_bounds; + // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div)!; } diff --git a/src/ugui_impl.c3 b/src/ugui_impl.c3 index 9b7d824..5503e34 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_impl.c3 @@ -85,26 +85,24 @@ fn void! Ctx.frame_begin(&ctx) // other stuff c_elem.flags.has_focus = ctx.has_focus; - if (c_elem.flags.is_new || c_elem.flags.updated) { - Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .bounds = { + Elem def_root = { + .id = ROOT_ID, + .type = ETYPE_DIV, + .bounds = { + .w = ctx.width, + .h = ctx.height, + }, + .div = { + .layout = LAYOUT_ROW, + .children_bounds = { .w = ctx.width, .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - .children_bounds = { - .w = ctx.width, - .h = ctx.height, - } - }, - .flags = c_elem.flags, - }; + } + }, + .flags = c_elem.flags, + }; - *c_elem = def_root; - } + *c_elem = def_root; // 3. Push the root element into the element tree ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 1b49ae5..0d55615 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -1,6 +1,7 @@ module ugui; import std::io; +import std::math; // TODO: this could be a bitstruct bitstruct InputEvents : uint { @@ -84,8 +85,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) // Mouse was moved, report absolute position fn void Ctx.input_mouse_abs(&ctx, short x, short y) { - ctx.input.mouse.pos.x = clamp(x, 0u16, ctx.width); - ctx.input.mouse.pos.y = clamp(y, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width); + ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height); short dx, dy; dx = x - ctx.input.mouse.pos.x; @@ -107,8 +108,8 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) mx = ctx.input.mouse.pos.x + dx; my = ctx.input.mouse.pos.y + dy; - ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width); - ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width); + ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height); ctx.input.events.mouse_move = true; } diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 423f127..59e6c9a 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -140,10 +140,10 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) Point off; Rect* cb = &div.children_bounds; if (div.scroll.can_x && div.scroll.on_x) { - off.x = (short)(cb.w * div.scroll.value_x); + off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll.value_x); } if (div.scroll.can_y && div.scroll.on_y) { - off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y); + off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll.value_y); } Rect view = { .x = parent.bounds.x + off.x, diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 59ef66b..7d15450 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -1,15 +1,28 @@ module ugui; import std::io; +import std::math; + +// slider element +struct ElemSlider { + Rect handle; +} + +const Elem SLIDER_DEFAULT = { + .type = ETYPE_SLIDER, +}; /* handle * +----+-----+---------------------+ * | |#####| | * +----+-----+---------------------+ */ -fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) +<* +@require value != null +*> +fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) { - Id id = label.hash(); + Id id = label.hash(); Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; @@ -17,29 +30,33 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - c_elem.type = ETYPE_SLIDER; - - // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); - c_elem.slider.handle = Rect{ - .x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w*(1.0-0.25) * c_elem.slider.value)), - .y = c_elem.bounds.y, - .w = (short)(c_elem.bounds.w * 0.25), - .h = c_elem.bounds.h, - }; + if (c_elem.flags.is_new) { + *c_elem = SLIDER_DEFAULT; + } else if (c_elem.type != SLIDER_DEFAULT.type) { + return UgError.WRONG_ELEMENT_TYPE?; } + // 2. Layout + c_elem.bounds = ctx.position_element(parent, size, true); + + // handle width + short hw = (short)(c_elem.bounds.w * 0.25); + Rect handle = { + .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value), + .y = c_elem.bounds.y, + .w = hw, + .h = c_elem.bounds.h, + }; + c_elem.slider.handle = handle; + + Point m = ctx.input.mouse.pos; c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w); - float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w); - c_elem.slider.handle.x = x; - c_elem.slider.value = v; - c_elem.events.update = true; - } + + if (parent.flags.has_focus && c_elem.events.mouse_hover && + point_in_rect(m, handle) && c_elem.events.mouse_hold) { + *value = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); + c_elem.slider.handle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value); + c_elem.events.update = true; } // Draw the slider background and handle @@ -63,7 +80,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size) * | | * +-+ */ -fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) +fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) { Id id = label.hash(); @@ -73,29 +90,34 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - c_elem.type = ETYPE_SLIDER; - - // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); - c_elem.slider.handle = Rect{ - .x = c_elem.bounds.x, - .y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1.0-0.25) * c_elem.slider.value)), - .w = c_elem.bounds.w, - .h = (short)(c_elem.bounds.h * 0.25), - }; + if (c_elem.flags.is_new) { + *c_elem = SLIDER_DEFAULT; + } else if (c_elem.type != SLIDER_DEFAULT.type) { + return UgError.WRONG_ELEMENT_TYPE?; } + // 2. Layout + c_elem.bounds = ctx.position_element(parent, size, true); + + // handle height + short hh = (short)(c_elem.bounds.h * 0.25); + Rect handle = { + .x = c_elem.bounds.x, + .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value), + .w = c_elem.bounds.w, + .h = hh, + }; + c_elem.slider.handle = handle; + + Point m = ctx.input.mouse.pos; c_elem.events = ctx.get_elem_events(c_elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) { - short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h); - float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h); - c_elem.slider.handle.y = y; - c_elem.slider.value = v; - c_elem.events.update = true; - } + + if (parent.flags.has_focus && c_elem.events.mouse_hover && + point_in_rect(m, handle) && + c_elem.events.mouse_hold) { + *value = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); + c_elem.slider.handle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value); + c_elem.events.update = true; } // Draw the slider background and handle @@ -106,3 +128,6 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size) return c_elem.events; } + +macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value); +macro float calc_value(ushort off, ushort mouse, ushort dim, ushort slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f); diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 88fb581..300825a 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -15,19 +15,12 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // this resets the flags c_elem.type = ETYPE_TEXT; - short baseline = (short)ctx.font.ascender; - short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; - short line_gap = (short)ctx.font.linegap; // if the element is new or the parent was updated then redo layout - if (c_elem.flags.is_new || parent.flags.updated) { - Rect text_size = ctx.get_text_bounds(text)!; - - // 2. Layout - c_elem.bounds = ctx.position_element(parent, text_size, true); - - // 3. Fill the button specific fields - c_elem.text.str = text; - } + Rect text_size = ctx.get_text_bounds(text)!; + // 2. Layout + c_elem.bounds = ctx.position_element(parent, text_size, true); + // 3. Fill the button specific fields + c_elem.text.str = text; ctx.push_string(c_elem.bounds, text)!; } From 740ea0c6be4cbeda9ca3518e6d825d312c022daa Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 18 Dec 2024 15:02:46 +0100 Subject: [PATCH 059/250] merged ugui_impl and ugui_data to ugui_core --- src/{ugui_impl.c3 => ugui_core.c3} | 171 ++++++++++++++++++++++++++++ src/ugui_data.c3 | 174 ----------------------------- 2 files changed, 171 insertions(+), 174 deletions(-) rename src/{ugui_impl.c3 => ugui_core.c3} (53%) delete mode 100644 src/ugui_data.c3 diff --git a/src/ugui_impl.c3 b/src/ugui_core.c3 similarity index 53% rename from src/ugui_impl.c3 rename to src/ugui_core.c3 index 5503e34..793885a 100644 --- a/src/ugui_impl.c3 +++ b/src/ugui_core.c3 @@ -1,6 +1,177 @@ module ugui; +import vtree; +import cache; +import fifo; + import std::io; +import std::core::string; + +struct Rect { + short x, y, w, h; +} + +struct Point { + short x, y; +} + +struct Color{ + char r, g, b, a; +} + +// element ids are just long ints +def Id = usz; + +enum ElemType { + ETYPE_NONE, + ETYPE_DIV, + ETYPE_BUTTON, + ETYPE_SLIDER, + ETYPE_TEXT, +} + +bitstruct ElemFlags : uint { + bool updated : 0; + bool has_focus : 1; + bool is_new : 2; +} + +bitstruct ElemEvents : uint { + bool key_press : 0; + bool key_release : 1; + bool key_hold : 2; + bool mouse_hover : 3; + bool mouse_press : 4; + bool mouse_release : 5; + bool mouse_hold : 6; + bool update : 7; +} + +struct ElemText { + char* str; +} + +// element structure +struct Elem { + Id id; + ElemFlags flags; + ElemEvents events; + Rect bounds; + ElemType type; + union { + ElemDiv div; + ElemButton button; + ElemSlider slider; + ElemText text; + } +} + + +// relationships between elements are stored in a tree, it stores just the ids +def IdTree = vtree::VTree() @private; + +// elements themselves are kept in a cache +const uint MAX_ELEMENTS = 1024; +def ElemCache = cache::Cache() @private; + +def CmdQueue = fifo::Fifo(); + +fault UgError { + INVALID_SIZE, + EVENT_UNSUPPORTED, + UNEXPECTED_ELEMENT, + WRONG_ELEMENT_TYPE, +} + +macro Color uint_to_rgba(uint $u) { + return Color{ + .r = (char)(($u >> 24) & 0xff), + .g = (char)(($u >> 16) & 0xff), + .b = (char)(($u >> 8) & 0xff), + .a = (char)(($u >> 0) & 0xff) + }; +} + +const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; + +const uint STACK_STEP = 10; +const uint MAX_ELEMS = 128; +const uint MAX_CMDS = 256; +const uint ROOT_ID = 1; + +enum Layout { + ROW, + COLUMN, + FLOATING +} + +// global style, similar to the css box model +struct Style { // css box model + Rect padding; + Rect border; + Rect margin; + Color bgcolor; // background color + Color fgcolor; // foreground color + Color brcolor; // border color + ushort radius; +} + + +struct Ctx { + Layout layout; + IdTree tree; + ElemCache cache; + CmdQueue cmd_queue; + // total size in pixels of the context + ushort width, height; + Style style; + Font font; + + bool has_focus; + struct input { + InputEvents events; + struct mouse { + Point pos, delta; + // mouse_down: bitmap of mouse buttons that are held + // mouse_updated: bitmap of mouse buttons that have been updated + // mouse_released = mouse_updated & ~mouse_down + // mouse_pressed = mouse_updated & mouse_down + MouseButtons down; + MouseButtons updated; + } + } + + isz active_div; // tree node indicating the current active div +} + +macro point_in_rect(Point p, Rect r) +{ + return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); +} + +// return true if rect a contains b +macro bool Rect.contains(Rect a, Rect b) +{ + return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); +} + +macro Rect Rect.intersection(Rect a, Rect b) +{ + return Rect{ + .x = (short)max(a.x, b.x), + .y = (short)max(a.y, b.y), + .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), + .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), + }; +} + +// rect intersection not null +macro bool Rect.collides(Rect a, Rect b) +{ + return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); +} + +macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; // return a pointer to the parent of the current active div fn Elem*! Ctx.get_parent(&ctx) diff --git a/src/ugui_data.c3 b/src/ugui_data.c3 deleted file mode 100644 index a10b1f3..0000000 --- a/src/ugui_data.c3 +++ /dev/null @@ -1,174 +0,0 @@ -module ugui; - -import std::core::string; - -import vtree; -import cache; -import fifo; - - -struct Rect { - short x, y, w, h; -} - -struct Point { - short x, y; -} - -struct Color{ - char r, g, b, a; -} - -// element ids are just long ints -def Id = usz; - -enum ElemType { - ETYPE_NONE, - ETYPE_DIV, - ETYPE_BUTTON, - ETYPE_SLIDER, - ETYPE_TEXT, -} - -bitstruct ElemFlags : uint { - bool updated : 0; - bool has_focus : 1; - bool is_new : 2; -} - -bitstruct ElemEvents : uint { - bool key_press : 0; - bool key_release : 1; - bool key_hold : 2; - bool mouse_hover : 3; - bool mouse_press : 4; - bool mouse_release : 5; - bool mouse_hold : 6; - bool update : 7; -} - -struct ElemText { - char* str; -} - -// element structure -struct Elem { - Id id; - ElemFlags flags; - ElemEvents events; - Rect bounds; - ElemType type; - union { - ElemDiv div; - ElemButton button; - ElemSlider slider; - ElemText text; - } -} - - -// relationships between elements are stored in a tree, it stores just the ids -def IdTree = vtree::VTree() @private; - -// elements themselves are kept in a cache -const uint MAX_ELEMENTS = 1024; -def ElemCache = cache::Cache() @private; - -def CmdQueue = fifo::Fifo(); - -fault UgError { - INVALID_SIZE, - EVENT_UNSUPPORTED, - UNEXPECTED_ELEMENT, - WRONG_ELEMENT_TYPE, -} - -macro Color uint_to_rgba(uint $u) { - return Color{ - .r = (char)(($u >> 24) & 0xff), - .g = (char)(($u >> 16) & 0xff), - .b = (char)(($u >> 8) & 0xff), - .a = (char)(($u >> 0) & 0xff) - }; -} - -const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; - -const uint STACK_STEP = 10; -const uint MAX_ELEMS = 128; -const uint MAX_CMDS = 256; -const uint ROOT_ID = 1; - -enum Layout { - ROW, - COLUMN, - FLOATING -} - -// global style, similar to the css box model -struct Style { // css box model - Rect padding; - Rect border; - Rect margin; - Color bgcolor; // background color - Color fgcolor; // foreground color - Color brcolor; // border color - ushort radius; -} - - -struct Ctx { - Layout layout; - IdTree tree; - ElemCache cache; - CmdQueue cmd_queue; - // total size in pixels of the context - ushort width, height; - Style style; - Font font; - - bool has_focus; - struct input { - InputEvents events; - struct mouse { - Point pos, delta; - // mouse_down: bitmap of mouse buttons that are held - // mouse_updated: bitmap of mouse buttons that have been updated - // mouse_released = mouse_updated & ~mouse_down - // mouse_pressed = mouse_updated & mouse_down - MouseButtons down; - MouseButtons updated; - } - } - - isz active_div; // tree node indicating the current active div -} - -macro point_in_rect(Point p, Rect r) -{ - return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); -} - -// return true if rect a contains b -macro bool Rect.contains(Rect a, Rect b) -{ - return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); -} - -macro Rect Rect.intersection(Rect a, Rect b) -{ - return Rect{ - .x = (short)max(a.x, b.x), - .y = (short)max(a.y, b.y), - .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), - .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), - }; -} - -// rect intersection not null -macro bool Rect.collides(Rect a, Rect b) -{ - return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); -} - -macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; From 499f6dc79b529ee302bd80a47f8ab49a0282ee97 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 18 Dec 2024 15:10:17 +0100 Subject: [PATCH 060/250] removed force update --- src/main.c3 | 6 ------ src/ugui_core.c3 | 12 +----------- src/ugui_input.c3 | 1 - 3 files changed, 1 insertion(+), 18 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index f1f0d25..c2dd11d 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -126,7 +126,6 @@ fn int main(String[] args) if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; - ui.force_update()!!; } //ui.layout_next_column()!!; if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { @@ -181,11 +180,6 @@ fn int main(String[] args) {| ui.layout_set_row()!!; ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; - - //ui.layout_next_row()!!; - //ui.text_unbounded("draw avg", string::tformat("draw avg: %s", dts.avg))!!; - - //ui.force_update()!!; |}; ui.div_end()!!; diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 793885a..edc29b8 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -176,8 +176,6 @@ macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; // return a pointer to the parent of the current active div fn Elem*! Ctx.get_parent(&ctx) { - // FIXME: if the tree held pointers to the elements then no more - // redundant cache search Id parent_id = ctx.tree.get(ctx.active_div)!; return ctx.cache.search(parent_id); } @@ -249,8 +247,7 @@ fn void! Ctx.frame_begin(&ctx) // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree - c_elem.flags.updated = ctx.input.events.resize | ctx.input.events.force_update; - ctx.input.events.force_update = false; + c_elem.flags.updated = ctx.input.events.resize; // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff @@ -282,11 +279,6 @@ fn void! Ctx.frame_begin(&ctx) // TODO: add a background color taken from a theme or config } -fn void! Ctx.force_update(&ctx) -{ - ctx.input.events.force_update = true; -} - fn void! Ctx.frame_end(&ctx) { Elem* root = ctx.get_elem_by_tree_idx(0)!; @@ -296,9 +288,7 @@ fn void! Ctx.frame_end(&ctx) ctx.tree.nuke(); // 2. clear input fields - bool f = ctx.input.events.force_update; ctx.input.events = (InputEvents)0; - ctx.input.events.force_update = f; // send atlas updates if (ctx.font.should_update) { diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 0d55615..92d31a7 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -9,7 +9,6 @@ bitstruct InputEvents : uint { bool change_focus : 1; // window focus changed bool mouse_move : 2; // mouse was moved bool mouse_btn : 3; // mouse button pressed or released - bool force_update : 4; } // Window size was changed From a4812690228c193c48e458c5147e224831530799 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 18 Dec 2024 20:04:23 +0100 Subject: [PATCH 061/250] work on div sliders, major changes * Ids are now keyed based on the parent's id, this means that an element can have the same label when placed in different divs * Divs now enable the scissor test, this way the elements cannot draw outside of the parent div bounds * Introduced a LAYOUT_ABSOLUTE that disables all layout logic, for internal use * Divs now draw scrollbars using the slider_hor and slider_ver elements --- src/main.c3 | 18 ++++----- src/ugui_button.c3 | 14 ++----- src/ugui_cmd.c3 | 2 +- src/ugui_core.c3 | 17 ++++---- src/ugui_div.c3 | 98 +++++++++++++++------------------------------- src/ugui_layout.c3 | 8 ++-- src/ugui_slider.c3 | 48 ++++++++++++----------- src/ugui_text.c3 | 2 +- 8 files changed, 86 insertions(+), 121 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index c2dd11d..1d678b7 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -150,18 +150,18 @@ fn int main(String[] args) {| ui.layout_set_column()!!; static float slider2 = 0.5; - if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100}, &slider2)!!.update) { + if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider2)!!.update) { io::printfn("other slider: %f", slider2); } - ui.button("button10", ugui::Rect{0,0,50,50})!!; - ui.button("button11", ugui::Rect{0,0,50,50})!!; - ui.button("button12", ugui::Rect{0,0,50,50})!!; - ui.button("button13", ugui::Rect{0,0,50,50})!!; + ui.button("button0", ugui::Rect{0,0,50,50})!!; + ui.button("button1", ugui::Rect{0,0,50,50})!!; + ui.button("button2", ugui::Rect{0,0,50,50})!!; + ui.button("button3", ugui::Rect{0,0,50,50})!!; if (toggle) { - ui.button("button14", ugui::Rect{0,0,50,50})!!; - ui.button("button15", ugui::Rect{0,0,50,50})!!; - ui.button("button16", ugui::Rect{0,0,50,50})!!; - ui.button("button17", ugui::Rect{0,0,50,50})!!; + ui.button("button4", ugui::Rect{0,0,50,50})!!; + ui.button("button5", ugui::Rect{0,0,50,50})!!; + ui.button("button6", ugui::Rect{0,0,50,50})!!; + ui.button("button7", ugui::Rect{0,0,50,50})!!; } ui.layout_next_column()!!; ui.layout_set_row()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 7db15c1..4572ed0 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -8,27 +8,19 @@ struct ElemButton { bool active; } -const Elem BUTTON_DEFAULTS = { - .type = ETYPE_BUTTON, - .button = { - .color = uint_to_rgba(0x0000ffff), - }, -}; - // draw a button, return the events on that button fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) { - Id id = label.hash(); + Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; // add it to the tree ctx.tree.add(id, ctx.active_div)!; - bool needs_layout = c_elem.flags.is_new || parent.flags.updated; - if (c_elem.flags.is_new) { - *c_elem = BUTTON_DEFAULTS; + c_elem.type = ETYPE_BUTTON; + c_elem.button.color = uint_to_rgba(0x0000ffff); } else if (c_elem.type != ETYPE_BUTTON) { return UgError.WRONG_ELEMENT_TYPE?; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 7599d35..ba6c208 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -171,7 +171,7 @@ fn void! Ctx.push_scissor(&ctx, Rect rect) { Cmd sc = { .type = CMD_SCISSOR, - .scissor.rect = rect, + .scissor.rect = rect.intersection(ctx.div_scissor), }; ctx.cmd_queue.enqueue(&sc)!; } diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index edc29b8..8a54b29 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -99,12 +99,6 @@ const uint MAX_ELEMS = 128; const uint MAX_CMDS = 256; const uint ROOT_ID = 1; -enum Layout { - ROW, - COLUMN, - FLOATING -} - // global style, similar to the css box model struct Style { // css box model Rect padding; @@ -118,7 +112,6 @@ struct Style { // css box model struct Ctx { - Layout layout; IdTree tree; ElemCache cache; CmdQueue cmd_queue; @@ -141,6 +134,7 @@ struct Ctx { } } + Rect div_scissor; isz active_div; // tree node indicating the current active div } @@ -180,6 +174,12 @@ fn Elem*! Ctx.get_parent(&ctx) return ctx.cache.search(parent_id); } +macro Id! Ctx.gen_id(&ctx, String label) +{ + Id parent_id = ctx.tree.get(ctx.active_div)!; + return parent_id ^ label.hash(); +} + // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly macro Ctx.get_elem(&ctx, Id id) @@ -218,7 +218,6 @@ fn void! Ctx.init(&ctx) ctx.cmd_queue.init(MAX_ELEMENTS)!; defer catch { (void)ctx.cmd_queue.free(); } - ctx.layout = Layout.ROW; ctx.active_div = 0; // TODO: add style config @@ -275,6 +274,8 @@ fn void! Ctx.frame_begin(&ctx) // 3. Push the root element into the element tree ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; + ctx.div_scissor = {0, 0, ctx.width, ctx.height}; + // The root element does not push anything to the stack // TODO: add a background color taken from a theme or config } diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index fd43738..6d636d6 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -7,6 +7,7 @@ enum DivLayout { LAYOUT_ROW, LAYOUT_COLUMN, LAYOUT_FLOATING, + LAYOUT_ABSOLUTE, } // div element @@ -26,19 +27,9 @@ struct ElemDiv { Color bgcolor; } -const Elem DIV_DEFAULTS = { - .type = ETYPE_DIV, - .div = { - .scroll.can_x = false, - .scroll.can_y = false, - .scroll.value_x = 0, - .scroll.value_y = 0, - }, -}; - fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) { - Id id = label.hash(); + Id id = ctx.gen_id(label)!; Elem* parent = ctx.get_parent()!; Elem* c_elem = ctx.get_elem(id)!; @@ -48,7 +39,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo bool is_new = c_elem.flags.is_new; if (c_elem.flags.is_new) { - *c_elem = DIV_DEFAULTS; + c_elem.type = ETYPE_DIV; } else if (c_elem.type != ETYPE_DIV) { return UgError.WRONG_ELEMENT_TYPE?; } @@ -60,6 +51,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo c_elem.div.children_bounds = c_elem.bounds; c_elem.div.bgcolor = ctx.style.bgcolor; + // update the ctx scissor + ctx.div_scissor = c_elem.bounds; + ctx.push_scissor(c_elem.bounds)!; + // 4. Fill the div fields c_elem.div.origin_c = Point{ .x = c_elem.bounds.x, @@ -77,8 +72,23 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo // TODO: check active // TODO: check resizeable - // FIXME: we cannot use slider elements as scrollbars because of id management - // scrollbars + if (parent.flags.has_focus) { + if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { + c_elem.flags.has_focus = true; + } + } + +} + +fn void! Ctx.div_end(&ctx) +{ + // swap the children bounds + Elem* parent = ctx.get_parent()!; + Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; + c_elem.div.pcb = c_elem.div.children_bounds; + + c_elem.events = ctx.get_elem_events(c_elem); + Rect cb = c_elem.div.pcb; // children bounds bottom-right corner Point cbc = { @@ -105,25 +115,10 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo .w = 10, .h = c_elem.bounds.h, }; - - short hh = (short)(max((float)bc.y / cbc.y, (float)0.15) * c_elem.bounds.h); - Rect vhandle = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, - .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y), - .w = 10, - .h = hh, - }; - - Point m = ctx.input.mouse.pos; - if (parent.flags.has_focus && c_elem.events.mouse_hover && - c_elem.events.mouse_hold && point_in_rect(m, vhandle)) { - vhandle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, c_elem.div.scroll.value_y); - c_elem.div.scroll.value_y = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); - c_elem.flags.updated = true; - } - - ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!; - ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!; + DivLayout prev_l = c_elem.div.layout; + c_elem.div.layout = LAYOUT_ABSOLUTE; + ctx.slider_ver("div_scrollbar_vertical", vslider, &c_elem.div.scroll.value_y, max((float)bc.y / cbc.y, (float)0.15))!; + c_elem.div.layout = prev_l; } if (c_elem.div.scroll.on_x) { @@ -133,41 +128,12 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo .w = c_elem.bounds.w, .h = 10, }; - - short hw = (short)(max((float)bc.x / cbc.x, (float)0.15) * c_elem.bounds.w); - Rect hhandle = { - .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x), - .y = c_elem.bounds.y + c_elem.bounds.h - 10, - .w = hw, - .h = 10, - }; - - Point m = ctx.input.mouse.pos; - if (parent.flags.has_focus && c_elem.events.mouse_hover && - c_elem.events.mouse_hold && point_in_rect(m, hhandle)) { - hhandle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, c_elem.div.scroll.value_x); - c_elem.div.scroll.value_x = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); - c_elem.flags.updated = true; - } - - ctx.push_rect(hslider, uint_to_rgba(0x999999ff))!; - ctx.push_rect(hhandle, uint_to_rgba(0x9999ffff))!; + DivLayout prev_l = c_elem.div.layout; + c_elem.div.layout = LAYOUT_ABSOLUTE; + ctx.slider_hor("div_scrollbar_horizontal", hslider, &c_elem.div.scroll.value_x, max((float)bc.x / cbc.x, (float)0.15))!; + c_elem.div.layout = prev_l; } - if (parent.flags.has_focus) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { - c_elem.flags.has_focus = true; - } - } - -} - -fn void! Ctx.div_end(&ctx) -{ - // swap the children bounds - Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; - c_elem.div.pcb = c_elem.div.children_bounds; - // the active_div returns to the parent of the current one ctx.active_div = ctx.tree.parentof(ctx.active_div)!; } diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 59e6c9a..a65ee78 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -94,9 +94,11 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) origin = div.origin_r; case LAYOUT_COLUMN: origin = div.origin_c; - case LAYOUT_FLOATING: // none - default: - // Error + case LAYOUT_FLOATING: // none, relative to zero zero + case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect + return rect; + default: // error + return Rect{}; } // the bottom-right border of the element box diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 7d15450..28fd6a4 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -8,10 +8,6 @@ struct ElemSlider { Rect handle; } -const Elem SLIDER_DEFAULT = { - .type = ETYPE_SLIDER, -}; - /* handle * +----+-----+---------------------+ * | |#####| | @@ -20,9 +16,15 @@ const Elem SLIDER_DEFAULT = { <* @require value != null *> -fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) +fn ElemEvents! Ctx.slider_hor(&ctx, + String label, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = uint_to_rgba(0x0000ffff), + Color handlecolor = uint_to_rgba(0x0ff000ff)) { - Id id = label.hash(); + Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; @@ -31,8 +33,8 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) // 1. Fill the element fields if (c_elem.flags.is_new) { - *c_elem = SLIDER_DEFAULT; - } else if (c_elem.type != SLIDER_DEFAULT.type) { + c_elem.type = ETYPE_SLIDER; + } else if (c_elem.type != ETYPE_SLIDER) { return UgError.WRONG_ELEMENT_TYPE?; } @@ -40,7 +42,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) c_elem.bounds = ctx.position_element(parent, size, true); // handle width - short hw = (short)(c_elem.bounds.w * 0.25); + short hw = (short)(c_elem.bounds.w * hpercent); Rect handle = { .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value), .y = c_elem.bounds.y, @@ -60,10 +62,8 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) } // Draw the slider background and handle - Color bg_color = uint_to_rgba(0x0000ffff); - Color handle_color = uint_to_rgba(0x0ff000ff); - ctx.push_rect(c_elem.bounds, bg_color)!; - ctx.push_rect(c_elem.slider.handle, handle_color)!; + ctx.push_rect(c_elem.bounds, bgcolor)!; + ctx.push_rect(c_elem.slider.handle, handlecolor)!; return c_elem.events; } @@ -80,9 +80,15 @@ fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size, float* value) * | | * +-+ */ -fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) +fn ElemEvents! Ctx.slider_ver(&ctx, + String label, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = uint_to_rgba(0x0000ffff), + Color handlecolor = uint_to_rgba(0x0ff000ff)) { - Id id = label.hash(); + Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; @@ -91,8 +97,8 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) // 1. Fill the element fields if (c_elem.flags.is_new) { - *c_elem = SLIDER_DEFAULT; - } else if (c_elem.type != SLIDER_DEFAULT.type) { + c_elem.type = ETYPE_SLIDER; + } else if (c_elem.type != ETYPE_SLIDER) { return UgError.WRONG_ELEMENT_TYPE?; } @@ -100,7 +106,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) c_elem.bounds = ctx.position_element(parent, size, true); // handle height - short hh = (short)(c_elem.bounds.h * 0.25); + short hh = (short)(c_elem.bounds.h * hpercent); Rect handle = { .x = c_elem.bounds.x, .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value), @@ -121,10 +127,8 @@ fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size, float* value) } // Draw the slider background and handle - Color bg_color = uint_to_rgba(0x0000ffff); - Color handle_color = uint_to_rgba(0x0ff000ff); - ctx.push_rect(c_elem.bounds, bg_color)!; - ctx.push_rect(c_elem.slider.handle, handle_color)!; + ctx.push_rect(c_elem.bounds, bgcolor)!; + ctx.push_rect(c_elem.slider.handle, handlecolor)!; return c_elem.events; } diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 300825a..58da02f 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -4,7 +4,7 @@ import std::io; fn void! Ctx.text_unbounded(&ctx, String label, String text) { - Id id = label.hash(); + Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; Elem *c_elem = ctx.get_elem(id)!; From 601a396aa8162635c6c59127683398deee1f1440 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 19 Dec 2024 00:29:30 +0100 Subject: [PATCH 062/250] scrollbar fixup --- src/ugui_div.c3 | 60 ++++++++++++++++++++++++---------------------- src/ugui_layout.c3 | 22 +++++++++++------ 2 files changed, 46 insertions(+), 36 deletions(-) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 6d636d6..ea10c0a 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -3,23 +3,22 @@ module ugui; import std::io; import std::math; -enum DivLayout { - LAYOUT_ROW, - LAYOUT_COLUMN, - LAYOUT_FLOATING, - LAYOUT_ABSOLUTE, -} +const short SCROLLBAR_DIM = 5; // div element struct ElemDiv { - DivLayout layout; - struct scroll { - bool can_x; - bool can_y; - bool on_x; - bool on_y; - float value_x; - float value_y; + Layout layout; + struct scroll_x { + bool enabled; + bool on; + bool focus; + float value; + } + struct scroll_y { + bool enabled; + bool on; + bool focus; + float value; } Rect children_bounds; // current frame children bounds Rect pcb; // previous frame children bounds @@ -43,8 +42,8 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo } else if (c_elem.type != ETYPE_DIV) { return UgError.WRONG_ELEMENT_TYPE?; } - c_elem.div.scroll.can_x = scroll_x; - c_elem.div.scroll.can_y = scroll_y; + c_elem.div.scroll_x.enabled = scroll_x; + c_elem.div.scroll_y.enabled = scroll_y; // 2. layout the element c_elem.bounds = ctx.position_element(parent, size); @@ -103,34 +102,37 @@ fn void! Ctx.div_end(&ctx) // set the scrollbar flag, is used in layout - // vertical overflow - c_elem.div.scroll.on_y = cbc.y > bc.y && c_elem.div.scroll.can_y; // horizontal overflow - c_elem.div.scroll.on_x = cbc.x > bc.x && c_elem.div.scroll.can_x; + c_elem.div.scroll_x.on = cbc.x > bc.x && c_elem.div.scroll_x.enabled; + // vertical overflow + c_elem.div.scroll_y.on = cbc.y > bc.y && c_elem.div.scroll_y.enabled; - if (c_elem.div.scroll.on_y) { + short wdim = c_elem.div.scroll_y.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM; + short hdim = c_elem.div.scroll_x.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM; + + if (c_elem.div.scroll_y.on) { Rect vslider = { - .x = c_elem.bounds.x + c_elem.bounds.w - 10, + .x = c_elem.bounds.x + c_elem.bounds.w - wdim, .y = c_elem.bounds.y, - .w = 10, + .w = wdim, .h = c_elem.bounds.h, }; - DivLayout prev_l = c_elem.div.layout; + Layout prev_l = c_elem.div.layout; c_elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_ver("div_scrollbar_vertical", vslider, &c_elem.div.scroll.value_y, max((float)bc.y / cbc.y, (float)0.15))!; + ctx.slider_ver("div_scrollbar_vertical", vslider, &c_elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; c_elem.div.layout = prev_l; } - if (c_elem.div.scroll.on_x) { + if (c_elem.div.scroll_x.on) { Rect hslider = { .x = c_elem.bounds.x, - .y = c_elem.bounds.y + c_elem.bounds.h - 10, + .y = c_elem.bounds.y + c_elem.bounds.h - hdim, .w = c_elem.bounds.w, - .h = 10, + .h = hdim, }; - DivLayout prev_l = c_elem.div.layout; + Layout prev_l = c_elem.div.layout; c_elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_hor("div_scrollbar_horizontal", hslider, &c_elem.div.scroll.value_x, max((float)bc.x / cbc.x, (float)0.15))!; + ctx.slider_hor("div_scrollbar_horizontal", hslider, &c_elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; c_elem.div.layout = prev_l; } diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index a65ee78..095b41a 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -1,5 +1,11 @@ module ugui; +enum Layout { + LAYOUT_ROW, + LAYOUT_COLUMN, + LAYOUT_FLOATING, + LAYOUT_ABSOLUTE, +} fn void! Ctx.layout_set_row(&ctx) { @@ -139,19 +145,21 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) }; // 4. Calculate the "scrolled" view - Point off; + Rect off; Rect* cb = &div.children_bounds; - if (div.scroll.can_x && div.scroll.on_x) { - off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll.value_x); + if (div.scroll_x.enabled && div.scroll_x.on) { + off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll_x.value); + off.w = SCROLLBAR_DIM; } - if (div.scroll.can_y && div.scroll.on_y) { - off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll.value_y); + if (div.scroll_y.enabled && div.scroll_y.on) { + off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll_y.value); + off.h = SCROLLBAR_DIM; } Rect view = { .x = parent.bounds.x + off.x, .y = parent.bounds.y + off.y, - .w = parent.bounds.w, - .h = parent.bounds.h, + .w = parent.bounds.w - off.w, + .h = parent.bounds.h - off.h, }; // 5. check if the placement overflows the children ounds, if so update them From 1088083e1e047aa924c9bb7df39ab09f8fb9dbfc Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 19 Dec 2024 15:24:39 +0100 Subject: [PATCH 063/250] stuff, mostly renaming variables --- src/main.c3 | 6 +-- src/ugui_button.c3 | 30 +++++++------- src/ugui_core.c3 | 22 +++++------ src/ugui_div.c3 | 98 +++++++++++++++++++++++----------------------- src/ugui_input.c3 | 14 +++---- src/ugui_slider.c3 | 76 +++++++++++++++++------------------ src/ugui_text.c3 | 10 ++--- 7 files changed, 127 insertions(+), 129 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 1d678b7..42f38a9 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -132,10 +132,8 @@ fn int main(String[] args) io::printn("press button1"); } //ui.layout_next_column()!!; - if (toggle) { - if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { - io::printn("release button2"); - } + if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + io::printn("release button2"); } static float slider1; if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider1)!!.update) { diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 4572ed0..e1d9fb6 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -14,39 +14,39 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; - Elem *c_elem = ctx.get_elem(id)!; + Elem *elem = ctx.get_elem(id)!; // add it to the tree ctx.tree.add(id, ctx.active_div)!; - if (c_elem.flags.is_new) { - c_elem.type = ETYPE_BUTTON; - c_elem.button.color = uint_to_rgba(0x0000ffff); - } else if (c_elem.type != ETYPE_BUTTON) { + if (elem.flags.is_new) { + elem.type = ETYPE_BUTTON; + elem.button.color = uint_to_rgba(0x0000ffff); + } else if (elem.type != ETYPE_BUTTON) { return UgError.WRONG_ELEMENT_TYPE?; } - c_elem.bounds = ctx.position_element(parent, size, true); + elem.bounds = ctx.position_element(parent, size, true); // if the bounds are null the element is outside the div view, // no interaction should occur so just return - if (c_elem.bounds.is_null()) { return ElemEvents{}; } + if (elem.bounds.is_null()) { return ElemEvents{}; } - c_elem.events = ctx.get_elem_events(c_elem); - if (c_elem.events.mouse_hover) { + elem.events = ctx.get_elem_events(elem); + if (elem.events.mouse_hover) { if (parent.flags.has_focus) { - c_elem.flags.has_focus = true; - c_elem.button.color = uint_to_rgba(0x00ff00ff); + elem.flags.has_focus = true; + elem.button.color = uint_to_rgba(0x00ff00ff); } } else { - c_elem.button.color = uint_to_rgba(0x0000ffff); + elem.button.color = uint_to_rgba(0x0000ffff); } if (state) { - c_elem.button.color = uint_to_rgba(0xff00ffff); + elem.button.color = uint_to_rgba(0xff00ffff); } // Draw the button - ctx.push_rect(c_elem.bounds, c_elem.button.color, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, elem.button.color, do_border: true, do_radius: true)!; - return c_elem.events; + return elem.events; } diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 8a54b29..8bc082a 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -134,7 +134,7 @@ struct Ctx { } } - Rect div_scissor; + Rect div_scissor; // the current div bounds used for scissor test isz active_div; // tree node indicating the current active div } @@ -186,11 +186,11 @@ macro Ctx.get_elem(&ctx, Id id) { Elem empty_elem; bool is_new; - Elem* c_elem; - c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; - c_elem.flags = (ElemFlags)0; - c_elem.flags.is_new = is_new; - return c_elem; + Elem* elem; + elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; + elem.flags = (ElemFlags)0; + elem.flags.is_new = is_new; + return elem; } // this searches an element in the cache by label, it does not create a new element @@ -242,15 +242,15 @@ fn void! Ctx.frame_begin(&ctx) { // 2. Get the root element from the cache and update it - Elem* c_elem = ctx.get_elem(ROOT_ID)!; + Elem* elem = ctx.get_elem(ROOT_ID)!; // The root should have the updated flag only if the size of the window // was changed between frames, this propagates an element size recalculation // down the element tree - c_elem.flags.updated = ctx.input.events.resize; + elem.flags.updated = ctx.input.events.resize; // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff - c_elem.flags.has_focus = ctx.has_focus; + elem.flags.has_focus = ctx.has_focus; Elem def_root = { .id = ROOT_ID, @@ -266,10 +266,10 @@ fn void! Ctx.frame_begin(&ctx) .h = ctx.height, } }, - .flags = c_elem.flags, + .flags = elem.flags, }; - *c_elem = def_root; + *elem = def_root; // 3. Push the root element into the element tree ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index ea10c0a..9647a84 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -23,7 +23,9 @@ struct ElemDiv { Rect children_bounds; // current frame children bounds Rect pcb; // previous frame children bounds Point origin_r, origin_c; - Color bgcolor; + // the id of the active element, this field is set and reset by the elements themselves and it is + // used to keep track of which element should handle some input events between frames + Id active_id; } fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) @@ -31,49 +33,48 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo Id id = ctx.gen_id(label)!; Elem* parent = ctx.get_parent()!; - Elem* c_elem = ctx.get_elem(id)!; + Elem* elem = ctx.get_elem(id)!; isz div_node = ctx.tree.add(id, ctx.active_div)!; ctx.active_div = div_node; - bool is_new = c_elem.flags.is_new; + bool is_new = elem.flags.is_new; - if (c_elem.flags.is_new) { - c_elem.type = ETYPE_DIV; - } else if (c_elem.type != ETYPE_DIV) { + if (elem.flags.is_new) { + elem.type = ETYPE_DIV; + } else if (elem.type != ETYPE_DIV) { return UgError.WRONG_ELEMENT_TYPE?; } - c_elem.div.scroll_x.enabled = scroll_x; - c_elem.div.scroll_y.enabled = scroll_y; + elem.div.scroll_x.enabled = scroll_x; + elem.div.scroll_y.enabled = scroll_y; // 2. layout the element - c_elem.bounds = ctx.position_element(parent, size); - c_elem.div.children_bounds = c_elem.bounds; - c_elem.div.bgcolor = ctx.style.bgcolor; + elem.bounds = ctx.position_element(parent, size); + elem.div.children_bounds = elem.bounds; // update the ctx scissor - ctx.div_scissor = c_elem.bounds; - ctx.push_scissor(c_elem.bounds)!; + ctx.div_scissor = elem.bounds; + ctx.push_scissor(elem.bounds)!; // 4. Fill the div fields - c_elem.div.origin_c = Point{ - .x = c_elem.bounds.x, - .y = c_elem.bounds.y, + elem.div.origin_c = Point{ + .x = elem.bounds.x, + .y = elem.bounds.y, }; - c_elem.div.origin_r = c_elem.div.origin_c; - c_elem.div.layout = parent.div.layout; + elem.div.origin_r = elem.div.origin_c; + elem.div.layout = parent.div.layout; // Add the background to the draw stack bool do_border = parent.div.layout == LAYOUT_FLOATING; - ctx.push_rect(c_elem.bounds, c_elem.div.bgcolor, do_border: do_border)!; + ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: do_border)!; - c_elem.events = ctx.get_elem_events(c_elem); + elem.events = ctx.get_elem_events(elem); // TODO: check active // TODO: check resizeable if (parent.flags.has_focus) { - if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) { - c_elem.flags.has_focus = true; + if (point_in_rect(ctx.input.mouse.pos, elem.bounds)) { + elem.flags.has_focus = true; } } @@ -83,12 +84,12 @@ fn void! Ctx.div_end(&ctx) { // swap the children bounds Elem* parent = ctx.get_parent()!; - Elem* c_elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; - c_elem.div.pcb = c_elem.div.children_bounds; + Elem* elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; + elem.div.pcb = elem.div.children_bounds; - c_elem.events = ctx.get_elem_events(c_elem); + elem.events = ctx.get_elem_events(elem); - Rect cb = c_elem.div.pcb; + Rect cb = elem.div.pcb; // children bounds bottom-right corner Point cbc = { .x = cb.x + cb.w, @@ -96,44 +97,43 @@ fn void! Ctx.div_end(&ctx) }; // div bounds bottom-right corner Point bc = { - .x = c_elem.bounds.x + c_elem.bounds.w, - .y = c_elem.bounds.y + c_elem.bounds.h, + .x = elem.bounds.x + elem.bounds.w, + .y = elem.bounds.y + elem.bounds.h, }; - // set the scrollbar flag, is used in layout // horizontal overflow - c_elem.div.scroll_x.on = cbc.x > bc.x && c_elem.div.scroll_x.enabled; + elem.div.scroll_x.on = cbc.x > bc.x && elem.div.scroll_x.enabled; // vertical overflow - c_elem.div.scroll_y.on = cbc.y > bc.y && c_elem.div.scroll_y.enabled; + elem.div.scroll_y.on = cbc.y > bc.y && elem.div.scroll_y.enabled; - short wdim = c_elem.div.scroll_y.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM; - short hdim = c_elem.div.scroll_x.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM; + short wdim = elem.div.scroll_y.on ? (elem.div.scroll_y.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short hdim = elem.div.scroll_x.on ? (elem.div.scroll_x.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; - if (c_elem.div.scroll_y.on) { + if (elem.div.scroll_y.on) { Rect vslider = { - .x = c_elem.bounds.x + c_elem.bounds.w - wdim, - .y = c_elem.bounds.y, + .x = elem.bounds.x + elem.bounds.w - wdim, + .y = elem.bounds.y, .w = wdim, - .h = c_elem.bounds.h, + .h = elem.bounds.h - hdim, }; - Layout prev_l = c_elem.div.layout; - c_elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_ver("div_scrollbar_vertical", vslider, &c_elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; - c_elem.div.layout = prev_l; + Layout prev_l = elem.div.layout; + elem.div.layout = LAYOUT_ABSOLUTE; + ctx.slider_ver("div_scrollbar_vertical", vslider, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; + elem.div.layout = prev_l; } - if (c_elem.div.scroll_x.on) { + if (elem.div.scroll_x.on) { Rect hslider = { - .x = c_elem.bounds.x, - .y = c_elem.bounds.y + c_elem.bounds.h - hdim, - .w = c_elem.bounds.w, + .x = elem.bounds.x, + .y = elem.bounds.y + elem.bounds.h - hdim, + .w = elem.bounds.w - wdim, .h = hdim, }; - Layout prev_l = c_elem.div.layout; - c_elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_hor("div_scrollbar_horizontal", hslider, &c_elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; - c_elem.div.layout = prev_l; + Layout prev_l = elem.div.layout; + elem.div.layout = LAYOUT_ABSOLUTE; + ctx.slider_hor("div_scrollbar_horizontal", hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; + elem.div.layout = prev_l; } // the active_div returns to the parent of the current one diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 92d31a7..0c4421e 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -45,13 +45,13 @@ macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down; macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down; macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down; -const MouseButtons BTN_NONE = (MouseButtons)0u; -const MouseButtons BTN_ANY = (MouseButtons)(uint.max); -const MouseButtons BTN_LEFT = {.btn_left = true}; +const MouseButtons BTN_NONE = (MouseButtons)0u; +const MouseButtons BTN_ANY = (MouseButtons)(uint.max); +const MouseButtons BTN_LEFT = {.btn_left = true}; const MouseButtons BTN_MIDDLE = {.btn_middle = true}; -const MouseButtons BTN_RIGHT = {.btn_right = true}; -const MouseButtons BTN_4 = {.btn_4 = true}; -const MouseButtons BTN_5 = {.btn_5 = true}; +const MouseButtons BTN_RIGHT = {.btn_right = true}; +const MouseButtons BTN_4 = {.btn_4 = true}; +const MouseButtons BTN_5 = {.btn_5 = true}; // FIXME: hthis compairson could be done with a cast using MouseButtons.inner // property but I could not figure out how @@ -63,7 +63,7 @@ macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { // TODO: add the other events // FIXME: active should be elsewhere - bool active = elem.events.mouse_hold || ctx.is_hovered(elem); + bool active = ctx.is_hovered(elem); ElemEvents ev = { .mouse_hover = ctx.is_hovered(elem), .mouse_press = active & ctx.is_mouse_pressed(BTN_ANY), diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 28fd6a4..81671db 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -27,45 +27,45 @@ fn ElemEvents! Ctx.slider_hor(&ctx, Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; - Elem *c_elem = ctx.get_elem(id)!; + Elem *elem = ctx.get_elem(id)!; // add it to the tree ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - if (c_elem.flags.is_new) { - c_elem.type = ETYPE_SLIDER; - } else if (c_elem.type != ETYPE_SLIDER) { + if (elem.flags.is_new) { + elem.type = ETYPE_SLIDER; + } else if (elem.type != ETYPE_SLIDER) { return UgError.WRONG_ELEMENT_TYPE?; } // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); + elem.bounds = ctx.position_element(parent, size, true); // handle width - short hw = (short)(c_elem.bounds.w * hpercent); + short hw = (short)(elem.bounds.w * hpercent); Rect handle = { - .x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value), - .y = c_elem.bounds.y, + .x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value), + .y = elem.bounds.y, .w = hw, - .h = c_elem.bounds.h, + .h = elem.bounds.h, }; - c_elem.slider.handle = handle; + elem.slider.handle = handle; Point m = ctx.input.mouse.pos; - c_elem.events = ctx.get_elem_events(c_elem); + elem.events = ctx.get_elem_events(elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover && - point_in_rect(m, handle) && c_elem.events.mouse_hold) { - *value = calc_value(c_elem.bounds.x, m.x, c_elem.bounds.w, hw); - c_elem.slider.handle.x = calc_slider(c_elem.bounds.x, c_elem.bounds.w-hw, *value); - c_elem.events.update = true; + if (parent.flags.has_focus && elem.events.mouse_hover && + point_in_rect(m, handle) && elem.events.mouse_hold) { + *value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw); + elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value); + elem.events.update = true; } // Draw the slider background and handle - ctx.push_rect(c_elem.bounds, bgcolor)!; - ctx.push_rect(c_elem.slider.handle, handlecolor)!; + ctx.push_rect(elem.bounds, bgcolor)!; + ctx.push_rect(elem.slider.handle, handlecolor)!; - return c_elem.events; + return elem.events; } /* @@ -91,46 +91,46 @@ fn ElemEvents! Ctx.slider_ver(&ctx, Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; - Elem *c_elem = ctx.get_elem(id)!; + Elem *elem = ctx.get_elem(id)!; // add it to the tree ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields - if (c_elem.flags.is_new) { - c_elem.type = ETYPE_SLIDER; - } else if (c_elem.type != ETYPE_SLIDER) { + if (elem.flags.is_new) { + elem.type = ETYPE_SLIDER; + } else if (elem.type != ETYPE_SLIDER) { return UgError.WRONG_ELEMENT_TYPE?; } // 2. Layout - c_elem.bounds = ctx.position_element(parent, size, true); + elem.bounds = ctx.position_element(parent, size, true); // handle height - short hh = (short)(c_elem.bounds.h * hpercent); + short hh = (short)(elem.bounds.h * hpercent); Rect handle = { - .x = c_elem.bounds.x, - .y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value), - .w = c_elem.bounds.w, + .x = elem.bounds.x, + .y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value), + .w = elem.bounds.w, .h = hh, }; - c_elem.slider.handle = handle; + elem.slider.handle = handle; Point m = ctx.input.mouse.pos; - c_elem.events = ctx.get_elem_events(c_elem); + elem.events = ctx.get_elem_events(elem); - if (parent.flags.has_focus && c_elem.events.mouse_hover && + if (parent.flags.has_focus && elem.events.mouse_hover && point_in_rect(m, handle) && - c_elem.events.mouse_hold) { - *value = calc_value(c_elem.bounds.y, m.y, c_elem.bounds.h, hh); - c_elem.slider.handle.y = calc_slider(c_elem.bounds.y, c_elem.bounds.h-hh, *value); - c_elem.events.update = true; + elem.events.mouse_hold) { + *value = calc_value(elem.bounds.y, m.y, elem.bounds.h, hh); + elem.slider.handle.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value); + elem.events.update = true; } // Draw the slider background and handle - ctx.push_rect(c_elem.bounds, bgcolor)!; - ctx.push_rect(c_elem.slider.handle, handlecolor)!; + ctx.push_rect(elem.bounds, bgcolor)!; + ctx.push_rect(elem.slider.handle, handlecolor)!; - return c_elem.events; + return elem.events; } macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value); diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 58da02f..02413ae 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -7,20 +7,20 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) Id id = ctx.gen_id(label)!; Elem *parent = ctx.get_parent()!; - Elem *c_elem = ctx.get_elem(id)!; + Elem *elem = ctx.get_elem(id)!; // add it to the tree ctx.tree.add(id, ctx.active_div)!; // 1. Fill the element fields // this resets the flags - c_elem.type = ETYPE_TEXT; + elem.type = ETYPE_TEXT; // if the element is new or the parent was updated then redo layout Rect text_size = ctx.get_text_bounds(text)!; // 2. Layout - c_elem.bounds = ctx.position_element(parent, text_size, true); + elem.bounds = ctx.position_element(parent, text_size, true); // 3. Fill the button specific fields - c_elem.text.str = text; + elem.text.str = text; - ctx.push_string(c_elem.bounds, text)!; + ctx.push_string(elem.bounds, text)!; } From d31b4eab538d28efa361b2b1e62cff068e63d002 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 19 Dec 2024 15:27:14 +0100 Subject: [PATCH 064/250] update todo --- TODO | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index 757c3af..dea5c55 100644 --- a/TODO +++ b/TODO @@ -9,7 +9,7 @@ [ ] Do not redraw if there was no update (no layout and no draw) [ ] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars) -[ ] Clip element bounds to parent div, specifically text +[x] Clip element bounds to parent div, specifically text ## Commands @@ -35,5 +35,5 @@ _ border radius ## Widgets -[ ] Dynamic text box to implement an fps counter +[x] Dynamic text box to implement an fps counter [ ] Button with label From 4bd827ce5c267eaef579f79aefbbcb2129f938f0 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 19 Dec 2024 19:42:02 +0100 Subject: [PATCH 065/250] set ids --- src/ugui_core.c3 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 8bc082a..0a34627 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -190,6 +190,8 @@ macro Ctx.get_elem(&ctx, Id id) elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; elem.flags = (ElemFlags)0; elem.flags.is_new = is_new; + // FIXME: should this be here? or is it better to have the elements set the id? + elem.id = id; return elem; } From f7985f8c7f91666d3a63b958afea68afc38ffa67 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 20 Dec 2024 01:45:10 +0100 Subject: [PATCH 066/250] track focused and hovered elements --- src/main.c3 | 2 +- src/ugui_button.c3 | 20 +++++------------ src/ugui_core.c3 | 55 +++++++++++++++++++++++++++++++++++++--------- src/ugui_div.c3 | 14 +++--------- src/ugui_input.c3 | 14 ------------ src/ugui_slider.c3 | 9 +++----- 6 files changed, 58 insertions(+), 56 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 42f38a9..2a1a5d9 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -122,7 +122,7 @@ fn int main(String[] args) ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; {| - ui.layout_set_row()!!; + ui.layout_set_column()!!; if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index e1d9fb6..598389a 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -4,8 +4,7 @@ import std::io; // button element struct ElemButton { - Color color; - bool active; + int filler; } // draw a button, return the events on that button @@ -20,7 +19,6 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) if (elem.flags.is_new) { elem.type = ETYPE_BUTTON; - elem.button.color = uint_to_rgba(0x0000ffff); } else if (elem.type != ETYPE_BUTTON) { return UgError.WRONG_ELEMENT_TYPE?; } @@ -31,22 +29,16 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) // no interaction should occur so just return if (elem.bounds.is_null()) { return ElemEvents{}; } + Color col = uint_to_rgba(0x0000ffff); elem.events = ctx.get_elem_events(elem); - if (elem.events.mouse_hover) { - if (parent.flags.has_focus) { - elem.flags.has_focus = true; - elem.button.color = uint_to_rgba(0x00ff00ff); - } - } else { - elem.button.color = uint_to_rgba(0x0000ffff); - } - if (state) { - elem.button.color = uint_to_rgba(0xff00ffff); + col = uint_to_rgba(0xff0000ff); + } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { + col = uint_to_rgba(0xff00ffff); } // Draw the button - ctx.push_rect(elem.bounds, elem.button.color, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; return elem.events; } diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 0a34627..065af5d 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -32,8 +32,7 @@ enum ElemType { bitstruct ElemFlags : uint { bool updated : 0; - bool has_focus : 1; - bool is_new : 2; + bool is_new : 1; } bitstruct ElemEvents : uint { @@ -134,6 +133,9 @@ struct Ctx { } } + Id hover_id; + Id focus_id; + Rect div_scissor; // the current div bounds used for scissor test isz active_div; // tree node indicating the current active div } @@ -195,13 +197,15 @@ macro Ctx.get_elem(&ctx, Id id) return elem; } +// FIXME: Since ids are now keyed with the element's parent id, this function does not work +// outside of the element's div block. // this searches an element in the cache by label, it does not create a new element // if it does't find one -macro Ctx.get_elem_by_label(&ctx, String label) -{ - Id id = label.hash(); - return ctx.cache.search(id); -} +//macro Ctx.get_elem_by_label(&ctx, String label) +//{ +// Id id = ctx.get_id(label); +// return ctx.cache.search(id); +//} macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private { @@ -252,7 +256,7 @@ fn void! Ctx.frame_begin(&ctx) // if the window has focus then the root element also has focus, no other // computation needed, child elements need to check the mouse positon and // other stuff - elem.flags.has_focus = ctx.has_focus; + //elem.flags.has_focus = ctx.has_focus; Elem def_root = { .id = ROOT_ID, @@ -317,8 +321,39 @@ $endif <* * @ensure elem != null - *> -fn bool Ctx.is_hovered(&ctx, Elem *elem) +*> +macro bool Ctx.is_hovered(&ctx, Elem *elem) { return point_in_rect(ctx.input.mouse.pos, elem.bounds); } + +macro bool Ctx.elem_focus(&ctx, Elem *elem) +{ + return ctx.focus_id == elem.id; +} + +// TODO: add other events +// FIXME: this does not work with touch +macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) +{ + //io::printfn("id %x, focus id %x", elem.id, ctx.focus_id); + + bool hover = ctx.is_hovered(elem); + bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT)); + + if (hover) { + ctx.hover_id = elem.id; + } + if (focus) { + ctx.focus_id = elem.id; + } + + ElemEvents ev = { + .mouse_hover = hover, + .mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY), + .mouse_release = hover && focus && ctx.is_mouse_released(BTN_ANY), + .mouse_hold = hover && focus && ctx.is_mouse_down(BTN_ANY), + }; + return ev; +} + diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 9647a84..a59d37d 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -23,9 +23,6 @@ struct ElemDiv { Rect children_bounds; // current frame children bounds Rect pcb; // previous frame children bounds Point origin_r, origin_c; - // the id of the active element, this field is set and reset by the elements themselves and it is - // used to keep track of which element should handle some input events between frames - Id active_id; } fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) @@ -71,13 +68,6 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo // TODO: check active // TODO: check resizeable - - if (parent.flags.has_focus) { - if (point_in_rect(ctx.input.mouse.pos, elem.bounds)) { - elem.flags.has_focus = true; - } - } - } fn void! Ctx.div_end(&ctx) @@ -87,7 +77,9 @@ fn void! Ctx.div_end(&ctx) Elem* elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; elem.div.pcb = elem.div.children_bounds; - elem.events = ctx.get_elem_events(elem); + // FIXME: this causes all elements inside the div to loose focus since the mouse press happens + // both inside the element and inside the div bounds + //elem.events = ctx.get_elem_events(elem); Rect cb = elem.div.pcb; // children bounds bottom-right corner diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 0c4421e..a82240b 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -59,20 +59,6 @@ macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE; macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE; -macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) -{ - // TODO: add the other events - // FIXME: active should be elsewhere - bool active = ctx.is_hovered(elem); - ElemEvents ev = { - .mouse_hover = ctx.is_hovered(elem), - .mouse_press = active & ctx.is_mouse_pressed(BTN_ANY), - .mouse_release = active & ctx.is_mouse_released(BTN_ANY), - .mouse_hold = active & ctx.is_mouse_down(BTN_ANY), - }; - return ev; -} - // Mouse Button moved fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) { diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 81671db..e078103 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -54,8 +54,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, Point m = ctx.input.mouse.pos; elem.events = ctx.get_elem_events(elem); - if (parent.flags.has_focus && elem.events.mouse_hover && - point_in_rect(m, handle) && elem.events.mouse_hold) { + if (ctx.elem_focus(elem) && elem.events.mouse_hold) { *value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw); elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value); elem.events.update = true; @@ -117,10 +116,8 @@ fn ElemEvents! Ctx.slider_ver(&ctx, Point m = ctx.input.mouse.pos; elem.events = ctx.get_elem_events(elem); - - if (parent.flags.has_focus && elem.events.mouse_hover && - point_in_rect(m, handle) && - elem.events.mouse_hold) { + + if (ctx.elem_focus(elem) && elem.events.mouse_hold) { *value = calc_value(elem.bounds.y, m.y, elem.bounds.h, hh); elem.slider.handle.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value); elem.events.update = true; From ca691d1294f6df3046abfd07fb9c344d97c81e2a Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 20 Dec 2024 02:17:41 +0100 Subject: [PATCH 067/250] enlarge scrollbars when focused --- src/ugui_div.c3 | 9 +++++---- src/ugui_slider.c3 | 4 ++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index a59d37d..709c8eb 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -11,13 +11,11 @@ struct ElemDiv { struct scroll_x { bool enabled; bool on; - bool focus; float value; } struct scroll_y { bool enabled; bool on; - bool focus; float value; } Rect children_bounds; // current frame children bounds @@ -99,8 +97,11 @@ fn void! Ctx.div_end(&ctx) // vertical overflow elem.div.scroll_y.on = cbc.y > bc.y && elem.div.scroll_y.enabled; - short wdim = elem.div.scroll_y.on ? (elem.div.scroll_y.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; - short hdim = elem.div.scroll_x.on ? (elem.div.scroll_x.focus ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + Id hsid = ctx.gen_id("div_scrollbar_horizontal")!; + Id vsid = ctx.gen_id("div_scrollbar_vertical")!; + + short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; if (elem.div.scroll_y.on) { Rect vslider = { diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index e078103..4641322 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -54,7 +54,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, Point m = ctx.input.mouse.pos; elem.events = ctx.get_elem_events(elem); - if (ctx.elem_focus(elem) && elem.events.mouse_hold) { + if (ctx.elem_focus(elem) && ctx.is_mouse_down(BTN_LEFT)) { *value = calc_value(elem.bounds.x, m.x, elem.bounds.w, hw); elem.slider.handle.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value); elem.events.update = true; @@ -117,7 +117,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, Point m = ctx.input.mouse.pos; elem.events = ctx.get_elem_events(elem); - if (ctx.elem_focus(elem) && elem.events.mouse_hold) { + if (ctx.elem_focus(elem) && ctx.is_mouse_down(BTN_LEFT)) { *value = calc_value(elem.bounds.y, m.y, elem.bounds.h, hh); elem.slider.handle.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value); elem.events.update = true; From a0c6a3b2cbb79e8a8706198758dd970f4767b89c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 20 Dec 2024 19:50:58 +0100 Subject: [PATCH 068/250] work on button with label --- src/main.c3 | 17 ++++++++++++----- src/ugui_button.c3 | 41 +++++++++++++++++++++++++++++++++++++++++ src/ugui_core.c3 | 32 +++++++++++++++++++------------- src/ugui_div.c3 | 4 ++-- src/ugui_font.c3 | 10 +++++++++- src/ugui_text.c3 | 8 ++++++-- 6 files changed, 89 insertions(+), 23 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 2a1a5d9..311bd85 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -121,7 +121,6 @@ fn int main(String[] args) // main div, fill the whole window ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; {| - ui.layout_set_column()!!; if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); @@ -135,12 +134,20 @@ fn int main(String[] args) if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } - static float slider1; - if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider1)!!.update) { - io::printfn("slider: %f", slider1); - } + + ui.layout_set_row()!!; + ui.layout_next_row()!!; + static float rf, gf, bf, af; + ui.slider_ver("slider_r", ugui::Rect{0,0,30,100}, &rf)!!; + ui.slider_ver("slider_g", ugui::Rect{0,0,30,100}, &gf)!!; + ui.slider_ver("slider_b", ugui::Rect{0,0,30,100}, &bf)!!; + ui.slider_ver("slider_a", ugui::Rect{0,0,30,100}, &af)!!; + ui.layout_next_column()!!; ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; + + ui.layout_next_column()!!; + ui.button_label("Continua!")!!; |}; ui.div_end()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 598389a..e605c91 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -42,3 +42,44 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) return elem.events; } + +fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.max,short.max}, bool state = false) +{ + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + // this resets the flags + elem.type = ETYPE_BUTTON; + + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + Rect text_size = ctx.get_text_bounds(label)!; + Rect btn_size = rect_add(text_size, Rect{0,0,10,10}); + + // 2. Layout + elem.bounds = ctx.position_element(parent, btn_size, true); + if (elem.bounds.is_null()) { return ElemEvents{}; } + + Color col = uint_to_rgba(0x0000ffff); + elem.events = ctx.get_elem_events(elem); + if (state) { + col = uint_to_rgba(0xff0000ff); + } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { + col = uint_to_rgba(0xff00ffff); + } + + // Draw the button + text_size.x = elem.bounds.x; + text_size.y = elem.bounds.y; + Point off = ctx.center_text(text_size, elem.bounds); + text_size.x += off.x; + text_size.y += off.y; + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + ctx.push_string(text_size, label)!; + + return elem.events; +} diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 065af5d..9e0905c 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -46,10 +46,6 @@ bitstruct ElemEvents : uint { bool update : 7; } -struct ElemText { - char* str; -} - // element structure struct Elem { Id id; @@ -169,6 +165,16 @@ macro bool Rect.collides(Rect a, Rect b) macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; +macro Rect rect_add(Rect r1, Rect r2) +{ + return Rect{ + .x = r1.x + r2.x, + .y = r1.y + r2.y, + .w = r1.w + r2.w, + .h = r1.h + r2.h, + }; +} + // return a pointer to the parent of the current active div fn Elem*! Ctx.get_parent(&ctx) { @@ -334,19 +340,19 @@ macro bool Ctx.elem_focus(&ctx, Elem *elem) // TODO: add other events // FIXME: this does not work with touch -macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) +// FIXME: hacked together, please do better +fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { - //io::printfn("id %x, focus id %x", elem.id, ctx.focus_id); - bool hover = ctx.is_hovered(elem); bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT)); + + if (ctx.is_mouse_pressed(BTN_ANY) && !hover){ + focus = false; + if (ctx.focus_id == elem.id) ctx.focus_id = 0; + } - if (hover) { - ctx.hover_id = elem.id; - } - if (focus) { - ctx.focus_id = elem.id; - } + if (hover) { ctx.hover_id = elem.id; } + if (focus) { ctx.focus_id = elem.id; } ElemEvents ev = { .mouse_hover = hover, diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 709c8eb..f46eb15 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -100,8 +100,8 @@ fn void! Ctx.div_end(&ctx) Id hsid = ctx.gen_id("div_scrollbar_horizontal")!; Id vsid = ctx.gen_id("div_scrollbar_vertical")!; - short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; - short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.hover_id == vsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.hover_id == hsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; if (elem.div.scroll_y.on) { Rect vslider = { diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 6843da0..c9be65d 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -212,8 +212,16 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text) return text_bounds; } +fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds) +{ + short dw = bounds.w - text_bounds.w; + short dh = bounds.h - text_bounds.h; + + return Point{.x = dw/2, .y = dh/2}; +} + // TODO: check if the font is present in the context fn Id Ctx.get_font_id(&ctx, String label) { return (Id)label.hash(); -} \ No newline at end of file +} diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 02413ae..18628e0 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -2,6 +2,10 @@ module ugui; import std::io; +struct ElemText { + char* str; +} + fn void! Ctx.text_unbounded(&ctx, String label, String text) { Id id = ctx.gen_id(label)!; @@ -14,13 +18,13 @@ fn void! Ctx.text_unbounded(&ctx, String label, String text) // 1. Fill the element fields // this resets the flags elem.type = ETYPE_TEXT; + elem.text.str = text; // if the element is new or the parent was updated then redo layout Rect text_size = ctx.get_text_bounds(text)!; // 2. Layout elem.bounds = ctx.position_element(parent, text_size, true); - // 3. Fill the button specific fields - elem.text.str = text; + if (elem.bounds.is_null()) { return; } ctx.push_string(elem.bounds, text)!; } From 04843fe714192139c9a6d8c013b30fac1e5a1fc2 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 20 Dec 2024 20:17:53 +0100 Subject: [PATCH 069/250] do not use xor to combine keys since when more than two layers deep this would result in identical keys --- src/ugui_core.c3 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 9e0905c..3d7f50f 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -182,10 +182,13 @@ fn Elem*! Ctx.get_parent(&ctx) return ctx.cache.search(parent_id); } +// generate an id combining the hashes of the parent id and the label +// with the Cantor pairing function macro Id! Ctx.gen_id(&ctx, String label) { - Id parent_id = ctx.tree.get(ctx.active_div)!; - return parent_id ^ label.hash(); + Id a = ctx.tree.get(ctx.active_div)!; + Id b = label.hash(); + return (a + b) * (a + b + 1) / 2 + a; } // get or push an element from the cache, return a pointer to it From 87de68028ae4157113070741e04552b6ad1f44d1 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 20 Dec 2024 20:58:12 +0100 Subject: [PATCH 070/250] update todo --- TODO | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/TODO b/TODO index dea5c55..323c5bb 100644 --- a/TODO +++ b/TODO @@ -7,9 +7,14 @@ [ ] Write a README.md [ ] Use an arena allocator for cache [ ] Do not redraw if there was no update (no layout and no draw) -[ ] Better handling of the active and focused widgets, try +[x] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars) [x] Clip element bounds to parent div, specifically text +[ ] Keyboard input +[ ] Mouse scroll wheel +[ ] Touch input +[ ] Resizeable divs +[ ] Implement a z index ## Commands @@ -17,6 +22,7 @@ to maintain focus until mouse release (fix scroll bars) _ border width _ border radius [x] add a command to update an atlas +[ ] New window command, useful for popups ## Atlases @@ -36,4 +42,6 @@ _ border radius ## Widgets [x] Dynamic text box to implement an fps counter -[ ] Button with label +[x] Button with label +[ ] Text Input box +[ ] Icon Buttons From 169b5e1dfdec39f353398ad6e46e6a84557bd2f3 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 23 Dec 2024 15:49:46 +0100 Subject: [PATCH 071/250] cull commands that result in zero-area bounding boxes --- src/main.c3 | 48 +++++++++++++++++++++++++----------------------- src/ugui_cmd.c3 | 45 ++++++++++++++++++++++++++++++--------------- 2 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 311bd85..0650e4e 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -66,6 +66,16 @@ void main() } `; +macro rl::Color ugui::Color.conv(color) +{ + return rl::Color{.r = color.r, .g = color.g, .b = color.b, .a = color.a}; +} + +macro rl::Rectangle ugui::Rect.conv(rect) +{ + return rl::Rectangle{.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; +} + fn int main(String[] args) { ugui::Ctx ui; @@ -96,6 +106,17 @@ fn int main(String[] args) while (!rl::window_should_close()) { clock.mark(); + KeyboardKey k; + do { + k = rl::get_key_pressed(); + if (!k) { break; } + if (rl::is_key_down(k)) { io::print("down "); } + if (rl::is_key_pressed(k)) { io::print("pressed "); } + if (rl::is_key_released(k)) { io::print("released "); } + io::printfn("%s", k); + } while (k != 0); + + /* Start Input Handling */ if (rl::is_window_resized()) { width = (short)rl::get_screen_width(); @@ -197,27 +218,14 @@ fn int main(String[] args) rl::begin_drawing(); // ClearBackground(BLACK); - rl::Color c; - rl::Rectangle r; for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { case ugui::CmdType.CMD_RECT: - c = rl::Color{ - .r = cmd.rect.color.r, - .g = cmd.rect.color.g, - .b = cmd.rect.color.b, - .a = cmd.rect.color.a, - }; - r = rl::Rectangle{ - .x = cmd.rect.rect.x, - .y = cmd.rect.rect.y, - .height = cmd.rect.rect.h, - .width = cmd.rect.rect.w, - }; + Rect r = cmd.rect.rect; float rad = cmd.rect.radius; // for some weird-ass reason the straight forward inverse formula does not work - float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width; - rl::draw_rectangle_rounded(r, roundness, 0, c); + float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; + rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); case ugui::CmdType.CMD_UPDATE_ATLAS: if (cmd.update_atlas.id != font_id) { break; } //rl::unload_image(font_atlas); @@ -233,18 +241,12 @@ fn int main(String[] args) font_texture = rl::load_texture_from_image(font_atlas); case ugui::CmdType.CMD_SPRITE: if (cmd.sprite.texture_id != font_id) { break; } - rl::Rectangle source = { - .x = cmd.sprite.texture_rect.x, - .y = cmd.sprite.texture_rect.y, - .width = cmd.sprite.texture_rect.w, - .height = cmd.sprite.texture_rect.h, - }; rl::Vector2 position = { .x = cmd.sprite.rect.x, .y = cmd.sprite.rect.y, }; rl::begin_shader_mode(font_shader); - rl::draw_texture_rec(font_texture, source, position, rl::WHITE); + rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); rl::end_shader_mode(); case ugui::CmdType.CMD_SCISSOR: if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index ba6c208..36b970b 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -29,6 +29,7 @@ struct CmdUpdateAtlas { struct CmdSprite { Rect rect; Rect texture_rect; + Color hue; Id texture_id; } @@ -48,15 +49,29 @@ struct Cmd { } } +macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max}) +{ + bool no_area = rect.w <= 0 || rect.h <= 0; + return no_area || !rect.collides(clip); +} + +// FIXME: this whole thing could be done at compile time, maybe +macro Ctx.push_cmd(&ctx, Cmd *cmd) +{ + Rect rect; + switch (cmd.type) { + case CMD_RECT: rect = cmd.rect.rect; + case CMD_SPRITE: rect = cmd.sprite.rect; + default: return ctx.cmd_queue.enqueue(cmd); + } + if (cull_rect(rect, ctx.div_scissor)) return; + return ctx.cmd_queue.enqueue(cmd); +} + // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { - // FIXME: this should be culled higher up, maybe - if (rect.w <= 0 || rect.h <= 0) { - return; - } - Rect border = ctx.style.border; Rect padding = ctx.style.padding; ushort radius = ctx.style.radius; @@ -69,7 +84,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo .rect.color = border_color, .rect.radius = do_radius ? radius : 0, }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_cmd(&cmd)!; } Cmd cmd = { @@ -83,22 +98,24 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo .rect.color = color, .rect.radius = do_radius ? radius : 0, }; - ctx.cmd_queue.enqueue(&cmd)!; + if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return; + ctx.push_cmd(&cmd)!; } // TODO: add texture id -fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id) +fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = uint_to_rgba(0xffffffff)) { Cmd cmd = { .type = CMD_SPRITE, .sprite.rect = bounds, .sprite.texture_rect = texture, .sprite.texture_id = texture_id, + .sprite.hue = hue, }; - ctx.cmd_queue.enqueue(&cmd)!; + ctx.push_cmd(&cmd)!; } -fn void! Ctx.push_string(&ctx, Rect bounds, String text) +fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = uint_to_rgba(0xffffffff)) { if (text.len == 0) { return; @@ -136,9 +153,7 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text) .h = gp.h, }; // push the sprite only if it collides with the bounds - if (gb.collides(bounds)) { - ctx.push_sprite(gb, gt, texture_id)!; - } + if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, hue)!; line_len += gp.adv; } else if (cp == '\n'){ orig.y += line_height + line_gap; @@ -164,7 +179,7 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) .bpp = (ushort)atlas.type.bpp(), }, }; - ctx.cmd_queue.enqueue(&up)!; + ctx.push_cmd(&up)!; } fn void! Ctx.push_scissor(&ctx, Rect rect) @@ -173,5 +188,5 @@ fn void! Ctx.push_scissor(&ctx, Rect rect) .type = CMD_SCISSOR, .scissor.rect = rect.intersection(ctx.div_scissor), }; - ctx.cmd_queue.enqueue(&sc)!; + ctx.push_cmd(&sc)!; } From 1746f7d940ea51464288181c847508679ba0b356 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 23 Dec 2024 15:50:09 +0100 Subject: [PATCH 072/250] update TODO --- TODO | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 323c5bb..8c53143 100644 --- a/TODO +++ b/TODO @@ -15,6 +15,10 @@ to maintain focus until mouse release (fix scroll bars) [ ] Touch input [ ] Resizeable divs [ ] Implement a z index +[ ] Text reflow +[ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it +[ ] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division +[ ] Animations, somehow ## Commands @@ -36,7 +40,7 @@ _ border radius ## Raylib -[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: +[x] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui:: [x] Implement pixel radius rounding for border radius ## Widgets From 16adfd7cc51af101ce398e0eda986f9c511a0e5e Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 25 Dec 2024 12:30:35 +0100 Subject: [PATCH 073/250] idk look at the changes --- TODO | 27 +++++++-- src/cache.c3 | 15 ++++- src/main.c3 | 28 ++++++--- src/ugui_core.c3 | 37 ++++++++++-- src/ugui_div.c3 | 13 ++++- src/ugui_font.c3 | 1 + src/ugui_input.c3 | 132 +++++++++++++++++++++++++++++++++--------- src/ugui_slider.c3 | 5 +- test/test_error.c3 | 72 +++++++++++++++++++++++ test/test_keyboard.c3 | 26 +++++++++ 10 files changed, 303 insertions(+), 53 deletions(-) create mode 100644 test/test_error.c3 create mode 100644 test/test_keyboard.c3 diff --git a/TODO b/TODO index 8c53143..556b6f5 100644 --- a/TODO +++ b/TODO @@ -7,18 +7,31 @@ [ ] Write a README.md [ ] Use an arena allocator for cache [ ] Do not redraw if there was no update (no layout and no draw) +[ ] Do command buffer damage tracking based on a context grid (see rxi writeup) [x] Better handling of the active and focused widgets, try to maintain focus until mouse release (fix scroll bars) [x] Clip element bounds to parent div, specifically text -[ ] Keyboard input -[ ] Mouse scroll wheel -[ ] Touch input [ ] Resizeable divs -[ ] Implement a z index -[ ] Text reflow +[ ] Implement a z index and sort command buffer based on that [ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it -[ ] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division +[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division [ ] Animations, somehow +[ ] Maybe cache codepoint converted strings +[x] Fix scroll wheel when div is scrolled + +## Layout + +[ ] Text reflow +[ ] Flexbox +[ ] Center elements to the row/column + +## Input + +[x] Keyboard input +[x] Mouse scroll wheel +[ ] Touch input +[x] Do not set input event to true if the movement was zero (like no mouse movement) +[ ] Use input event flags, for example to consume the input event ## Commands @@ -49,3 +62,5 @@ _ border radius [x] Button with label [ ] Text Input box [ ] Icon Buttons +[ ] Switch + diff --git a/src/cache.c3 b/src/cache.c3 index 5b37d29..fac5cf2 100644 --- a/src/cache.c3 +++ b/src/cache.c3 @@ -62,8 +62,9 @@ fn Value*! Cache.search(&cache, Key id) // get_entry() faults on miss IdTableEntry* entry = cache.table.get_entry(id)!; - /* MISS */ + /* MISS, wrong key */ if (entry.key != id) { + cache.table.remove(id)!; return SearchResult.MISSING?; } @@ -79,6 +80,18 @@ fn Value*! Cache.search(&cache, Key id) return &(cache.pool[entry.value]); } +fn void Cache.remove(&cache, Key id) +{ + IdTableEntry*! entry = cache.table.get_entry(id); + if (catch entry) { + return; + } + + // found, remove it + cache.present[entry.value] = false; + (void)cache.table.remove(id); +} + /* Look for a free spot in the present bitmap and return its index */ /* If there is no free space left then just return the first position */ fn usz Cache.get_free_spot(&cache) @private diff --git a/src/main.c3 b/src/main.c3 index 0650e4e..be5c2f3 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -106,6 +106,7 @@ fn int main(String[] args) while (!rl::window_should_close()) { clock.mark(); +/* KeyboardKey k; do { k = rl::get_key_pressed(); @@ -115,7 +116,12 @@ fn int main(String[] args) if (rl::is_key_released(k)) { io::print("released "); } io::printfn("%s", k); } while (k != 0); - +*/ + for (int c; (c = rl::get_char_pressed()) != 0;) { + int[1] ts; + ts[0] = c; + ui.input_text_unicode(ts[..]); + } /* Start Input Handling */ if (rl::is_window_resized()) { @@ -128,11 +134,14 @@ fn int main(String[] args) rl::Vector2 mpos = rl::get_mouse_position(); ui.input_mouse_abs((short)mpos.x, (short)mpos.y); + rl::Vector2 mwheel = rl::get_mouse_wheel_move_v(); + ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y); - ugui::MouseButtons buttons; - buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT); - buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT); - buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE); + ugui::MouseButtons buttons = { + .btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT), + .btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT), + .btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE), + }; ui.input_mouse_button(buttons); /* End Input Handling */ @@ -196,16 +205,17 @@ fn int main(String[] args) ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!; |}; ui.div_end()!!; - + // Timings counter TimeStats dts = draw_times.get_stats(); TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", ugui::Rect{0, ui.height-60, 200, 60})!!; + ui.div_begin("fps", ugui::Rect{0, ui.height-100, 200, 100})!!; {| - ui.layout_set_row()!!; - ui.text_unbounded("ui avg", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; + ui.layout_set_column()!!; + ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; + ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; |}; ui.div_end()!!; diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 3d7f50f..09c5348 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -93,6 +93,7 @@ const uint STACK_STEP = 10; const uint MAX_ELEMS = 128; const uint MAX_CMDS = 256; const uint ROOT_ID = 1; +const uint TEXT_MAX = 64; // global style, similar to the css box model struct Style { // css box model @@ -126,6 +127,13 @@ struct Ctx { // mouse_pressed = mouse_updated & mouse_down MouseButtons down; MouseButtons updated; + // scroll wheel + Point scroll; + } + struct keyboard { + char[TEXT_MAX] text; + usz text_len; + ModKeys down; } } @@ -182,18 +190,26 @@ fn Elem*! Ctx.get_parent(&ctx) return ctx.cache.search(parent_id); } +macro @bits(#a) => $typeof(#a).sizeof*8; +macro Id.rotate_left(id, uint $n) => (id << $n) | (id >> (@bits(id) - $n)); +const uint GOLDEN_RATIO = 0x9E3779B9; + // generate an id combining the hashes of the parent id and the label // with the Cantor pairing function macro Id! Ctx.gen_id(&ctx, String label) { - Id a = ctx.tree.get(ctx.active_div)!; - Id b = label.hash(); - return (a + b) * (a + b + 1) / 2 + a; + Id id1 = ctx.tree.get(ctx.active_div)!; + Id id2 = label.hash(); + // Mix the two IDs non-linearly + Id mixed = id1 ^ id2.rotate_left(13); + mixed ^= id1.rotate_left(7); + mixed += GOLDEN_RATIO; + return mixed; } // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly -macro Ctx.get_elem(&ctx, Id id) +fn Elem*! Ctx.get_elem(&ctx, Id id) { Elem empty_elem; bool is_new; @@ -206,6 +222,18 @@ macro Ctx.get_elem(&ctx, Id id) return elem; } +// find an element, does not allocate a new one in cache +// THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE +macro Elem* Ctx.find_elem(&ctx, Id id) +{ + Elem*! elem; + elem = ctx.cache.search(id); + if (catch elem) { + return &&Elem{}; + } + return elem; +} + // FIXME: Since ids are now keyed with the element's parent id, this function does not work // outside of the element's div block. // this searches an element in the cache by label, it does not create a new element @@ -305,6 +333,7 @@ fn void! Ctx.frame_end(&ctx) // 2. clear input fields ctx.input.events = (InputEvents)0; + ctx.input.keyboard.text_len = 0; // send atlas updates if (ctx.font.should_update) { diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index f46eb15..1d02bdc 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -99,11 +99,14 @@ fn void! Ctx.div_end(&ctx) Id hsid = ctx.gen_id("div_scrollbar_horizontal")!; Id vsid = ctx.gen_id("div_scrollbar_vertical")!; - - short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.hover_id == vsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; - short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.hover_id == hsid ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.is_hovered(ctx.find_elem(vsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; + short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.is_hovered(ctx.find_elem(hsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; if (elem.div.scroll_y.on) { + if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) { + elem.div.scroll_y.value += ctx.input.mouse.scroll.y * 0.07f; + elem.div.scroll_y.value = math::clamp(elem.div.scroll_y.value, 0.0f, 1.0f); + } Rect vslider = { .x = elem.bounds.x + elem.bounds.w - wdim, .y = elem.bounds.y, @@ -117,6 +120,10 @@ fn void! Ctx.div_end(&ctx) } if (elem.div.scroll_x.on) { + if (ctx.input.events.mouse_scroll && ctx.hover_id == elem.id) { + elem.div.scroll_x.value += ctx.input.mouse.scroll.x * 0.07f; + elem.div.scroll_x.value = math::clamp(elem.div.scroll_x.value, 0.0f, 1.0f); + } Rect hslider = { .x = elem.bounds.x, .y = elem.bounds.y + elem.bounds.h - hdim, diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index c9be65d..189fdef 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -76,6 +76,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale) font.sft.font = schrift::loadfile(path); if (font.sft.font == null) { + font.table.free(); return UgFontError.TTF_LOAD_FAILED?; } diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index a82240b..b01d383 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -1,25 +1,72 @@ module ugui; +import grapheme; import std::io; import std::math; -// TODO: this could be a bitstruct + bitstruct InputEvents : uint { - bool resize : 0; // window size was changed + bool resize : 0; // window size was changed bool change_focus : 1; // window focus changed - bool mouse_move : 2; // mouse was moved - bool mouse_btn : 3; // mouse button pressed or released + bool mouse_move : 2; // mouse was moved + bool mouse_btn : 3; // mouse button pressed or released + bool mouse_scroll : 4; // mouse scroll wheel. x or y + bool text_input : 5; + bool mod_key : 6; } +bitstruct MouseButtons : uint { + bool btn_left : 0; + bool btn_middle : 1; + bool btn_right : 2; + bool btn_4 : 3; + bool btn_5 : 4; +} + +// FIXME: all of these names were prefixed with key_ idk if this is better, +// if it is remove the prefix on MouseButtons as well +// Modifier Keys, same as SDL +bitstruct ModKeys : uint { + bool lshift : 0; + bool rshift : 1; + bool lctrl : 2; + bool rctrl : 3; + bool lalt : 4; + bool ralt : 5; + bool lgui : 6; + bool rgui : 7; + bool num : 8; + bool caps : 9; + bool mode : 10; + bool scroll : 11; +} + +const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true}; +const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true}; +const ModKeys KMOD_ALT = {.lalt = true, .ralt = true}; +const ModKeys KMOD_GUI = {.lgui = true, .rgui = true}; +const ModKeys KMOD_NONE = ModKeys{}; +const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max); + +const MouseButtons BTN_NONE = MouseButtons{}; +const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max); +const MouseButtons BTN_LEFT = {.btn_left = true}; +const MouseButtons BTN_MIDDLE = {.btn_middle = true}; +const MouseButtons BTN_RIGHT = {.btn_right = true}; +const MouseButtons BTN_4 = {.btn_4 = true}; +const MouseButtons BTN_5 = {.btn_5 = true}; + +const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max); + // Window size was changed fn void! Ctx.input_window_size(&ctx, short width, short height) { if (width <= 0 || height <= 0) { return UgError.INVALID_SIZE?; } + ctx.input.events.resize = ctx.width != width || ctx.height != height; ctx.width = width; ctx.height = height; - ctx.input.events.resize = true; } // Window gained/lost focus @@ -27,44 +74,26 @@ fn void Ctx.input_changefocus(&ctx, bool has_focus) { // FIXME: raylib only has an API to query the focus status so we have to // update the input flag only if the focus changed - if (ctx.has_focus != has_focus) { - ctx.input.events.change_focus = true; - } + ctx.input.events.change_focus = ctx.has_focus != has_focus; ctx.has_focus = has_focus; } -bitstruct MouseButtons : uint { - bool btn_left : 0; - bool btn_middle : 1; - bool btn_right : 2; - bool btn_4 : 3; - bool btn_5 : 4; -} - macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down; macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down; macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down; -const MouseButtons BTN_NONE = (MouseButtons)0u; -const MouseButtons BTN_ANY = (MouseButtons)(uint.max); -const MouseButtons BTN_LEFT = {.btn_left = true}; -const MouseButtons BTN_MIDDLE = {.btn_middle = true}; -const MouseButtons BTN_RIGHT = {.btn_right = true}; -const MouseButtons BTN_4 = {.btn_4 = true}; -const MouseButtons BTN_5 = {.btn_5 = true}; - // FIXME: hthis compairson could be done with a cast using MouseButtons.inner // property but I could not figure out how macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE; macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE; macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE; -// Mouse Button moved +// Mouse Buttons down fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) { ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons; ctx.input.mouse.down = buttons; - ctx.input.events.mouse_btn = true; + ctx.input.events.mouse_btn = (uint)ctx.input.mouse.down != 0 || (uint)ctx.input.mouse.updated != 0; } // Mouse was moved, report absolute position @@ -80,7 +109,7 @@ fn void Ctx.input_mouse_abs(&ctx, short x, short y) ctx.input.mouse.delta.x = dx; ctx.input.mouse.delta.y = dy; - ctx.input.events.mouse_move = true; + ctx.input.events.mouse_move = dx != 0 || dy != 0; } // Mouse was moved, report relative motion @@ -96,5 +125,52 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width); ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height); - ctx.input.events.mouse_move = true; + ctx.input.events.mouse_move = dx != 0 || dy != 0; +} + +fn void Ctx.input_mouse_wheel(&ctx, short x, short y, float scale = 1.0) +{ + ctx.input.mouse.scroll.x = (short)((float)-x*scale); + ctx.input.mouse.scroll.y = (short)((float)-y*scale); + ctx.input.events.mouse_scroll = x !=0 || y != 0; +} + +// append utf-8 encoded text to the context text input +fn void Ctx.input_text_utf8(&ctx, char[] text) +{ + if (text.len == 0) { return; } + + usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len; + usz len = text.len > remaining ? remaining : text.len; + char[] s = ctx.input.keyboard.text[ctx.input.keyboard.text_len ..]; + s[..len-1] = text[..len-1]; + ctx.input.keyboard.text_len += len; + ctx.input.events.text_input = true; +} + +fn void Ctx.input_text_unicode(&ctx, int[] text) +{ + if (text.len == 0) { return; } + + char[32] tmp; + usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len; + char[] s = ctx.input.keyboard.text[ctx.input.keyboard.text_len ..]; + + usz off; + foreach (idx, cp: text) { + if (off >= remaining) { break; } + usz enc = grapheme::encode_utf8(cp, tmp[..], tmp.len); + s[off..off+enc] = tmp[..enc]; + off += enc; + } + ctx.input.keyboard.text_len += off; + + ctx.input.events.text_input = true; +} + +// Mouse Buttons down +fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys) +{ + ctx.input.keyboard.down = modkeys; + ctx.input.events.mod_key = (uint)ctx.input.keyboard.down != 0; } diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 4641322..a51d952 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -130,5 +130,6 @@ fn ElemEvents! Ctx.slider_ver(&ctx, return elem.events; } -macro short calc_slider(ushort off, ushort dim, float value) => (short)off + (short)(dim * value); -macro float calc_value(ushort off, ushort mouse, ushort dim, ushort slider) => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f); +macro short calc_slider(short off, short dim, float value) => (short)off + (short)(dim * value); +macro float calc_value(short off, short mouse, short dim, short slider) + => math::clamp((float)(mouse-off-slider/2)/(float)(dim-slider), 0.0f, 1.0f); diff --git a/test/test_error.c3 b/test/test_error.c3 new file mode 100644 index 0000000..051c928 --- /dev/null +++ b/test/test_error.c3 @@ -0,0 +1,72 @@ +import std::io; + +struct FaultStack { + usz elem; + anyfault[16] v; +} + +fn void FaultStack.push(&fs, anyfault f) +{ + if (fs.elem < fs.v.len) { + fs.v[fs.elem++] = f; + } +} + +fn anyfault FaultStack.pop(&fs) +{ + return fs.elem > 0 ? fs.v[fs.elem-- - 1] : anyfault{}; +} + +FaultStack fs; + +fn int! err1() +{ + return IoError.OUT_OF_SPACE?; +} + +fn void! err2() +{ + return IoError.EOF?; +} + +/* +macro @unwrap(#f) +{ + $if ($typeof(#f).typeid == void!.typeid) { + if (catch err = #f) { fs.push(err); } + return; + } $else { + $typeof(#f) x = #f; + if (catch err = x) { + fs.push(err); + return $typeof(#f!!){}; + } else {return x;} + } +} +*/ + +<* +@require @typekind(#func) == OPTIONAL : `@unwrap requires an optional value` +*> +macro @unwrap(#func) +{ + anyfault exc = @catch(#func); + if (exc != anyfault{}) { + fs.push(exc); + $if $typeof(#func!!).typeid != void.typeid: + return $typeof(#func!!){}; + $else + return; + $endif + } else { + return #func!!; + } +} + +fn void main() +{ + @unwrap(err1()); + @unwrap(err2()); + + io::printfn("%s", fs.v); +} diff --git a/test/test_keyboard.c3 b/test/test_keyboard.c3 new file mode 100644 index 0000000..c9ab167 --- /dev/null +++ b/test/test_keyboard.c3 @@ -0,0 +1,26 @@ +import rl; +import std::io; + +fn int main(String[] args) +{ + short width = 800; + short height = 450; + rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); + rl::init_window(width, height, "Ugui Test"); + rl::set_target_fps(60); + rl::enable_event_waiting(); + + // Main loop + KeyboardKey k; + while (!rl::window_should_close()) { + do { + k = rl::get_char_pressed(); + io::printfn("%s", k); + } while (k != 0); + } + + rl::close_window(); + + return 0; +} + From 78e2c64da6ca88a497f37523cb18021fc4df69e9 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 26 Dec 2024 22:58:24 +0100 Subject: [PATCH 074/250] implemented adaptive size divs --- TODO | 2 +- src/main.c3 | 4 +- src/ugui_button.c3 | 2 +- src/ugui_core.c3 | 52 +----------- src/ugui_div.c3 | 13 ++- src/ugui_layout.c3 | 125 ++++++++++++++++------------- src/ugui_shapes.c3 | 194 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 279 insertions(+), 113 deletions(-) create mode 100644 src/ugui_shapes.c3 diff --git a/TODO b/TODO index 556b6f5..6252fdc 100644 --- a/TODO +++ b/TODO @@ -22,7 +22,7 @@ to maintain focus until mouse release (fix scroll bars) ## Layout [ ] Text reflow -[ ] Flexbox +[x] Flexbox [ ] Center elements to the row/column ## Input diff --git a/src/main.c3 b/src/main.c3 index be5c2f3..7a88495 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -149,7 +149,7 @@ fn int main(String[] args) ui.frame_begin()!!; // main div, fill the whole window - ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!; + ui.div_begin("main", ugui::Rect{.w=-100})!!; {| ui.layout_set_column()!!; if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { @@ -211,7 +211,7 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", ugui::Rect{0, ui.height-100, 200, 100})!!; + ui.div_begin("fps", ugui::Rect{0, ui.height-100, -300, 100})!!; {| ui.layout_set_column()!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index e605c91..7afd4d1 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -58,7 +58,7 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; Rect text_size = ctx.get_text_bounds(label)!; - Rect btn_size = rect_add(text_size, Rect{0,0,10,10}); + Rect btn_size = text_size.add(Rect{0,0,10,10}); // 2. Layout elem.bounds = ctx.position_element(parent, btn_size, true); diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 09c5348..1d4efd4 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -7,17 +7,6 @@ import fifo; import std::io; import std::core::string; -struct Rect { - short x, y, w, h; -} - -struct Point { - short x, y; -} - -struct Color{ - char r, g, b, a; -} // element ids are just long ints def Id = usz; @@ -144,45 +133,6 @@ struct Ctx { isz active_div; // tree node indicating the current active div } -macro point_in_rect(Point p, Rect r) -{ - return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); -} - -// return true if rect a contains b -macro bool Rect.contains(Rect a, Rect b) -{ - return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); -} - -macro Rect Rect.intersection(Rect a, Rect b) -{ - return Rect{ - .x = (short)max(a.x, b.x), - .y = (short)max(a.y, b.y), - .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), - .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), - }; -} - -// rect intersection not null -macro bool Rect.collides(Rect a, Rect b) -{ - return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); -} - -macro bool Rect.is_null(r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; - -macro Rect rect_add(Rect r1, Rect r2) -{ - return Rect{ - .x = r1.x + r2.x, - .y = r1.y + r2.y, - .w = r1.w + r2.w, - .h = r1.h + r2.h, - }; -} - // return a pointer to the parent of the current active div fn Elem*! Ctx.get_parent(&ctx) { @@ -362,7 +312,7 @@ $endif *> macro bool Ctx.is_hovered(&ctx, Elem *elem) { - return point_in_rect(ctx.input.mouse.pos, elem.bounds); + return ctx.input.mouse.pos.in_rect(elem.bounds); } macro bool Ctx.elem_focus(&ctx, Elem *elem) diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 1d02bdc..3f32e73 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -23,6 +23,11 @@ struct ElemDiv { Point origin_r, origin_c; } +// begin a widget container, or div, the size determines the offset (x,y) width and height. +// if the width or height are zero the width or height are set to the maximum available. +// if the width or height are negative the width or height will be calculated based on the children size +// sort similar to a flexbox, and the minimum size is set by the negative of the width or height +// FIXME: there is a bug if the size.w or size.h == -0 fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) { Id id = ctx.gen_id(label)!; @@ -43,7 +48,13 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo elem.div.scroll_y.enabled = scroll_y; // 2. layout the element - elem.bounds = ctx.position_element(parent, size); + Rect wanted_size = { + .x = size.x, + .y = size.y, + .w = size.w < 0 ? max(elem.div.pcb.w, (short)-size.w) : size.w, + .h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h, + }; + elem.bounds = ctx.position_element(parent, wanted_size); elem.div.children_bounds = elem.bounds; // update the ctx scissor diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 095b41a..01c03bc 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -80,6 +80,26 @@ fn void! Ctx.layout_next_column(&ctx) parent.div.origin_r = parent.div.origin_c; } + +macro Rect Elem.get_view(&elem) +{ + Rect off; + if (elem.div.scroll_x.enabled && elem.div.scroll_x.on) { + off.x = (short)((float)(elem.div.pcb.w - elem.bounds.w) * elem.div.scroll_x.value); + off.w = -SCROLLBAR_DIM; + } + if (elem.div.scroll_y.enabled && elem.div.scroll_y.on) { + off.y = (short)((float)(elem.div.pcb.h - elem.bounds.h) * elem.div.scroll_y.value); + off.h = -SCROLLBAR_DIM; + } + return elem.bounds.add(off); +} + +macro Point Elem.get_view_off(&elem) +{ + return elem.get_view().sub(elem.bounds).position(); +} + // position the rectangle inside the parent according to the layout // parent: parent div // rect: the requested size @@ -90,11 +110,13 @@ fn void! Ctx.layout_next_column(&ctx) *> fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) { - Rect placement; - Point origin; ElemDiv* div = &parent.div; + Rect parent_bounds, parent_view; + Rect child_placement, child_occupied; + // 1. Select the right origin + Point origin; switch (div.layout) { case LAYOUT_ROW: origin = div.origin_r; @@ -107,81 +129,70 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) return Rect{}; } - // the bottom-right border of the element box - Point pl_corner; + // 2. Compute the parent's view + parent_bounds = parent.bounds; + parent_view = parent.get_view(); - // 2. Calculate the placement - placement.x = (short)max(origin.x + rect.x, 0); - placement.y = (short)max(origin.y + rect.y, 0); - placement.w = rect.w > 0 ? rect.w : (short)max(parent.bounds.w - (placement.x - parent.bounds.x), 0); - placement.h = rect.h > 0 ? rect.h : (short)max(parent.bounds.h - (placement.y - parent.bounds.y), 0); + // 3. Compute the placement and occupied area - pl_corner.x = placement.x + placement.w; - pl_corner.y = placement.y + placement.h; + // grow rect (wanted size) when widht or height are less than zero + bool adapt_x = rect.w <= 0; + bool adapt_y = rect.h <= 0; + if (adapt_x) rect.w = parent_bounds.w - parent_bounds.x - origin.x; + if (adapt_y) rect.h = parent_bounds.h - parent_bounds.y - origin.y; - // 2.1 apply style, css box model + // offset placement and area + child_placement = child_placement.off(origin.add(rect.position())); + child_occupied = child_occupied.off(origin.add(rect.position())); if (style) { - Rect margin = ctx.style.margin; - Rect border = ctx.style.border; + Rect margin = ctx.style.margin; + Rect border = ctx.style.border; Rect padding = ctx.style.padding; - placement.x += margin.x; - placement.y += margin.y; - if (rect.w != 0) { placement.w += border.x+border.w + padding.x+padding.w; } - if (rect.h != 0) { placement.h += border.y+border.h + padding.y+padding.h; } - - pl_corner.x = placement.x + placement.w + margin.w; - pl_corner.y = placement.y + placement.h + margin.h; + // padding, grows both the placement and occupied area + child_placement = child_placement.grow(padding.position().add(padding.size())); + child_occupied = child_occupied.grow(padding.position().add(padding.size())); + // border, grows both the placement and occupied area + child_placement = child_placement.grow(border.position().add(border.size())); + child_occupied = child_occupied.grow(border.position().add(border.size())); + // margin, offsets the placement and grows the occupied area + child_placement = child_placement.off(margin.position()); + child_occupied = child_occupied.grow(margin.position().add(margin.size())); + + // oh yeah also adjust the rect if i was to grow + if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w; + if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h; } + // set the size + child_placement = child_placement.grow(rect.size()); + child_occupied = child_occupied.grow(rect.size()); - // 3. Update the origins of the parent + // 4. Update the parent's origin div.origin_r = Point{ - .x = pl_corner.x, + .x = child_occupied.bottom_right().x, .y = origin.y, }; div.origin_c = Point{ .x = origin.x, - .y = pl_corner.y, + .y = child_occupied.bottom_right().y, }; - // 4. Calculate the "scrolled" view - Rect off; - Rect* cb = &div.children_bounds; - if (div.scroll_x.enabled && div.scroll_x.on) { - off.x = (short)((float)(div.pcb.w - parent.bounds.w) * div.scroll_x.value); - off.w = SCROLLBAR_DIM; - } - if (div.scroll_y.enabled && div.scroll_y.on) { - off.y = (short)((float)(div.pcb.h - parent.bounds.h) * div.scroll_y.value); - off.h = SCROLLBAR_DIM; - } - Rect view = { - .x = parent.bounds.x + off.x, - .y = parent.bounds.y + off.y, - .w = parent.bounds.w - off.w, - .h = parent.bounds.h - off.h, - }; - - // 5. check if the placement overflows the children ounds, if so update them - if (!point_in_rect(pl_corner, *cb)) { - if (pl_corner.y > cb.y+cb.h) { - cb.h = pl_corner.y - cb.y; + // 5. Update the parent's children bounds + if (!child_occupied.bottom_right().in_rect(div.children_bounds)) { + // right overflow + if (child_occupied.bottom_right().x > div.children_bounds.bottom_right().x) { + div.children_bounds.w += child_occupied.bottom_right().x - div.children_bounds.bottom_right().x; } - if (pl_corner.x > cb.x+cb.w) { - cb.w += pl_corner.x - (cb.x + cb.w); + // left overflow + if (child_occupied.bottom_right().y > div.children_bounds.bottom_right().y) { + div.children_bounds.h += child_occupied.bottom_right().y - div.children_bounds.bottom_right().y; } } - // 6. check if the placement is inside the view - if (placement.collides(view)) { - return Rect{ - .x = placement.x - off.x, - .y = placement.y - off.y, - .w = placement.w, - .h = placement.h, - }; + // 99. return the placement + if (child_placement.collides(parent_view)) { + return child_placement.off(parent.get_view_off().neg()); } else { return Rect{}; } } - diff --git a/src/ugui_shapes.c3 b/src/ugui_shapes.c3 new file mode 100644 index 0000000..4b45377 --- /dev/null +++ b/src/ugui_shapes.c3 @@ -0,0 +1,194 @@ +module ugui; + + +// ---------------------------------------------------------------------------------- // +// RECTANGLE // +// ---------------------------------------------------------------------------------- // + +// Rect and it's methods +struct Rect { + short x, y, w, h; +} + +// return true if rect a contains b +macro bool Rect.contains(Rect a, Rect b) +{ + return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h); +} + +// returns the intersection of a and b +macro Rect Rect.intersection(Rect a, Rect b) +{ + return Rect{ + .x = (short)max(a.x, b.x), + .y = (short)max(a.y, b.y), + .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), + .h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y), + }; +} + +// returns true if the intersection not null +macro bool Rect.collides(Rect a, Rect b) +{ + return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y); +} + +// check for empty rect +macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; + +// returns the element-wise addition of r1 and r2 +macro Rect Rect.add(Rect r1, Rect r2) +{ + return Rect{ + .x = r1.x + r2.x, + .y = r1.y + r2.y, + .w = r1.w + r2.w, + .h = r1.h + r2.h, + }; +} + +// returns the element-wise subtraction of r1 and r2 +macro Rect Rect.sub(Rect r1, Rect r2) +{ + return Rect{ + .x = r1.x - r2.x, + .y = r1.y - r2.y, + .w = r1.w - r2.w, + .h = r1.h - r2.h, + }; +} + +// returns the element-wise multiplication of r1 and r2 +macro Rect Rect.mul(Rect r1, Rect r2) +{ + return Rect{ + .x = r1.x * r2.x, + .y = r1.y * r2.y, + .w = r1.w * r2.w, + .h = r1.h * r2.h, + }; +} + +macro Point Rect.position(Rect r) +{ + return Point{ + .x = r.x, + .y = r.y, + }; +} + +macro Point Rect.size(Rect r) +{ + return Point{ + .x = r.w, + .y = r.h, + }; +} + +macro Rect Rect.max(Rect a, Rect b) +{ + return Rect{ + .x = max(a.x, b.x), + .y = max(a.y, b.y), + .w = max(a.w, b.w), + .h = max(a.h, b.h), + }; +} + +macro Rect Rect.min(Rect a, Rect b) +{ + return Rect{ + .x = min(a.x, b.x), + .y = min(a.y, b.y), + .w = min(a.w, b.w), + .h = min(a.h, b.h), + }; +} + +// Offset a rect by a point +macro Rect Rect.off(Rect r, Point p) +{ + return Rect{ + .x = r.x + p.x, + .y = r.y + p.y, + .w = r.w, + .h = r.h, + }; +} + +// Resize a rect width and height +macro Rect Rect.grow(Rect r, Point p) +{ + return Rect{ + .x = r.x, + .y = r.y, + .w = r.w + p.x, + .h = r.h + p.y, + }; +} + +// Return the bottom-right corner of a rectangle +macro Point Rect.bottom_right(Rect r) +{ + return Point{ + .x = r.x + r.w, + .y = r.y + r.h, + }; +} + + +// ---------------------------------------------------------------------------------- // +// POINT // +// ---------------------------------------------------------------------------------- // + +struct Point { + short x, y; +} + +// returns true if a point is inside the rectangle +macro bool Point.in_rect(Point p, Rect r) +{ + return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h); +} + +macro Point Point.add(Point a, Point b) +{ + return Point{ + .x = a.x + b.x, + .y = a.y + b.y, + }; +} + +macro Point Point.sub(Point a, Point b) +{ + return Point{ + .x = a.x - b.x, + .y = a.y - b.y, + }; +} + +macro Point Point.neg(Point p) => Point{-p.x, -p.y}; + +macro Point Point.max(Point a, Point b) +{ + return Point{ + .x = max(a.x, b.x), + .y = max(a.y, b.y), + }; +} + +macro Point Point.min(Point a, Point b) +{ + return Point{ + .x = min(a.x, b.x), + .y = min(a.y, b.y), + }; +} + +// ---------------------------------------------------------------------------------- // +// COLOR // +// ---------------------------------------------------------------------------------- // + +struct Color{ + char r, g, b, a; +} From 9aa0d58d680218f360a6baf9fe382c2fe54497cb Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 28 Dec 2024 16:59:12 +0100 Subject: [PATCH 075/250] better to_rgba() macro --- src/ugui_button.c3 | 12 ++++++------ src/ugui_cmd.c3 | 4 ++-- src/ugui_core.c3 | 17 ++++------------- src/ugui_shapes.c3 | 10 ++++++++++ src/ugui_slider.c3 | 8 ++++---- test/test_color.c3 | 19 +++++++++++++++++++ 6 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 test/test_color.c3 diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index 7afd4d1..f18b417 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -29,12 +29,12 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) // no interaction should occur so just return if (elem.bounds.is_null()) { return ElemEvents{}; } - Color col = uint_to_rgba(0x0000ffff); + Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); if (state) { - col = uint_to_rgba(0xff0000ff); + col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { - col = uint_to_rgba(0xff00ffff); + col = 0xff00ffffu.to_rgba(); } // Draw the button @@ -64,12 +64,12 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m elem.bounds = ctx.position_element(parent, btn_size, true); if (elem.bounds.is_null()) { return ElemEvents{}; } - Color col = uint_to_rgba(0x0000ffff); + Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); if (state) { - col = uint_to_rgba(0xff0000ff); + col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { - col = uint_to_rgba(0xff00ffff); + col = 0xff00ffffu.to_rgba(); } // Draw the button diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 36b970b..9ca04da 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -103,7 +103,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo } // TODO: add texture id -fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = uint_to_rgba(0xffffffff)) +fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba()) { Cmd cmd = { .type = CMD_SPRITE, @@ -115,7 +115,7 @@ fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color h ctx.push_cmd(&cmd)!; } -fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = uint_to_rgba(0xffffffff)) +fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba()) { if (text.len == 0) { return; diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 1d4efd4..52f9fb4 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -67,15 +67,6 @@ fault UgError { WRONG_ELEMENT_TYPE, } -macro Color uint_to_rgba(uint $u) { - return Color{ - .r = (char)(($u >> 24) & 0xff), - .g = (char)(($u >> 16) & 0xff), - .b = (char)(($u >> 8) & 0xff), - .a = (char)(($u >> 0) & 0xff) - }; -} - const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; const uint STACK_STEP = 10; @@ -218,9 +209,9 @@ fn void! Ctx.init(&ctx) ctx.style.border = Rect{2, 2, 2, 2}; ctx.style.padding = Rect{1, 1, 1, 1}; ctx.style.radius = 5; - ctx.style.bgcolor = uint_to_rgba(0x282828ff); - ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff); - ctx.style.brcolor = uint_to_rgba(0xd79921ff); + ctx.style.bgcolor = 0x282828ffu.to_rgba(); + ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba(); + ctx.style.brcolor = 0xd79921ffu.to_rgba(); } fn void Ctx.free(&ctx) @@ -301,7 +292,7 @@ $if 1: .w = 4, .h = 4, }, - .rect.color = uint_to_rgba(0xff00ffff) + .rect.color = 0xff00ffffu.to_rgba() }; ctx.cmd_queue.enqueue(&cmd)!; $endif diff --git a/src/ugui_shapes.c3 b/src/ugui_shapes.c3 index 4b45377..39a782c 100644 --- a/src/ugui_shapes.c3 +++ b/src/ugui_shapes.c3 @@ -192,3 +192,13 @@ macro Point Point.min(Point a, Point b) struct Color{ char r, g, b, a; } + +macro Color uint.to_rgba(uint u) +{ + return Color{ + .r = (char)((u >> 24) & 0xff), + .g = (char)((u >> 16) & 0xff), + .b = (char)((u >> 8) & 0xff), + .a = (char)((u >> 0) & 0xff) + }; +} diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index a51d952..1d76376 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -21,8 +21,8 @@ fn ElemEvents! Ctx.slider_hor(&ctx, Rect size, float* value, float hpercent = 0.25, - Color bgcolor = uint_to_rgba(0x0000ffff), - Color handlecolor = uint_to_rgba(0x0ff000ff)) + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba()) { Id id = ctx.gen_id(label)!; @@ -84,8 +84,8 @@ fn ElemEvents! Ctx.slider_ver(&ctx, Rect size, float* value, float hpercent = 0.25, - Color bgcolor = uint_to_rgba(0x0000ffff), - Color handlecolor = uint_to_rgba(0x0ff000ff)) + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba()) { Id id = ctx.gen_id(label)!; diff --git a/test/test_color.c3 b/test/test_color.c3 new file mode 100644 index 0000000..08a3245 --- /dev/null +++ b/test/test_color.c3 @@ -0,0 +1,19 @@ +import std::io; + +struct Color { short r,g,b,a; } + +macro Color uint.to_rgba(uint u) +{ + return Color{ + .r = (char)((u >> 24) & 0xff), + .g = (char)((u >> 16) & 0xff), + .b = (char)((u >> 8) & 0xff), + .a = (char)((u >> 0) & 0xff) + }; +} + +fn void main(String[] args) +{ + uint col = args[1].to_uint()!!; + io::printn(col.to_rgba()); +} From b317951c321051a456215cf202be586ffea33c38 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 29 Jan 2025 01:10:18 +0100 Subject: [PATCH 076/250] first working prototype of sprite drawing --- TODO | 3 +- lib/libmqoi.c3l/Makefile | 6 ++ lib/libmqoi.c3l/libmqoi.c3i | 122 ++++++++++++++++++++++++++ lib/libmqoi.c3l/manifest.json | 9 ++ lib/libmqoi.c3l/project.json | 31 +++++++ lib/libmqoi.c3l/thirdparty/mini-qoi | 1 + project.json | 16 ++-- resources/tux.qoi | Bin 0 -> 4092 bytes src/main.c3 | 70 ++++++++++----- src/ugui_atlas.c3 | 25 +++++- src/ugui_core.c3 | 6 ++ src/ugui_font.c3 | 3 +- src/ugui_sprite.c3 | 130 ++++++++++++++++++++++++++++ 13 files changed, 386 insertions(+), 36 deletions(-) create mode 100644 lib/libmqoi.c3l/Makefile create mode 100644 lib/libmqoi.c3l/libmqoi.c3i create mode 100644 lib/libmqoi.c3l/manifest.json create mode 100644 lib/libmqoi.c3l/project.json create mode 160000 lib/libmqoi.c3l/thirdparty/mini-qoi create mode 100644 resources/tux.qoi create mode 100644 src/ugui_sprite.c3 diff --git a/TODO b/TODO index 6252fdc..bf076ae 100644 --- a/TODO +++ b/TODO @@ -18,6 +18,8 @@ to maintain focus until mouse release (fix scroll bars) [ ] Animations, somehow [ ] Maybe cache codepoint converted strings [x] Fix scroll wheel when div is scrolled +[ ] Be consistent with the initialization methods some are foo.new() and some are foo.init() +[ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg ## Layout @@ -63,4 +65,3 @@ _ border radius [ ] Text Input box [ ] Icon Buttons [ ] Switch - diff --git a/lib/libmqoi.c3l/Makefile b/lib/libmqoi.c3l/Makefile new file mode 100644 index 0000000..53230c5 --- /dev/null +++ b/lib/libmqoi.c3l/Makefile @@ -0,0 +1,6 @@ +all: thirdparty/mini-qoi/mqoi.a + cp thirdparty/mini-qoi/mqoi.a linux-x64/libmqoi.a + +thirdparty/mini-qoi/mqoi.a: + gcc -O2 -c -o thirdparty/mini-qoi/mqoi.o thirdparty/mini-qoi/src/mini_qoi.c + ar -rc thirdparty/mini-qoi/mqoi.a thirdparty/mini-qoi/mqoi.o diff --git a/lib/libmqoi.c3l/libmqoi.c3i b/lib/libmqoi.c3l/libmqoi.c3i new file mode 100644 index 0000000..330145d --- /dev/null +++ b/lib/libmqoi.c3l/libmqoi.c3i @@ -0,0 +1,122 @@ +module mqoi; + +macro rgb_hash(Rgb px) => ((px.r * 3 + px.g * 5 + px.b * 7) & 0b00111111); +macro mqoi_rgba_hash(Rgba px) => ((px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) & 0b00111111); + +const uint MQOI_HEADER_SIZE = 14; + +const char MQOI_MASK_OP_2B = 0b11000000; +const char MQOI_MASK_OP_8B = 0b11111111; + +const char MQOI_MASK_OP_LUMA_DG = 0b00111111; +const char MQOI_MASK_OP_RUN = 0b00111111; + +// basic types + +enum DescErr : uint (uint value) { + MQOI_DESC_OK = 0, // The descriptor is valid + MQOI_DESC_INVALID_MAGIC = 1, // The magic value isn't correct + MQOI_DESC_INVALID_CHANNELS = 2, // The channel number isn't valid + MQOI_DESC_INVALID_COLORSPACE = 3, // The colorspace isn't valid +} + +enum Op : char (char value) { + MQOI_OP2_INDEX = (0b00 << 6), + MQOI_OP2_DIFF = (0b01 << 6), + MQOI_OP2_LUMA = (0b10 << 6), + MQOI_OP2_RUN = (0b11 << 6), + MQOI_OP8_RUN_RGB = (0b11111110), + MQOI_OP8_RUN_RGBA = (0b11111111), +} + +enum Channels : char (char value) { + MQOI_CHANNELS_RGB = 3, + MQOI_CHANNELS_RGBA = 4, +} + +enum Colorspace : char (char value) { + MQOI_COLORSPACE_SRGB = 0, + MQOI_COLORSPACE_LINEAR = 1, +} + +union Rgb { + struct { char r, g, b; } + char[3] value; +} + +union Rgba{ + struct { char r, g, b, a; } + char[4] value; +} + +struct Desc { + char head; + + char[4] magic; + char[4] width; // big-endian width + char[4] height; // big-endian height + char channels; + char colorspace; +} + +// ==== chunks ==== + +union Chunk { + struct { + char head; + union { + Rgb rgb; + Rgba rgba; + char drdb; + } + } + char[5] value; +} + +// ==== codecs ==== + +struct Encoding { + Rgba[64] hashtable; + Rgba prev_px; + Chunk working_chunk; + char working_chunk_size; +} + +struct Dec { + Rgba[64] hashtable; + Rgba prev_px; + Chunk curr_chunk; + bitstruct : char { + char curr_chunk_head : 0..3; + char curr_chunk_size : 4..7; + } + uint pix_left; +} + +// ==== utilities ==== + +fn void u32_write(uint * n, char * dest) @extern("mqoi_u32_write"); +fn void u32_read(char * src, uint * n) @extern("mqoi_u32_read"); + +// ==== Desc ==== + +fn void desc_init(Desc* desc) @extern("mqoi_desc_init"); +fn void desc_push(Desc* desc, char byte) @extern("mqoi_desc_push"); +fn char* desc_pop(Desc* desc) @extern("mqoi_desc_pop"); +fn char desc_verify(Desc* desc, uint* w, uint* h) @extern("mqoi_desc_verify"); +fn bool desc_done(Desc* desc) @extern("mqoi_desc_done"); + +/* the encoder is still WIP +void mqoi_enc_init(mqoi_enc_t * enc); +void mqoi_enc_push(mqoi_enc_t * enc, Rgba * pix) +Chunk * mqoi_enc_pop(mqoi_enc_t * enc, char * size); +*/ + +// ==== Dec ==== + +fn void dec_init(Dec* dec, uint n_pix) @extern("mqoi_dec_init"); +fn void dec_push(Dec* dec, char byte) @extern("mqoi_dec_push"); +fn char dec_take(Dec* dec, char* bytes) @extern("mqoi_dec_take"); +fn Rgba* dec_pop(Dec* dec) @extern("mqoi_dec_pop"); +fn bool dec_done(Dec* dec) @extern("mqoi_dec_done"); + diff --git a/lib/libmqoi.c3l/manifest.json b/lib/libmqoi.c3l/manifest.json new file mode 100644 index 0000000..4043957 --- /dev/null +++ b/lib/libmqoi.c3l/manifest.json @@ -0,0 +1,9 @@ +{ + "provides" : "mqoi", + "targets" : { + "linux-x64" : { + "dependencies" : [], + "linked-libraries" : ["mqoi", "c"] + } + } +} diff --git a/lib/libmqoi.c3l/project.json b/lib/libmqoi.c3l/project.json new file mode 100644 index 0000000..78ab828 --- /dev/null +++ b/lib/libmqoi.c3l/project.json @@ -0,0 +1,31 @@ +{ + // Language version of C3. + "langrev": "1", + // Warnings used for all targets. + "warnings": [ "no-unused" ], + // Directories where C3 library files may be found. + "dependency-search-paths": [ ".." ], + // Libraries to use for all targets. + "dependencies": [ "mqoi" ], + // Authors, optionally with email. + "authors": [ "Alessandro Mauri " ], + "authors": ["Alessandro Mauri "], // Version using semantic versioning. "version": "0.1.0", // Sources compiled for all targets. - "sources": [ "src/**" ], + "sources": ["src/**"], // C sources if the project also compiles C sources // relative to the project file. // "c-sources": [ "csource/**" ], @@ -41,15 +41,15 @@ "targets": { "ugui": { // Executable or library. - "type": "executable", + "type": "executable" // Additional libraries, sources // and overrides of global settings here. - }, + } }, // Global settings. // CPU name, used for optimizations in the LLVM backend. "cpu": "generic", // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". - "opt": "O0", + "opt": "O0" // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. } diff --git a/resources/tux.qoi b/resources/tux.qoi new file mode 100644 index 0000000000000000000000000000000000000000..57d6333e7db058c1037e408d0b58459c73dc8b2c GIT binary patch literal 4092 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 literal 0 HcmV?d00001 diff --git a/src/main.c3 b/src/main.c3 index 7a88495..a5cad6f 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -81,6 +81,8 @@ fn int main(String[] args) ugui::Ctx ui; ui.init()!!; ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; + ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; + ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; short width = 800; short height = 450; @@ -99,8 +101,11 @@ fn int main(String[] args) // font stuff rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); rl::Image font_atlas; + rl::Image sprite_atlas; rl::Texture2D font_texture; + rl::Texture2D sprite_texture; ugui::Id font_id = ui.get_font_id("font1"); + ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); // Main loop while (!rl::window_should_close()) { @@ -164,7 +169,7 @@ fn int main(String[] args) if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } - + ui.layout_set_row()!!; ui.layout_next_row()!!; static float rf, gf, bf, af; @@ -179,6 +184,7 @@ fn int main(String[] args) ui.layout_next_column()!!; ui.button_label("Continua!")!!; |}; + ui.draw_sprite("tux")!!; ui.div_end()!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; @@ -205,7 +211,7 @@ fn int main(String[] args) ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!; |}; ui.div_end()!!; - + // Timings counter TimeStats dts = draw_times.get_stats(); TimeStats uts = ui_times.get_stats(); @@ -237,27 +243,48 @@ fn int main(String[] args) float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); case ugui::CmdType.CMD_UPDATE_ATLAS: - if (cmd.update_atlas.id != font_id) { break; } - //rl::unload_image(font_atlas); - font_atlas.data = cmd.update_atlas.raw_buffer; - font_atlas.width = cmd.update_atlas.width; - font_atlas.height = cmd.update_atlas.height; - font_atlas.mipmaps = 1; - //font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; - font_atlas.format = 1; - if (rl::is_texture_ready(font_texture)) { - rl::unload_texture(font_texture); + if (cmd.update_atlas.id == font_id) { + //rl::unload_image(font_atlas); + font_atlas.data = cmd.update_atlas.raw_buffer; + font_atlas.width = cmd.update_atlas.width; + font_atlas.height = cmd.update_atlas.height; + font_atlas.mipmaps = 1; + //font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; + font_atlas.format = 1; + if (rl::is_texture_ready(font_texture)) { + rl::unload_texture(font_texture); + } + font_texture = rl::load_texture_from_image(font_atlas); + } else if (cmd.update_atlas.id == sprite_id) { + sprite_atlas.data = cmd.update_atlas.raw_buffer; + sprite_atlas.width = cmd.update_atlas.width; + sprite_atlas.height = cmd.update_atlas.height; + sprite_atlas.mipmaps = 1; + //sprite_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; + sprite_atlas.format = 7; + if (rl::is_texture_ready(sprite_texture)) { + rl::unload_texture(sprite_texture); + } + sprite_texture = rl::load_texture_from_image(sprite_atlas); } - font_texture = rl::load_texture_from_image(font_atlas); case ugui::CmdType.CMD_SPRITE: - if (cmd.sprite.texture_id != font_id) { break; } - rl::Vector2 position = { - .x = cmd.sprite.rect.x, - .y = cmd.sprite.rect.y, - }; - rl::begin_shader_mode(font_shader); - rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); - rl::end_shader_mode(); + if (cmd.sprite.texture_id == font_id) { + rl::Vector2 position = { + .x = cmd.sprite.rect.x, + .y = cmd.sprite.rect.y, + }; + rl::begin_shader_mode(font_shader); + rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + rl::end_shader_mode(); + } else if (cmd.sprite.texture_id == sprite_id) { + rl::Vector2 position = { + .x = cmd.sprite.rect.x, + .y = cmd.sprite.rect.y, + }; + rl::draw_texture_rec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + } else { + io::printfn("unknown texture id: %d", cmd.sprite.texture_id); + } case ugui::CmdType.CMD_SCISSOR: if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { rl::end_scissor_mode(); @@ -272,7 +299,6 @@ fn int main(String[] args) //draw_times.print_stats(); rl::end_drawing(); /* End Drawing */ - } rl::close_window(); diff --git a/src/ugui_atlas.c3 b/src/ugui_atlas.c3 index 0f04c08..1976ec4 100644 --- a/src/ugui_atlas.c3 +++ b/src/ugui_atlas.c3 @@ -9,6 +9,7 @@ fault UgAtlasError { enum AtlasType { ATLAS_GRAYSCALE, + ATLAS_RGBA32, } // black and white atlas @@ -28,6 +29,7 @@ macro usz AtlasType.bpp(type) { switch (type) { case ATLAS_GRAYSCALE: return 1; + case ATLAS_RGBA32: return 4; } } @@ -46,10 +48,25 @@ fn void Atlas.free(&atlas) free(atlas.buffer); } + +/* + * pixels -> +--------------+-----+ + * | | | h + * | | | e + * | | | i + * | | | g + * | | | h + * | | | t + * +--------------+-----+ + * |<--- width -->| + * |<----- stride ----->| + * bytes per pixels are inferred and have to be the same + * as the atlas type + */ // place a rect inside the atlas // uses a row first algorithm // TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ -fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h) +fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride) { Point p; @@ -66,11 +83,11 @@ fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h) } } + usz bpp = atlas.type.bpp(); for (usz y = 0; y < h; y++) { for (usz x = 0; x < w; x++) { - char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x) ..]; - char[] pix = pixels[y*w + x ..]; - usz bpp = atlas.type.bpp(); + char[] buf = atlas.buffer[(usz)(p.y+y)*atlas.width*bpp + (p.x+x)*bpp ..]; + char[] pix = pixels[(usz)y*stride*bpp + x*bpp ..]; buf[0..bpp-1] = pix[0..bpp-1]; } diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 52f9fb4..e2b9a0e 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -95,6 +95,7 @@ struct Ctx { ushort width, height; Style style; Font font; + SpriteAtlas sprite_atlas; bool has_focus; struct input { @@ -220,6 +221,7 @@ fn void Ctx.free(&ctx) (void)ctx.cache.free(); (void)ctx.cmd_queue.free(); (void)ctx.font.free(); + (void)ctx.sprite_atlas.free(); } fn void! Ctx.frame_begin(&ctx) @@ -281,6 +283,10 @@ fn void! Ctx.frame_end(&ctx) ctx.push_update_atlas(&ctx.font.atlas)!; ctx.font.should_update = false; } + if (ctx.sprite_atlas.should_update) { + ctx.push_update_atlas(&ctx.sprite_atlas.atlas)!; + ctx.sprite_atlas.should_update = false; + } $if 1: // draw mouse position diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 189fdef..546c7b9 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -144,7 +144,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code) //io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d", // glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv); - Point uv = font.atlas.place(pixels, glyph.w, glyph.h)!; + Point uv = font.atlas.place(pixels, glyph.w, glyph.h, (ushort)img.width)!; glyph.u = uv.x; glyph.v = uv.y; @@ -159,6 +159,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code) fn void Font.free(&font) { font.atlas.free(); + font.table.free(); schrift::freefont(font.sft.font); } diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 new file mode 100644 index 0000000..aff0649 --- /dev/null +++ b/src/ugui_sprite.c3 @@ -0,0 +1,130 @@ +module ugui; + +import std::collections::map; +import std::io; +import mqoi; + +const usz SRITES_PER_ATLAS = 64; + +struct Sprite { + Id id; + ushort u, v; + ushort w, h; +} + +def SpriteMap = map::HashMap(); + +struct SpriteAtlas { + Id id; + Atlas atlas; + SpriteMap sprites; + bool should_update; +} + + +// name: some examples are "icons" or "images" +fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) +{ + // FIXME: for now only RGBA32 format is supported + if (type != ATLAS_RGBA32) { + return UgAtlasError.INVALID_TYPE?; + } + + this.id = name.hash(); + this.atlas.new(this.id, AtlasType.ATLAS_RGBA32, width, height)!; + this.sprites.new_init(capacity: SRITES_PER_ATLAS); + this.should_update = false; +} + +fn void! SpriteAtlas.free(&this) +{ + this.atlas.free(); + this.sprites.free(); +} + +// FIXME: this should throw an error when a different pixel format than the atlas' is used +fn Sprite*! SpriteAtlas.insert(&this, String name, char[] pixels, ushort w, ushort h, ushort stride) +{ + Sprite s; + s.id = name.hash(); + Point uv = this.atlas.place(pixels, w, h, stride)!; + s.w = w; + s.h = h; + s.u = uv.x; + s.v = uv.y; + this.sprites.set(s.id, s); + this.should_update = true; + return this.sprites.get_ref(s.id); +} + +fn Sprite*! SpriteAtlas.get(&this, String name) +{ + Id id = name.hash(); + return this.sprites.get_ref(id); +} + +fn Sprite*! SpriteAtlas.get_by_id(&this, Id id) +{ + return this.sprites.get_ref(id); +} + +fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h) +{ + ctx.sprite_atlas.init(name, type, w, h)!; +} + +fn Id Ctx.get_sprite_atlas_id(&ctx, String name) +{ + return name.hash(); +} + +fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride) +{ + ctx.sprite_atlas.insert(name, pixels, w, h, stride)!; +} + +fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) +{ + mqoi::Desc image_desc; + uint w, h; + + File file = file::open(path, "rb")!; + defer (void) file.close(); + + while (!mqoi::desc_done(&image_desc)) { + mqoi::desc_push(&image_desc, file.read_byte()!); + } + if (mqoi::desc_verify(&image_desc, &w, &h) != 0) { + return IoError.FILE_NOT_VALID?; + } + + mqoi::Dec dec; + mqoi::Rgba* px; + + usz idx; + char[] pixels = mem::new_array(char, (usz)w*h*4); + defer mem::free(pixels); + + mqoi::dec_init(&dec, w*h); + while (!mqoi::dec_done(&dec)) { + mqoi::dec_push(&dec, file.read_byte()!); + + while ((px = mqoi::dec_pop(&dec)) != null) { + pixels[idx..idx+3] = px.value; + idx += 4; + } + } + + ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; +} + +// FIXME: test function, very different from every other function here +fn void! Ctx.draw_sprite(&ctx, String name) +{ + Sprite* sprite = ctx.sprite_atlas.get(name)!; + Rect bounds = { 100, 100, sprite.w, sprite.h }; + Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; + Id tex_id = ctx.sprite_atlas.id; + + return ctx.push_sprite(bounds, uv, tex_id)!; +} From fbe631b4b420859875e7cd9bed97ec13d136ca74 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 30 Jan 2025 18:36:47 +0100 Subject: [PATCH 077/250] better sprites --- src/main.c3 | 16 ++++++++++++---- src/ugui_core.c3 | 7 ++++--- src/ugui_input.c3 | 16 +++++++++++++++- src/ugui_sprite.c3 | 32 ++++++++++++++++++++++++++++---- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index a5cad6f..cdd65cb 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -80,6 +80,8 @@ fn int main(String[] args) { ugui::Ctx ui; ui.init()!!; + defer ui.free(); + ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; @@ -122,7 +124,12 @@ fn int main(String[] args) io::printfn("%s", k); } while (k != 0); */ - for (int c; (c = rl::get_char_pressed()) != 0;) { + ugui::ModKeys mod; + mod.rctrl = rl::is_key_pressed(rl::KEY_RIGHT_CONTROL); + mod.lctrl = rl::is_key_pressed(rl::KEY_LEFT_CONTROL); + ui.input_mod_keys(mod); + + for (int c; (c = rl::get_key_pressed()) != 0;) { int[1] ts; ts[0] = c; ui.input_text_unicode(ts[..]); @@ -153,6 +160,8 @@ fn int main(String[] args) /* Start UI Handling */ ui.frame_begin()!!; + if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; + // main div, fill the whole window ui.div_begin("main", ugui::Rect{.w=-100})!!; {| @@ -179,12 +188,12 @@ fn int main(String[] args) ui.slider_ver("slider_a", ugui::Rect{0,0,30,100}, &af)!!; ui.layout_next_column()!!; - ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!; + ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; ui.layout_next_column()!!; ui.button_label("Continua!")!!; |}; - ui.draw_sprite("tux")!!; + ui.draw_sprite("sprite1", "tux")!!; ui.div_end()!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; @@ -303,7 +312,6 @@ fn int main(String[] args) rl::close_window(); - ui.free(); return 0; } diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index e2b9a0e..90d0fbe 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -17,6 +17,7 @@ enum ElemType { ETYPE_BUTTON, ETYPE_SLIDER, ETYPE_TEXT, + ETYPE_SPRITE, } bitstruct ElemFlags : uint { @@ -47,6 +48,7 @@ struct Elem { ElemButton button; ElemSlider slider; ElemText text; + ElemSprite sprite; } } @@ -324,7 +326,7 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) { bool hover = ctx.is_hovered(elem); bool focus = ctx.focus_id == elem.id || (hover && ctx.is_mouse_pressed(BTN_LEFT)); - + if (ctx.is_mouse_pressed(BTN_ANY) && !hover){ focus = false; if (ctx.focus_id == elem.id) ctx.focus_id = 0; @@ -332,7 +334,7 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) if (hover) { ctx.hover_id = elem.id; } if (focus) { ctx.focus_id = elem.id; } - + ElemEvents ev = { .mouse_hover = hover, .mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY), @@ -341,4 +343,3 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) }; return ev; } - diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index b01d383..fdfa9e3 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -3,7 +3,7 @@ module ugui; import grapheme; import std::io; import std::math; - +import std::core::string; bitstruct InputEvents : uint { bool resize : 0; // window size was changed @@ -58,6 +58,20 @@ const MouseButtons BTN_5 = {.btn_5 = true}; const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max); +fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String key) +{ + bool is_mod = (bool)(ctx.input.keyboard.down & mod); + bool is_keys = true; + String haystack = (String)ctx.input.keyboard.text[0..ctx.input.keyboard.text_len]; + char[2] needle; + foreach (c: key) { + needle[0] = c; + is_keys = is_keys && haystack.contains((String)needle[..]); + } + io::printfn("%b %b", is_mod, is_keys); + return is_mod && is_keys; +} + // Window size was changed fn void! Ctx.input_window_size(&ctx, short width, short height) { diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 index aff0649..ce4cabb 100644 --- a/src/ugui_sprite.c3 +++ b/src/ugui_sprite.c3 @@ -21,6 +21,9 @@ struct SpriteAtlas { bool should_update; } +struct ElemSprite { + Id id; +} // name: some examples are "icons" or "images" fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) @@ -118,13 +121,34 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; } -// FIXME: test function, very different from every other function here -fn void! Ctx.draw_sprite(&ctx, String name) +fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) { + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + if (elem.flags.is_new) { + elem.type = ETYPE_SPRITE; + } else if (elem.type != ETYPE_SPRITE) { + return UgError.WRONG_ELEMENT_TYPE?; + } + Sprite* sprite = ctx.sprite_atlas.get(name)!; - Rect bounds = { 100, 100, sprite.w, sprite.h }; + Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; + Rect bounds = { 0, 0, sprite.w, sprite.h }; + + elem.bounds = ctx.position_element(parent, bounds.off(off), true); + elem.sprite.id = ctx.get_sprite_atlas_id(name); + + // if the bounds are null the element is outside the div view, + // no interaction should occur so just return + if (elem.bounds.is_null()) return; + Id tex_id = ctx.sprite_atlas.id; - return ctx.push_sprite(bounds, uv, tex_id)!; + return ctx.push_sprite(elem.bounds, uv, tex_id)!; } From 07857fcd4462d7db2a66da0e798378bc7da4c909 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 30 Jan 2025 19:38:53 +0100 Subject: [PATCH 078/250] switch to c3s' vendor raylib 5.5 --- TODO | 9 +++++ lib/raylib.c3l | 1 - project.json | 4 +-- src/main.c3 | 84 +++++++++++++++++++++++------------------------ src/ugui_input.c3 | 9 +++-- 5 files changed, 57 insertions(+), 50 deletions(-) delete mode 160000 lib/raylib.c3l diff --git a/TODO b/TODO index bf076ae..d62e1de 100644 --- a/TODO +++ b/TODO @@ -20,6 +20,14 @@ to maintain focus until mouse release (fix scroll bars) [x] Fix scroll wheel when div is scrolled [ ] Be consistent with the initialization methods some are foo.new() and some are foo.init() [ ] Implement image loading (.bmp, .ff, .qoi and .png), in the future even lossy images like .jpg + [x] .qoi + [ ] .ff + [ ] .bmp + [ ] .png + [ ] .jpg +[ ] gif support? +[ ] layout_set_max_rows() and layout_set_max_columns() +[ ] Maybe SDF sprites?? ## Layout @@ -65,3 +73,4 @@ _ border radius [ ] Text Input box [ ] Icon Buttons [ ] Switch +[ ] Checkbox diff --git a/lib/raylib.c3l b/lib/raylib.c3l deleted file mode 160000 index c7ebe05..0000000 --- a/lib/raylib.c3l +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e diff --git a/project.json b/project.json index 0101175..c777c37 100644 --- a/project.json +++ b/project.json @@ -4,9 +4,9 @@ // Warnings used for all targets. "warnings": ["no-unused"], // Directories where C3 library files may be found. - "dependency-search-paths": ["lib"], + "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], // Libraries to use for all targets. - "dependencies": ["raylib", "schrift", "grapheme", "mqoi"], + "dependencies": ["raylib55", "schrift", "grapheme", "mqoi"], "features": [ // See rcore.c3 //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", diff --git a/src/main.c3 b/src/main.c3 index cdd65cb..5f4873e 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -2,7 +2,7 @@ import std::io; import vtree; import cache; import ugui; -import rl; +import raylib5::rl; import std::time; import std::collections::ringbuffer; import std::core::string; @@ -88,11 +88,11 @@ fn int main(String[] args) short width = 800; short height = 450; - rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE); - rl::init_window(width, height, "Ugui Test"); + rl::setConfigFlags(rl::FLAG_WINDOW_RESIZABLE); + rl::initWindow(width, height, "Ugui Test"); ui.input_window_size(width, height)!!; - rl::set_target_fps(60); - rl::enable_event_waiting(); + rl::setTargetFPS(60); + rl::enableEventWaiting(); isz frame; bool toggle = true; @@ -101,7 +101,7 @@ fn int main(String[] args) Times draw_times; // font stuff - rl::Shader font_shader = rl::load_shader_from_memory(null, FONT_FS); + rl::Shader font_shader = rl::loadShaderFromMemory(null, FONT_FS); rl::Image font_atlas; rl::Image sprite_atlas; rl::Texture2D font_texture; @@ -110,7 +110,7 @@ fn int main(String[] args) ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); // Main loop - while (!rl::window_should_close()) { + while (!rl::windowShouldClose()) { clock.mark(); /* @@ -124,35 +124,37 @@ fn int main(String[] args) io::printfn("%s", k); } while (k != 0); */ + ugui::ModKeys mod; - mod.rctrl = rl::is_key_pressed(rl::KEY_RIGHT_CONTROL); - mod.lctrl = rl::is_key_pressed(rl::KEY_LEFT_CONTROL); + mod.rctrl = rl::isKeyDown(rl::KEY_RIGHT_CONTROL); + mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL); ui.input_mod_keys(mod); - for (int c; (c = rl::get_key_pressed()) != 0;) { - int[1] ts; - ts[0] = c; - ui.input_text_unicode(ts[..]); + for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) { + ZString kname = rl::getKeyName(key); + if (kname == null) continue; + ui.input_text_unicode(kname.str_view()); } + /* Start Input Handling */ - if (rl::is_window_resized()) { - width = (short)rl::get_screen_width(); - height = (short)rl::get_screen_height(); + if (rl::isWindowResized()) { + width = (short)rl::getScreenWidth(); + height = (short)rl::getScreenHeight(); ui.input_window_size(width, height)!!; } - ui.input_changefocus(rl::is_window_focused()); + ui.input_changefocus(rl::isWindowFocused()); - rl::Vector2 mpos = rl::get_mouse_position(); + rl::Vector2 mpos = rl::getMousePosition(); ui.input_mouse_abs((short)mpos.x, (short)mpos.y); - rl::Vector2 mwheel = rl::get_mouse_wheel_move_v(); + rl::Vector2 mwheel = rl::getMouseWheelMoveV(); ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y); ugui::MouseButtons buttons = { - .btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT), - .btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT), - .btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE), + .btn_left = rl::isMouseButtonDown(rl::MouseButton.LEFT), + .btn_right = rl::isMouseButtonDown(rl::MouseButton.RIGHT), + .btn_middle = rl::isMouseButtonDown(rl::MouseButton.MIDDLE), }; ui.input_mouse_button(buttons); /* End Input Handling */ @@ -240,7 +242,7 @@ fn int main(String[] args) //ui_times.print_stats(); /* Start UI Drawing */ - rl::begin_drawing(); + rl::beginDrawing(); // ClearBackground(BLACK); for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { @@ -250,7 +252,7 @@ fn int main(String[] args) float rad = cmd.rect.radius; // for some weird-ass reason the straight forward inverse formula does not work float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; - rl::draw_rectangle_rounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); + rl::drawRectangleRounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); case ugui::CmdType.CMD_UPDATE_ATLAS: if (cmd.update_atlas.id == font_id) { //rl::unload_image(font_atlas); @@ -258,23 +260,21 @@ fn int main(String[] args) font_atlas.width = cmd.update_atlas.width; font_atlas.height = cmd.update_atlas.height; font_atlas.mipmaps = 1; - //font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE; - font_atlas.format = 1; - if (rl::is_texture_ready(font_texture)) { - rl::unload_texture(font_texture); + font_atlas.format = rl::PixelFormat.UNCOMPRESSED_GRAYSCALE; + if (rl::isTextureValid(font_texture)) { + rl::unloadTexture(font_texture); } - font_texture = rl::load_texture_from_image(font_atlas); + font_texture = rl::loadTextureFromImage(font_atlas); } else if (cmd.update_atlas.id == sprite_id) { sprite_atlas.data = cmd.update_atlas.raw_buffer; sprite_atlas.width = cmd.update_atlas.width; sprite_atlas.height = cmd.update_atlas.height; sprite_atlas.mipmaps = 1; - //sprite_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_R8G8B8A8; - sprite_atlas.format = 7; - if (rl::is_texture_ready(sprite_texture)) { - rl::unload_texture(sprite_texture); + sprite_atlas.format = rl::PixelFormat.UNCOMPRESSED_R8G8B8A8; + if (rl::isTextureValid(sprite_texture)) { + rl::unloadTexture(sprite_texture); } - sprite_texture = rl::load_texture_from_image(sprite_atlas); + sprite_texture = rl::loadTextureFromImage(sprite_atlas); } case ugui::CmdType.CMD_SPRITE: if (cmd.sprite.texture_id == font_id) { @@ -282,23 +282,23 @@ fn int main(String[] args) .x = cmd.sprite.rect.x, .y = cmd.sprite.rect.y, }; - rl::begin_shader_mode(font_shader); - rl::draw_texture_rec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); - rl::end_shader_mode(); + rl::beginShaderMode(font_shader); + rl::drawTextureRec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + rl::endShaderMode(); } else if (cmd.sprite.texture_id == sprite_id) { rl::Vector2 position = { .x = cmd.sprite.rect.x, .y = cmd.sprite.rect.y, }; - rl::draw_texture_rec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + rl::drawTextureRec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); } else { io::printfn("unknown texture id: %d", cmd.sprite.texture_id); } case ugui::CmdType.CMD_SCISSOR: if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { - rl::end_scissor_mode(); + rl::endScissorMode(); } else { - rl::begin_scissor_mode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h); + rl::beginScissorMode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h); } default: io::printfn("Unknown cmd type: %s", cmd.type); @@ -306,11 +306,11 @@ fn int main(String[] args) } draw_times.push(clock.mark()); //draw_times.print_stats(); - rl::end_drawing(); + rl::endDrawing(); /* End Drawing */ } - rl::close_window(); + rl::closeWindow(); return 0; } diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index fdfa9e3..1911d9f 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -58,17 +58,16 @@ const MouseButtons BTN_5 = {.btn_5 = true}; const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max); -fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String key) +fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String keys) { bool is_mod = (bool)(ctx.input.keyboard.down & mod); bool is_keys = true; String haystack = (String)ctx.input.keyboard.text[0..ctx.input.keyboard.text_len]; char[2] needle; - foreach (c: key) { + foreach (c: keys) { needle[0] = c; is_keys = is_keys && haystack.contains((String)needle[..]); } - io::printfn("%b %b", is_mod, is_keys); return is_mod && is_keys; } @@ -162,9 +161,9 @@ fn void Ctx.input_text_utf8(&ctx, char[] text) ctx.input.events.text_input = true; } -fn void Ctx.input_text_unicode(&ctx, int[] text) +fn void Ctx.input_text_unicode(&ctx, char[] text) { - if (text.len == 0) { return; } + if (text.ptr == null || text.len == 0) { return; } char[32] tmp; usz remaining = ctx.input.keyboard.text.len - ctx.input.keyboard.text_len; From f516a68cee032724d4dd6f50b665bc60d7dc0883 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 30 Jan 2025 22:27:24 +0100 Subject: [PATCH 079/250] correct font atlas size --- src/ugui_font.c3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 546c7b9..762c9f3 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -39,7 +39,7 @@ struct Glyph { short adv, ox, oy; } -const uint FONT_CACHED = 512; +const uint FONT_CACHED = 128; def GlyphTable = map::HashMap() @private; fault UgFontError { @@ -89,7 +89,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale) // TODO: allocate buffer based on FONT_CACHED and the size of a sample letter // like the letter 'A' - ushort size = (ushort)font.size*256; + ushort size = (ushort)font.size*(ushort)($$sqrt((float)FONT_CACHED)); font.atlas.new(font.id, ATLAS_GRAYSCALE, size, size)!; // preallocate the ASCII range From 0531f58a5685db91aa5cc43d18fa75ae2242f892 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 30 Jan 2025 22:27:46 +0100 Subject: [PATCH 080/250] less cached elements by default --- src/ugui_core.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index 90d0fbe..c330efe 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -57,7 +57,7 @@ struct Elem { def IdTree = vtree::VTree() @private; // elements themselves are kept in a cache -const uint MAX_ELEMENTS = 1024; +const uint MAX_ELEMENTS = 256; def ElemCache = cache::Cache() @private; def CmdQueue = fifo::Fifo(); From de64746fdf06e6363518986f317f0934bc29b252 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 31 Jan 2025 12:20:19 +0100 Subject: [PATCH 081/250] renamed libraries --- TODO | 2 + .../Makefile | 0 .../libgrapheme.c3i | 0 .../manifest.json | 0 .../project.json | 0 .../thirdparty/libgrapheme | 0 lib/libschrift.c3l/linux-x64/libschrift.a | Bin 19078 -> 0 bytes lib/{libmqoi.c3l => mini-qoi.c3l}/Makefile | 0 lib/{libmqoi.c3l => mini-qoi.c3l}/libmqoi.c3i | 0 .../manifest.json | 0 .../project.json | 0 .../thirdparty/mini-qoi | 0 lib/{libschrift.c3l => schrift.c3l}/Makefile | 0 .../libschrift.c3 | 0 .../manifest.json | 0 .../project.json | 0 .../thirdparty/libschrift | 0 src/main.c3 | 16 ++----- src/ugui_button.c3 | 40 ++++++++++++++++++ 19 files changed, 46 insertions(+), 12 deletions(-) rename lib/{libgrapheme.c3l => grapheme.c3l}/Makefile (100%) rename lib/{libgrapheme.c3l => grapheme.c3l}/libgrapheme.c3i (100%) rename lib/{libgrapheme.c3l => grapheme.c3l}/manifest.json (100%) rename lib/{libgrapheme.c3l => grapheme.c3l}/project.json (100%) rename lib/{libgrapheme.c3l => grapheme.c3l}/thirdparty/libgrapheme (100%) delete mode 100644 lib/libschrift.c3l/linux-x64/libschrift.a rename lib/{libmqoi.c3l => mini-qoi.c3l}/Makefile (100%) rename lib/{libmqoi.c3l => mini-qoi.c3l}/libmqoi.c3i (100%) rename lib/{libmqoi.c3l => mini-qoi.c3l}/manifest.json (100%) rename lib/{libmqoi.c3l => mini-qoi.c3l}/project.json (100%) rename lib/{libmqoi.c3l => mini-qoi.c3l}/thirdparty/mini-qoi (100%) rename lib/{libschrift.c3l => schrift.c3l}/Makefile (100%) rename lib/{libschrift.c3l => schrift.c3l}/libschrift.c3 (100%) rename lib/{libschrift.c3l => schrift.c3l}/manifest.json (100%) rename lib/{libschrift.c3l => schrift.c3l}/project.json (100%) rename lib/{libschrift.c3l => schrift.c3l}/thirdparty/libschrift (100%) diff --git a/TODO b/TODO index d62e1de..c2e8c1b 100644 --- a/TODO +++ b/TODO @@ -28,6 +28,8 @@ to maintain focus until mouse release (fix scroll bars) [ ] gif support? [ ] layout_set_max_rows() and layout_set_max_columns() [ ] Maybe SDF sprites?? +[ ] Stylesheets and stylesheet import +[ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame ## Layout diff --git a/lib/libgrapheme.c3l/Makefile b/lib/grapheme.c3l/Makefile similarity index 100% rename from lib/libgrapheme.c3l/Makefile rename to lib/grapheme.c3l/Makefile diff --git a/lib/libgrapheme.c3l/libgrapheme.c3i b/lib/grapheme.c3l/libgrapheme.c3i similarity index 100% rename from lib/libgrapheme.c3l/libgrapheme.c3i rename to lib/grapheme.c3l/libgrapheme.c3i diff --git a/lib/libgrapheme.c3l/manifest.json b/lib/grapheme.c3l/manifest.json similarity index 100% rename from lib/libgrapheme.c3l/manifest.json rename to lib/grapheme.c3l/manifest.json diff --git a/lib/libgrapheme.c3l/project.json b/lib/grapheme.c3l/project.json similarity index 100% rename from lib/libgrapheme.c3l/project.json rename to lib/grapheme.c3l/project.json diff --git a/lib/libgrapheme.c3l/thirdparty/libgrapheme b/lib/grapheme.c3l/thirdparty/libgrapheme similarity index 100% rename from lib/libgrapheme.c3l/thirdparty/libgrapheme rename to lib/grapheme.c3l/thirdparty/libgrapheme diff --git a/lib/libschrift.c3l/linux-x64/libschrift.a b/lib/libschrift.c3l/linux-x64/libschrift.a deleted file mode 100644 index e3a042393ad6f6b4e78e5d729a610b74dd03c1b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19078 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 z 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 G|9=4{+1>vD diff --git a/lib/libmqoi.c3l/Makefile b/lib/mini-qoi.c3l/Makefile similarity index 100% rename from lib/libmqoi.c3l/Makefile rename to lib/mini-qoi.c3l/Makefile diff --git a/lib/libmqoi.c3l/libmqoi.c3i b/lib/mini-qoi.c3l/libmqoi.c3i similarity index 100% rename from lib/libmqoi.c3l/libmqoi.c3i rename to lib/mini-qoi.c3l/libmqoi.c3i diff --git a/lib/libmqoi.c3l/manifest.json b/lib/mini-qoi.c3l/manifest.json similarity index 100% rename from lib/libmqoi.c3l/manifest.json rename to lib/mini-qoi.c3l/manifest.json diff --git a/lib/libmqoi.c3l/project.json b/lib/mini-qoi.c3l/project.json similarity index 100% rename from lib/libmqoi.c3l/project.json rename to lib/mini-qoi.c3l/project.json diff --git a/lib/libmqoi.c3l/thirdparty/mini-qoi b/lib/mini-qoi.c3l/thirdparty/mini-qoi similarity index 100% rename from lib/libmqoi.c3l/thirdparty/mini-qoi rename to lib/mini-qoi.c3l/thirdparty/mini-qoi diff --git a/lib/libschrift.c3l/Makefile b/lib/schrift.c3l/Makefile similarity index 100% rename from lib/libschrift.c3l/Makefile rename to lib/schrift.c3l/Makefile diff --git a/lib/libschrift.c3l/libschrift.c3 b/lib/schrift.c3l/libschrift.c3 similarity index 100% rename from lib/libschrift.c3l/libschrift.c3 rename to lib/schrift.c3l/libschrift.c3 diff --git a/lib/libschrift.c3l/manifest.json b/lib/schrift.c3l/manifest.json similarity index 100% rename from lib/libschrift.c3l/manifest.json rename to lib/schrift.c3l/manifest.json diff --git a/lib/libschrift.c3l/project.json b/lib/schrift.c3l/project.json similarity index 100% rename from lib/libschrift.c3l/project.json rename to lib/schrift.c3l/project.json diff --git a/lib/libschrift.c3l/thirdparty/libschrift b/lib/schrift.c3l/thirdparty/libschrift similarity index 100% rename from lib/libschrift.c3l/thirdparty/libschrift rename to lib/schrift.c3l/thirdparty/libschrift diff --git a/src/main.c3 b/src/main.c3 index 5f4873e..7c7228d 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -113,18 +113,6 @@ fn int main(String[] args) while (!rl::windowShouldClose()) { clock.mark(); -/* - KeyboardKey k; - do { - k = rl::get_key_pressed(); - if (!k) { break; } - if (rl::is_key_down(k)) { io::print("down "); } - if (rl::is_key_pressed(k)) { io::print("pressed "); } - if (rl::is_key_released(k)) { io::print("released "); } - io::printfn("%s", k); - } while (k != 0); -*/ - ugui::ModKeys mod; mod.rctrl = rl::isKeyDown(rl::KEY_RIGHT_CONTROL); mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL); @@ -194,6 +182,10 @@ fn int main(String[] args) ui.layout_next_column()!!; ui.button_label("Continua!")!!; + + ui.layout_next_row()!!; + static bool check; + ui.checkbox("check1", "", ugui::Point{}, &check)!!; |}; ui.draw_sprite("sprite1", "tux")!!; ui.div_end()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index f18b417..ee3a898 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -8,6 +8,8 @@ struct ElemButton { } // draw a button, return the events on that button +// FIXME: "state" should be renamed "active" to toggle between an usable button and +// an inactive (greyed-out) button fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) { Id id = ctx.gen_id(label)!; @@ -83,3 +85,41 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m return elem.events; } + +// FIXME: this should be inside the style +const ushort DEFAULT_CHECKBOX_SIZE = 16; +fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state) +{ + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // FIXME: for now checkboxes and buttons have no members so the element types + // can be the same + if (elem.flags.is_new) { + elem.type = ETYPE_BUTTON; + } else if (elem.type != ETYPE_BUTTON) { + return UgError.WRONG_ELEMENT_TYPE?; + } + + Rect size = {off.x, off.y, DEFAULT_CHECKBOX_SIZE, DEFAULT_CHECKBOX_SIZE}; + elem.bounds = ctx.position_element(parent, size, true); + + // if the bounds are null the element is outside the div view, + // no interaction should occur so just return + if (elem.bounds.is_null()) return; + + Color col = 0x0000ffffu.to_rgba(); + elem.events = ctx.get_elem_events(elem); + if (state) { + col = 0xff0000ffu.to_rgba(); + } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { + col = 0xff00ffffu.to_rgba(); + } + + // Draw the button + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; +} From d94e430807fbf56b59f398a6f71b42391b9ad279 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 31 Jan 2025 12:35:59 +0100 Subject: [PATCH 082/250] added library submodules --- .gitmodules | 13 ++++++++++++ lib/grapheme.c3l/project.json | 36 +++------------------------------- lib/mini-qoi.c3l/project.json | 21 ++------------------ lib/schrift.c3l/project.json | 37 +++-------------------------------- 4 files changed, 21 insertions(+), 86 deletions(-) diff --git a/.gitmodules b/.gitmodules index 0bc165c..e9200f8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,16 @@ [submodule "lib/raylib.c3l"] path = lib/raylib.c3l url = https://github.com/NexushasTaken/raylib.c3l + ignore = dirty +[submodule "lib/grapheme.c3l/thirdparty/libgrapheme"] + path = lib/grapheme.c3l/thirdparty/libgrapheme + url = git://git.suckless.org/libgrapheme + ignore = dirty +[submodule "lib/mini-qoi.c3l/thirdparty/mini-qoi"] + path = lib/mini-qoi.c3l/thirdparty/mini-qoi + url = https://github.com/shraiwi/mini-qoi + ignore = dirty +[submodule "lib/schrift.c3l/thirdparty/libschrift"] + path = lib/schrift.c3l/thirdparty/libschrift + url = https://github.com/tomolt/libschrift + ignore = dirty diff --git a/lib/grapheme.c3l/project.json b/lib/grapheme.c3l/project.json index 876ca0c..4b2d212 100644 --- a/lib/grapheme.c3l/project.json +++ b/lib/grapheme.c3l/project.json @@ -1,44 +1,14 @@ { - // Language version of C3. "langrev": "1", - // Warnings used for all targets. "warnings": ["no-unused"], - // Directories where C3 library files may be found. "dependency-search-paths": [".."], - // Libraries to use for all targets. "dependencies": ["grapheme"], - // Authors, optionally with email. - "authors": ["Alessandro Mauri ", "Laslo Hunhold "], "version": "0.1.0", - // Sources compiled for all targets. "sources": [], - // C sources if the project also compiles C sources - // relative to the project file. - // "c-sources": [ "csource/**" ], - // Output location, relative to project file. "output": "build", - // Architecture and OS target. - // You can use 'c3c --list-targets' to list all valid targets. - // "target": "windows-x64", - "features": [ - // See rcore.c3 - //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", - //"SUPPORT_STANDARD_FILEIO", - //"SUPPORT_FILE_SYSTEM_FUNCTIONS", - //"SUPPORT_DATA_ENCODER", - // See text.c3 - //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", - //"SUPPORT_TEXT_C_STRING_MANAGEMENT", - //"SUPPORT_RANDOM_GENERATION", - //"SUPPORT_RAYGUI", - //"RAYGUI_NO_ICONS", - //"RAYGUI_CUSTOM_ICONS", - ], - // Global settings. - // CPU name, used for optimizations in the LLVM backend. + "target": "linux-x64", + "features": [], "cpu": "generic", - // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". "opt": "O0" - // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. } diff --git a/lib/mini-qoi.c3l/project.json b/lib/mini-qoi.c3l/project.json index 78ab828..cda4a4d 100644 --- a/lib/mini-qoi.c3l/project.json +++ b/lib/mini-qoi.c3l/project.json @@ -1,31 +1,14 @@ { - // Language version of C3. "langrev": "1", - // Warnings used for all targets. "warnings": [ "no-unused" ], - // Directories where C3 library files may be found. "dependency-search-paths": [ ".." ], - // Libraries to use for all targets. "dependencies": [ "mqoi" ], - // Authors, optionally with email. - "authors": [ "Alessandro Mauri ", "Shranav Palakurthi"], "version": "0.1.0", - // Sources compiled for all targets. "sources": [ ], - // C sources if the project also compiles C sources - // relative to the project file. - // "c-sources": [ "csource/**" ], - // Output location, relative to project file. "output": "build", - // Architecture and OS target. - // You can use 'c3c --list-targets' to list all valid targets. - // "target": "windows-x64", + "target": "linux-x64", "features": [], - // Global settings. - // CPU name, used for optimizations in the LLVM backend. "cpu": "generic", - // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". "opt": "O0", - // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. } diff --git a/lib/schrift.c3l/project.json b/lib/schrift.c3l/project.json index 573769f..92b049c 100644 --- a/lib/schrift.c3l/project.json +++ b/lib/schrift.c3l/project.json @@ -1,45 +1,14 @@ { - // Language version of C3. "langrev": "1", - // Warnings used for all targets. "warnings": [ "no-unused" ], - // Directories where C3 library files may be found. "dependency-search-paths": [ ".." ], - // Libraries to use for all targets. "dependencies": [ "schrift" ], - // Authors, optionally with email. - "authors": [ "Alessandro Mauri ", "Thomas Oltmann " ], "version": "0.1.0", - // Sources compiled for all targets. "sources": [ ], - // C sources if the project also compiles C sources - // relative to the project file. - // "c-sources": [ "csource/**" ], - // Output location, relative to project file. "output": "build", - // Architecture and OS target. - // You can use 'c3c --list-targets' to list all valid targets. - // "target": "windows-x64", - "features": [ - // See rcore.c3 - //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", - //"SUPPORT_STANDARD_FILEIO", - //"SUPPORT_FILE_SYSTEM_FUNCTIONS", - //"SUPPORT_DATA_ENCODER", - // See text.c3 - //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", - //"SUPPORT_TEXT_C_STRING_MANAGEMENT", - //"SUPPORT_RANDOM_GENERATION", - - //"SUPPORT_RAYGUI", - //"RAYGUI_NO_ICONS", - //"RAYGUI_CUSTOM_ICONS", - ], - // Global settings. - // CPU name, used for optimizations in the LLVM backend. + "target": "linux-x64", + "features": [], "cpu": "generic", - // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". "opt": "O0", - // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. } From 92614e4d8b1af577b62dc6df668cee28b78e0053 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 31 Jan 2025 23:15:40 +0100 Subject: [PATCH 083/250] removed comments --- project.json | 45 +++++---------------------------------------- 1 file changed, 5 insertions(+), 40 deletions(-) diff --git a/project.json b/project.json index c777c37..66150e9 100644 --- a/project.json +++ b/project.json @@ -1,55 +1,20 @@ { - // Language version of C3. "langrev": "1", - // Warnings used for all targets. "warnings": ["no-unused"], - // Directories where C3 library files may be found. "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], - // Libraries to use for all targets. "dependencies": ["raylib55", "schrift", "grapheme", "mqoi"], - "features": [ - // See rcore.c3 - //"SUPPORT_INTERNAL_MEMORY_MANAGEMENT", - //"SUPPORT_STANDARD_FILEIO", - //"SUPPORT_FILE_SYSTEM_FUNCTIONS", - //"SUPPORT_DATA_ENCODER", - // See text.c3 - //"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT", - //"SUPPORT_TEXT_C_STRING_MANAGEMENT", - //"SUPPORT_RANDOM_GENERATION", - //"SUPPORT_RAYGUI", - //"RAYGUI_NO_ICONS", - //"RAYGUI_CUSTOM_ICONS", - ], - // Authors, optionally with email. + "features": [], "authors": ["Alessandro Mauri "], - // Version using semantic versioning. "version": "0.1.0", - // Sources compiled for all targets. "sources": ["src/**"], - // C sources if the project also compiles C sources - // relative to the project file. - // "c-sources": [ "csource/**" ], - // Include directories for C sources relative to the project file. - // "c-include-dirs": [ "csource/include" ], - // Output location, relative to project file. "output": "build", - // Architecture and OS target. - // You can use 'c3c --list-targets' to list all valid targets. - // "target": "windows-x64", - // Targets. + "target": "linux-x64", "targets": { "ugui": { - // Executable or library. "type": "executable" - // Additional libraries, sources - // and overrides of global settings here. } }, - // Global settings. - // CPU name, used for optimizations in the LLVM backend. - "cpu": "generic", - // Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz". - "opt": "O0" - // See resources/examples/project_all_settings.json and 'c3c --list-project-properties' to see more properties. + "cpu": "native", + "opt": "O0", + "debug-info": "full" } From c53b9eed5e608c3ada2c566e342091315cdb0fc6 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 1 Feb 2025 01:01:13 +0100 Subject: [PATCH 084/250] MAYBE correct layout with next_row and next_column --- src/main.c3 | 12 +++++++++++- src/ugui_div.c3 | 4 ++-- src/ugui_layout.c3 | 10 ++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 7c7228d..d2be50b 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -152,7 +152,6 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; - // main div, fill the whole window ui.div_begin("main", ugui::Rect{.w=-100})!!; {| ui.layout_set_column()!!; @@ -186,6 +185,17 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; ui.checkbox("check1", "", ugui::Point{}, &check)!!; + +/* + ui.layout_set_column()!!; + ui.button_label(" A ")!!; + ui.button_label(" B ")!!; + ui.layout_next_column()!!; + ui.button_label(" C ")!!; + ui.button_label(" D ")!!; + ui.layout_next_row()!!; + ui.button_label(" E ")!!; +*/ |}; ui.draw_sprite("sprite1", "tux")!!; ui.div_end()!!; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index 3f32e73..da175cf 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -55,7 +55,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo .h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h, }; elem.bounds = ctx.position_element(parent, wanted_size); - elem.div.children_bounds = elem.bounds; + elem.div.children_bounds = Rect{}; // update the ctx scissor ctx.div_scissor = elem.bounds; @@ -89,7 +89,7 @@ fn void! Ctx.div_end(&ctx) // FIXME: this causes all elements inside the div to loose focus since the mouse press happens // both inside the element and inside the div bounds //elem.events = ctx.get_elem_events(elem); - + Rect cb = elem.div.pcb; // children bounds bottom-right corner Point cbc = { diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 01c03bc..3a2ac9a 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -52,13 +52,12 @@ fn void! Ctx.layout_next_row(&ctx) Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { - // what? return UgError.UNEXPECTED_ELEMENT?; } parent.div.origin_r = Point{ .x = parent.bounds.x, - .y = parent.div.origin_c.y, + .y = parent.div.children_bounds.bottom_right().y, }; parent.div.origin_c = parent.div.origin_r; } @@ -69,12 +68,11 @@ fn void! Ctx.layout_next_column(&ctx) Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { - // what? return UgError.UNEXPECTED_ELEMENT?; } parent.div.origin_c = Point{ - .x = parent.div.origin_r.x, + .x = parent.div.children_bounds.bottom_right().x, .y = parent.bounds.y, }; parent.div.origin_r = parent.div.origin_c; @@ -158,7 +156,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) // margin, offsets the placement and grows the occupied area child_placement = child_placement.off(margin.position()); child_occupied = child_occupied.grow(margin.position().add(margin.size())); - + // oh yeah also adjust the rect if i was to grow if (adapt_x) rect.w -= padding.x+padding.w + border.x+border.w + margin.x+margin.w; if (adapt_y) rect.h -= padding.y+padding.h + border.y+border.h + margin.y+margin.h; @@ -183,7 +181,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) if (child_occupied.bottom_right().x > div.children_bounds.bottom_right().x) { div.children_bounds.w += child_occupied.bottom_right().x - div.children_bounds.bottom_right().x; } - // left overflow + // bottom overflow if (child_occupied.bottom_right().y > div.children_bounds.bottom_right().y) { div.children_bounds.h += child_occupied.bottom_right().y - div.children_bounds.bottom_right().y; } From 196a2474fd22546120c74d8c24714abd12e8f934 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 3 Feb 2025 23:07:14 +0100 Subject: [PATCH 085/250] todos --- TODO | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO b/TODO index c2e8c1b..562326a 100644 --- a/TODO +++ b/TODO @@ -30,6 +30,8 @@ to maintain focus until mouse release (fix scroll bars) [ ] Maybe SDF sprites?? [ ] Stylesheets and stylesheet import [ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame +[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for + internal use functions (used to create widgets) ## Layout From 14359a9b7e37f3ef170308ee2e68b927e08789ea Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 3 Feb 2025 23:07:32 +0100 Subject: [PATCH 086/250] first checkbox --- src/ugui_button.c3 | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index ee3a898..bbb0483 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -112,11 +112,13 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s // no interaction should occur so just return if (elem.bounds.is_null()) return; - Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); - if (state) { + if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + + Color col; + if (*state) { col = 0xff0000ffu.to_rgba(); - } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { + } else { col = 0xff00ffffu.to_rgba(); } From 588a417413d72b9730dbb56aaa722268b597b562 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 4 Feb 2025 22:40:44 +0100 Subject: [PATCH 087/250] checkbox and msdf sprite rendering --- TODO | 6 ++--- resources/tick_sdf.qoi | Bin 0 -> 1821 bytes src/main.c3 | 54 +++++++++++++++++++++++++++++++++++------ src/ugui_button.c3 | 21 ++++++++++------ src/ugui_cmd.c3 | 9 +++---- src/ugui_sprite.c3 | 30 ++++++++++++++++++----- 6 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 resources/tick_sdf.qoi diff --git a/TODO b/TODO index 562326a..d570a17 100644 --- a/TODO +++ b/TODO @@ -27,10 +27,10 @@ to maintain focus until mouse release (fix scroll bars) [ ] .jpg [ ] gif support? [ ] layout_set_max_rows() and layout_set_max_columns() -[ ] Maybe SDF sprites?? +[x] Maybe SDF sprites?? [ ] Stylesheets and stylesheet import [ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame -[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for +[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for internal use functions (used to create widgets) ## Layout @@ -77,4 +77,4 @@ _ border radius [ ] Text Input box [ ] Icon Buttons [ ] Switch -[ ] Checkbox +[x] Checkbox diff --git a/resources/tick_sdf.qoi b/resources/tick_sdf.qoi new file mode 100644 index 0000000000000000000000000000000000000000..51a8f81a83fcd592409942a386816af9f9329275 GIT binary patch literal 1821 zcmX|?eMpsO7{;Gkjp@joeW7NFnPlbL$vTddlBqeZnOM`XNJ*PkT4pVqHj^o6rAwz} zTKSPO3w0SOCZ*}|<7g+>i5wNpR$El7MQsHY6xX%;J&hgso%6?Y-xtqwe_SU$ZA+3; z%3bFJWl0)@dZeKGglZv6h!GYF69lDT5WW&Bg{w*#FB^uSUd!2ta7H*NBnh7>xVyO_ zTH+1}mxMNnw-_!%-Iw!A!Z8KM3`Z=FT8>N~ADpqXL%+n8*y1cuFBQgI9Rp*d5nJ-N zpnBs*Twl5rwa(5+wZ&Y)Y|cjPZ?QOZ;ShSu%W*b69p6So;B#+pqufkQG#V_J!YK%6 zIQDTL&OJMao*oN|iW;zX?RS_pYlCsH_K<{rYV0u#)!xApF2QzgM;+^M?b;2TI6)*Q z-^QXv?Gp8aQHrEAAzqJ%H@y+ZIFzy!=6hx|H^0T6Jw$LYF>&G(i8C9`@;zIK5%x>g zAe`xpRa}K!=A!-Yc64_Bg@OWN%^G6nOiCPU^Na)XJ6(tna)h&zYla=|u#C&FlRMGC z1`H4Xilaw~#6)7j0$Z-h*e~Iegb*P^I3?V&rEAoXzyy@D99KrK;M6H%*DfM3kg&I> zW-|6kti2E*qzcu7Rg>fa%-{^H=4#|K9~Ylq#QF2jk(WnAMiMSA)OxW{8u*bkrzb_S ze1$~eh@cnYjjqxU2Ra~#LC9bRn%*^GaPTh5%82-QV(wgOeMk@$f8i5Rl00u=olqon z3D2}1qsh<*U-}}6NjT2qu->$yzMe=+BmDh|4?om}%0whZ2o_vi+$A?k$P?Ox2LeqZ z)e8@LAciq0WFfjAb)&WQC313z6)T9TQ*Bl4$AGAu5Wb!^4PS(X$?#-}LX$A6^@@xM zPIN*jL$Q~8(ek8Ik%vdFRmk}3->XH(eO(t}7Jh$C9Ir;rW z$kVl+6q5l{ITb6p5;@F4>&sSjcmIil2Z@*%!o!2wAf`u8M@T^kwsma7k;g~S^|%Wr z(?e8L5Ic4dVPXHzw$UUH3Sa=zn1*`R!)m>SW5Oc4>hs4 zS4w8jo$fY&N@qGMSqakv6S}&d;P7FE)YJt?3`st4lM6S>%j0kTvi&(9CA#q*8 f0pUx*U637AYL1}0L8fq0kanmS+GRP^KV$v{LxTmh literal 0 HcmV?d00001 diff --git a/src/main.c3 b/src/main.c3 index d2be50b..d012e4a 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -44,6 +44,35 @@ fn TimeStats Times.get_stats(×) } +const ZString MSDF_FS = ` +#version 330 + +in vec2 fragTexCoord; +out vec4 fragColor; +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +const float pxRange = 4.0f; + +float screenPxRange() { + vec2 unitRange = vec2(pxRange)/vec2(textureSize(texture0, 0)); + vec2 screenTexSize = vec2(1.0)/fwidth(fragTexCoord); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + vec3 msd = texture(texture0, fragTexCoord).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange()*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + fragColor = colDiffuse * opacity; +} +`; + const ZString FONT_FS = ` #version 330 @@ -85,6 +114,7 @@ fn int main(String[] args) ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; + ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; short width = 800; short height = 450; @@ -103,10 +133,13 @@ fn int main(String[] args) // font stuff rl::Shader font_shader = rl::loadShaderFromMemory(null, FONT_FS); rl::Image font_atlas; - rl::Image sprite_atlas; rl::Texture2D font_texture; - rl::Texture2D sprite_texture; ugui::Id font_id = ui.get_font_id("font1"); + + // sprite stuff + rl::Shader msdf_shader = rl::loadShaderFromMemory(null, MSDF_FS); + rl::Image sprite_atlas; + rl::Texture2D sprite_texture; ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); // Main loop @@ -184,7 +217,7 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", ugui::Point{}, &check)!!; + ui.checkbox("check1", "", ugui::Point{}, &check, "tick")!!; /* ui.layout_set_column()!!; @@ -288,11 +321,16 @@ fn int main(String[] args) rl::drawTextureRec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); rl::endShaderMode(); } else if (cmd.sprite.texture_id == sprite_id) { - rl::Vector2 position = { - .x = cmd.sprite.rect.x, - .y = cmd.sprite.rect.y, - }; - rl::drawTextureRec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + // FIXME: THIS CODE IS SHIT, REAL DOO DOO + {| + if (cmd.sprite.type == SpriteType.SPRITE_MSDF) { + rl::beginShaderMode(msdf_shader); + defer rl::endShaderMode(); + rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); + } else { + rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); + } + |}; } else { io::printfn("unknown texture id: %d", cmd.sprite.texture_id); } diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index bbb0483..c7089f4 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -88,7 +88,7 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m // FIXME: this should be inside the style const ushort DEFAULT_CHECKBOX_SIZE = 16; -fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state) +fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) { Id id = ctx.gen_id(label)!; @@ -116,12 +116,19 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); Color col; - if (*state) { - col = 0xff0000ffu.to_rgba(); + if (tick_sprite != String{}) { + col = ctx.style.bgcolor; + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + if (*state) { + ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; + } } else { - col = 0xff00ffffu.to_rgba(); + if (*state) { + col = 0xff0000ffu.to_rgba(); + } else { + col = 0xff00ffffu.to_rgba(); + } + // Draw the button + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; } - - // Draw the button - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 9ca04da..9524dc3 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -23,14 +23,12 @@ struct CmdUpdateAtlas { short width, height, bpp; } -// TODO: -// 1. Add atlases as a data type -// 2. Each atlas has an id struct CmdSprite { + Id texture_id; + SpriteType type; Rect rect; Rect texture_rect; Color hue; - Id texture_id; } // if rect is zero Rect{0} then reset the scissor @@ -103,10 +101,11 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo } // TODO: add texture id -fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba()) +fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) { Cmd cmd = { .type = CMD_SPRITE, + .sprite.type = type, .sprite.rect = bounds, .sprite.texture_rect = texture, .sprite.texture_id = texture_id, diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 index ce4cabb..7c1d368 100644 --- a/src/ugui_sprite.c3 +++ b/src/ugui_sprite.c3 @@ -6,8 +6,16 @@ import mqoi; const usz SRITES_PER_ATLAS = 64; +enum SpriteType { + SPRITE_NORMAL, + SPRITE_SDF, + SPRITE_MSDF, + SPRITE_ANIMATED, +} + struct Sprite { Id id; + SpriteType type; ushort u, v; ushort w, h; } @@ -46,10 +54,11 @@ fn void! SpriteAtlas.free(&this) } // FIXME: this should throw an error when a different pixel format than the atlas' is used -fn Sprite*! SpriteAtlas.insert(&this, String name, char[] pixels, ushort w, ushort h, ushort stride) +fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) { Sprite s; s.id = name.hash(); + s.type = type; Point uv = this.atlas.place(pixels, w, h, stride)!; s.w = w; s.h = h; @@ -81,12 +90,12 @@ fn Id Ctx.get_sprite_atlas_id(&ctx, String name) return name.hash(); } -fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride) +fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL) { - ctx.sprite_atlas.insert(name, pixels, w, h, stride)!; + ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!; } -fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) +fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) { mqoi::Desc image_desc; uint w, h; @@ -118,7 +127,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) } } - ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; + ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!; } fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) @@ -146,9 +155,18 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) // if the bounds are null the element is outside the div view, // no interaction should occur so just return - if (elem.bounds.is_null()) return; + if (elem.bounds.is_null()) return; Id tex_id = ctx.sprite_atlas.id; return ctx.push_sprite(elem.bounds, uv, tex_id)!; } + +fn void! Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) +{ + Sprite* sprite = ctx.sprite_atlas.get(name)!; + Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; + //Rect bounds = Rect{ 0, 0, sprite.w, sprite.h }.off(off); + Id tex_id = ctx.sprite_atlas.id; + return ctx.push_sprite(bounds, uv, tex_id, type: sprite.type)!; +} From e09107af980e1466a2248da01fd9956df93317be Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 6 Feb 2025 23:44:03 +0100 Subject: [PATCH 088/250] simpler main --- TODO | 1 + src/main.c3 | 107 +++++++++++++++++++++------------------------------- 2 files changed, 45 insertions(+), 63 deletions(-) diff --git a/TODO b/TODO index d570a17..0caf07d 100644 --- a/TODO +++ b/TODO @@ -32,6 +32,7 @@ to maintain focus until mouse release (fix scroll bars) [ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame [ ] Subdivide modules into ugui::ug for exported functions and ugui::core for internal use functions (used to create widgets) +[ ] The render loop RAPES the gpu, valve pls fix ## Layout diff --git a/src/main.c3 b/src/main.c3 index d012e4a..b0176eb 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -95,12 +95,30 @@ void main() } `; +// SDF based rect rendering +const ZString RECT_FS = ` +#version 330 + +in vec2 fragTexCoord; +in vec4 fragColor; +out vec4 finalColor; + +uniform float radius; +uniform float border; +uniform vec2 size; + +void main() +{ + +} +`; + macro rl::Color ugui::Color.conv(color) { return rl::Color{.r = color.r, .g = color.g, .b = color.b, .a = color.a}; } -macro rl::Rectangle ugui::Rect.conv(rect) +macro rl::Rectangle Rect.conv(rect) { return rl::Rectangle{.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; } @@ -185,29 +203,29 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; - ui.div_begin("main", ugui::Rect{.w=-100})!!; + ui.div_begin("main", Rect{.w=-100})!!; {| ui.layout_set_column()!!; - if (ui.button("button0", ugui::Rect{0,0,30,30}, toggle)!!.mouse_press) { + if (ui.button("button0", Rect{0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; } //ui.layout_next_column()!!; - if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) { + if (ui.button("button1", Rect{0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; - if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) { + if (ui.button("button2", Rect{0,0,30,30})!!.mouse_release) { io::printn("release button2"); } ui.layout_set_row()!!; ui.layout_next_row()!!; static float rf, gf, bf, af; - ui.slider_ver("slider_r", ugui::Rect{0,0,30,100}, &rf)!!; - ui.slider_ver("slider_g", ugui::Rect{0,0,30,100}, &gf)!!; - ui.slider_ver("slider_b", ugui::Rect{0,0,30,100}, &bf)!!; - ui.slider_ver("slider_a", ugui::Rect{0,0,30,100}, &af)!!; + ui.slider_ver("slider_r", Rect{0,0,30,100}, &rf)!!; + ui.slider_ver("slider_g", Rect{0,0,30,100}, &gf)!!; + ui.slider_ver("slider_b", Rect{0,0,30,100}, &bf)!!; + ui.slider_ver("slider_a", Rect{0,0,30,100}, &af)!!; ui.layout_next_column()!!; ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; @@ -217,7 +235,7 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", ugui::Point{}, &check, "tick")!!; + ui.checkbox("check1", "", Point{}, &check, "tick")!!; /* ui.layout_set_column()!!; @@ -237,24 +255,24 @@ fn int main(String[] args) {| ui.layout_set_column()!!; static float slider2 = 0.5; - if (ui.slider_ver("slider", ugui::Rect{0,0,30,100}, &slider2)!!.update) { + if (ui.slider_ver("slider", Rect{0,0,30,100}, &slider2)!!.update) { io::printfn("other slider: %f", slider2); } - ui.button("button0", ugui::Rect{0,0,50,50})!!; - ui.button("button1", ugui::Rect{0,0,50,50})!!; - ui.button("button2", ugui::Rect{0,0,50,50})!!; - ui.button("button3", ugui::Rect{0,0,50,50})!!; + ui.button("button0", Rect{0,0,50,50})!!; + ui.button("button1", Rect{0,0,50,50})!!; + ui.button("button2", Rect{0,0,50,50})!!; + ui.button("button3", Rect{0,0,50,50})!!; if (toggle) { - ui.button("button4", ugui::Rect{0,0,50,50})!!; - ui.button("button5", ugui::Rect{0,0,50,50})!!; - ui.button("button6", ugui::Rect{0,0,50,50})!!; - ui.button("button7", ugui::Rect{0,0,50,50})!!; + ui.button("button4", Rect{0,0,50,50})!!; + ui.button("button5", Rect{0,0,50,50})!!; + ui.button("button6", Rect{0,0,50,50})!!; + ui.button("button7", Rect{0,0,50,50})!!; } ui.layout_next_column()!!; ui.layout_set_row()!!; static float f1, f2; - ui.slider_hor("hs1", ugui::Rect{0,0,100,30}, &f1)!!; - ui.slider_hor("hs2", ugui::Rect{0,0,100,30}, &f2)!!; + ui.slider_hor("hs1", Rect{0,0,100,30}, &f1)!!; + ui.slider_hor("hs2", Rect{0,0,100,30}, &f2)!!; |}; ui.div_end()!!; @@ -263,7 +281,7 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", ugui::Rect{0, ui.height-100, -300, 100})!!; + ui.div_begin("fps", Rect{0, ui.height-100, -300, 100})!!; {| ui.layout_set_column()!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; @@ -282,13 +300,13 @@ fn int main(String[] args) for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { switch (cmd.type) { - case ugui::CmdType.CMD_RECT: + case CmdType.CMD_RECT: Rect r = cmd.rect.rect; float rad = cmd.rect.radius; // for some weird-ass reason the straight forward inverse formula does not work float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; rl::drawRectangleRounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); - case ugui::CmdType.CMD_UPDATE_ATLAS: + case CmdType.CMD_UPDATE_ATLAS: if (cmd.update_atlas.id == font_id) { //rl::unload_image(font_atlas); font_atlas.data = cmd.update_atlas.raw_buffer; @@ -311,7 +329,7 @@ fn int main(String[] args) } sprite_texture = rl::loadTextureFromImage(sprite_atlas); } - case ugui::CmdType.CMD_SPRITE: + case CmdType.CMD_SPRITE: if (cmd.sprite.texture_id == font_id) { rl::Vector2 position = { .x = cmd.sprite.rect.x, @@ -334,7 +352,7 @@ fn int main(String[] args) } else { io::printfn("unknown texture id: %d", cmd.sprite.texture_id); } - case ugui::CmdType.CMD_SCISSOR: + case CmdType.CMD_SCISSOR: if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { rl::endScissorMode(); } else { @@ -354,40 +372,3 @@ fn int main(String[] args) return 0; } - -/* -fn void! test_vtree() @test -{ - vtree::VTree() vt; - vt.init(10)!!; - defer vt.free(); - - assert(vt.size() == 10, "Size is incorrect"); - - isz ref = vt.add("Ciao Mamma", 0)!!; - String s = vt.get(ref)!!; - assert(s == "Ciao Mamma", "String is incorrect"); - isz par = vt.parentof(0)!!; - assert(ref == par, "Not Root"); - - vt.print(); -} - -def StrCache = cache::Cache(); -fn void! test_cache() @test -{ - StrCache cc; - cc.init()!!; - defer cc.free(); - - String*! r = cc.search(1); - if (catch ex = r) { - if (ex != SearchResult.MISSING) { - return ex?; - } - } - - r = cc.get_or_insert(&&"Ciao Mamma", 1)!; - assert(*r!! == "Ciao Mamma", "incorrect string"); -} -*/ From 52f3929a429797ad46b8908a16156e5d6e8c365f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 6 Feb 2025 23:51:15 +0100 Subject: [PATCH 089/250] renamed ATlAS_RGBA32 to ATLAS_R8G8B8A8 --- src/main.c3 | 2 +- src/ugui_atlas.c3 | 4 ++-- src/ugui_sprite.c3 | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index b0176eb..7382ebc 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -130,7 +130,7 @@ fn int main(String[] args) defer ui.free(); ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; - ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; + ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!; ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; diff --git a/src/ugui_atlas.c3 b/src/ugui_atlas.c3 index 1976ec4..8745fd7 100644 --- a/src/ugui_atlas.c3 +++ b/src/ugui_atlas.c3 @@ -9,7 +9,7 @@ fault UgAtlasError { enum AtlasType { ATLAS_GRAYSCALE, - ATLAS_RGBA32, + ATLAS_R8G8B8A8, } // black and white atlas @@ -29,7 +29,7 @@ macro usz AtlasType.bpp(type) { switch (type) { case ATLAS_GRAYSCALE: return 1; - case ATLAS_RGBA32: return 4; + case ATLAS_R8G8B8A8: return 4; } } diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 index 7c1d368..b4a83f2 100644 --- a/src/ugui_sprite.c3 +++ b/src/ugui_sprite.c3 @@ -36,13 +36,13 @@ struct ElemSprite { // name: some examples are "icons" or "images" fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) { - // FIXME: for now only RGBA32 format is supported - if (type != ATLAS_RGBA32) { + // FIXME: for now only R8G8B8A8 format is supported + if (type != ATLAS_R8G8B8A8) { return UgAtlasError.INVALID_TYPE?; } this.id = name.hash(); - this.atlas.new(this.id, AtlasType.ATLAS_RGBA32, width, height)!; + this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!; this.sprites.new_init(capacity: SRITES_PER_ATLAS); this.should_update = false; } @@ -54,6 +54,7 @@ fn void! SpriteAtlas.free(&this) } // FIXME: this should throw an error when a different pixel format than the atlas' is used +// or convert from the source's pixel format to the atlas' fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) { Sprite s; From 7c6f7d31d2e8101c69b62e82901001457a16a148 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 7 Feb 2025 23:46:21 +0100 Subject: [PATCH 090/250] quick and dirty checkbox --- TODO | 2 +- src/main.c3 | 1 + src/ugui_button.c3 | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/TODO b/TODO index 0caf07d..9b412a2 100644 --- a/TODO +++ b/TODO @@ -77,5 +77,5 @@ _ border radius [x] Button with label [ ] Text Input box [ ] Icon Buttons -[ ] Switch +[x] Switch [x] Checkbox diff --git a/src/main.c3 b/src/main.c3 index 7382ebc..7b710fd 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -236,6 +236,7 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; ui.checkbox("check1", "", Point{}, &check, "tick")!!; + ui.toggle("toggle1", "", Point{}, &toggle)!!; /* ui.layout_set_column()!!; diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index c7089f4..cd76206 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -132,3 +132,45 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; } } + +// FIXME: this should be inside the style +const short DEFAULT_SWITCH_SIZE = 16; +fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* state) +{ + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // FIXME: for now switches and buttons have no members so the element types + // can be the same + if (elem.flags.is_new) { + elem.type = ETYPE_BUTTON; + } else if (elem.type != ETYPE_BUTTON) { + return UgError.WRONG_ELEMENT_TYPE?; + } + + Rect size = {off.x, off.y, DEFAULT_SWITCH_SIZE*2, DEFAULT_SWITCH_SIZE}; + elem.bounds = ctx.position_element(parent, size, true); + + // if the bounds are null the element is outside the div view, + // no interaction should occur so just return + if (elem.bounds.is_null()) return; + + elem.events = ctx.get_elem_events(elem); + if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + + Color col; + if (*state) { + col = 0xff0000ffu.to_rgba(); + } else { + col = 0xff00ffffu.to_rgba(); + } + // Draw the button + // FIXME: THIS IS SHIT + ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!; + Rect t = elem.bounds.add(Rect{*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); + ctx.push_rect(t, col, do_border: false, do_radius: true)!; +} From 34e75f8c06ee1f44c0bcd3c208e95206f8556b6e Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 8 Feb 2025 12:51:10 +0100 Subject: [PATCH 091/250] larger font cache --- src/ugui_font.c3 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 762c9f3..535432d 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -39,7 +39,7 @@ struct Glyph { short adv, ox, oy; } -const uint FONT_CACHED = 128; +const uint FONT_CACHED = 255; def GlyphTable = map::HashMap() @private; fault UgFontError { From 79a2d66880bea156e4d81c5bde2d2b4d6b11f368 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 5 May 2025 16:23:26 +0200 Subject: [PATCH 092/250] update project to c3 0.7.1 --- TODO | 34 ++++++++++++++-- lib/schrift.c3l/libschrift.c3 | 6 +-- src/cache.c3 | 31 ++++++++------- src/fifo.c3 | 17 ++++---- src/main.c3 | 74 ++++++++++++++++++----------------- src/ugui_atlas.c3 | 43 ++++++++++++++++---- src/ugui_button.c3 | 65 ++++++++++++++++++++++++------ src/ugui_cmd.c3 | 12 +++--- src/ugui_core.c3 | 37 ++++++++---------- src/ugui_div.c3 | 12 +++--- src/ugui_font.c3 | 43 ++++++++++---------- src/ugui_input.c3 | 8 ++-- src/ugui_layout.c3 | 32 +++++++-------- src/ugui_shapes.c3 | 34 ++++++++-------- src/ugui_slider.c3 | 8 ++-- src/ugui_sprite.c3 | 40 ++++++++++--------- src/ugui_text.c3 | 2 +- src/vtree.c3 | 64 ++++++++++++++---------------- 18 files changed, 324 insertions(+), 238 deletions(-) diff --git a/TODO b/TODO index 9b412a2..1817dd9 100644 --- a/TODO +++ b/TODO @@ -13,6 +13,8 @@ to maintain focus until mouse release (fix scroll bars) [x] Clip element bounds to parent div, specifically text [ ] Resizeable divs [ ] Implement a z index and sort command buffer based on that + [ ] Ctx.set_z_index() + [ ] Sort command buffer on insertion [ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it [x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division [ ] Animations, somehow @@ -33,12 +35,19 @@ to maintain focus until mouse release (fix scroll bars) [ ] Subdivide modules into ugui::ug for exported functions and ugui::core for internal use functions (used to create widgets) [ ] The render loop RAPES the gpu, valve pls fix +[ ] The way the element structures are implemented wastes a lot of memory since + each struct Elem, struct Cmd, etc. is as big as the largest element. It would + be better to use a different allcation strategy. +[ ] Add a way to handle time events like double clicks ## Layout [ ] Text reflow [x] Flexbox [ ] Center elements to the row/column +[ ] Text wrapping +[ ] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay) + instead of the curren multi-frame approach. ## Input @@ -51,15 +60,16 @@ to maintain focus until mouse release (fix scroll bars) ## Commands [x] rect commads should have: -_ border width -_ border radius + - border width + - border radius [x] add a command to update an atlas [ ] New window command, useful for popups -## Atlases +## Atlas [ ] Add an interface to create, destroy, update and get atlases based on their ids [ ] Implement multiple font atlases +[ ] Pixel format conversion ## Fonts @@ -79,3 +89,21 @@ _ border radius [ ] Icon Buttons [x] Switch [x] Checkbox +[ ] Selectable text box + +## Main / exaple + +[ ] Create maps from ids to textures and images instead of hardcoding them + + +## API + +[ ] Introduce a Layout structure that specifies the positioning of elements inside + a Div element. This would allow specifying alignment, maximum and minimum sizing + margins between children, etc. + This is different from style which is applied per-element. +[ ] Remove Ids for element that don't need them. Such elements are button, toggles, + and all elements which do not have internal data that has to be cached and/or + queried by the user for later use. This allows for smaller caches and in general + reduces some load, since most of the stuff is recomputed for every frame. + diff --git a/lib/schrift.c3l/libschrift.c3 b/lib/schrift.c3l/libschrift.c3 index dde82a5..699d296 100644 --- a/lib/schrift.c3l/libschrift.c3 +++ b/lib/schrift.c3l/libschrift.c3 @@ -1,8 +1,8 @@ module schrift; -def SftFont = void*; -def SftUChar = uint; -def SftGlyph = uint; +alias SftFont = void*; +alias SftUChar = uint; +alias SftGlyph = uint; const int SFT_DOWNWARD_Y = 0x01; diff --git a/src/cache.c3 b/src/cache.c3 index fac5cf2..130ffd1 100644 --- a/src/cache.c3 +++ b/src/cache.c3 @@ -1,4 +1,4 @@ -module cache(); +module cache{Key, Value, SIZE}; /* LRU Cache * The cache uses a pool (array) to store all the elements, each element has @@ -14,12 +14,13 @@ module cache(); // happens at the same time import std::core::mem; +import std::core::mem::allocator; import std::collections::bitset; import std::collections::map; -def BitArr = bitset::BitSet() @private; -def IdTable = map::HashMap() @private; -def IdTableEntry = map::Entry() @private; +alias BitArr = bitset::BitSet{SIZE}; +alias IdTable = map::HashMap{Key, usz}; +alias IdTableEntry = map::Entry{Key, usz}; const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); @@ -42,9 +43,9 @@ macro Cache.cycle(&cache) @private { } } -fn void! Cache.init(&cache) +fn void? Cache.init(&cache) { - cache.table.new_init(capacity: SIZE); + cache.table.init(allocator::heap(), capacity: SIZE); // FIXME: this shit is SLOW foreach (idx, bit : cache.used) { cache.used[idx] = false; } foreach (idx, bit : cache.present) { cache.present[idx] = false; } @@ -57,7 +58,7 @@ fn void Cache.free(&cache) (void)mem::free(cache.pool); } -fn Value*! Cache.search(&cache, Key id) +fn Value*? Cache.search(&cache, Key id) { // get_entry() faults on miss IdTableEntry* entry = cache.table.get_entry(id)!; @@ -65,14 +66,14 @@ fn Value*! Cache.search(&cache, Key id) /* MISS, wrong key */ if (entry.key != id) { cache.table.remove(id)!; - return SearchResult.MISSING?; + return NOT_FOUND?; } /* MISS, the data is not valid (not present) */ if (!cache.present[entry.value]) { // if the data is not present but it is still in the table, remove it cache.table.remove(id)!; - return SearchResult.MISSING?; + return NOT_FOUND?; } /* HIT, set as recently used */ @@ -82,7 +83,7 @@ fn Value*! Cache.search(&cache, Key id) fn void Cache.remove(&cache, Key id) { - IdTableEntry*! entry = cache.table.get_entry(id); + IdTableEntry*? entry = cache.table.get_entry(id); if (catch entry) { return; } @@ -105,7 +106,7 @@ fn usz Cache.get_free_spot(&cache) @private return 0; } -fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private +fn Value*? Cache.insert_at(&cache, Value *g, Key id, usz index) @private { // TODO: verify index, g and id Value* spot; @@ -122,17 +123,17 @@ fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private } // Insert an element in the cache, returns the index -fn Value*! Cache.insert_new(&cache, Value* g, Key id) +fn Value*? Cache.insert_new(&cache, Value* g, Key id) { usz index = cache.get_free_spot(); return cache.insert_at(g, id, index); } -fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) +fn Value*? Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) { - Value*! c = cache.search(id); + Value*? c = cache.search(id); if (catch e = c) { - if (e != SearchResult.MISSING) { + if (e != NOT_FOUND) { return e?; } else { // if the element is new (inserted) set the is_new flag diff --git a/src/fifo.c3 b/src/fifo.c3 index dbc50f0..b4c49d4 100644 --- a/src/fifo.c3 +++ b/src/fifo.c3 @@ -1,11 +1,8 @@ -module fifo(); +module fifo{Type}; import std::core::mem; -fault FifoErr { - FULL, - EMPTY, -} +faultdef FULL, EMPTY; // TODO: specify the allocator @@ -15,7 +12,7 @@ struct Fifo { usz count; } -fn void! Fifo.init(&fifo, usz size) +fn void? Fifo.init(&fifo, usz size) { fifo.arr = mem::new_array(Type, size); fifo.out = 0; @@ -27,20 +24,20 @@ fn void Fifo.free(&fifo) (void)mem::free(fifo.arr); } -fn void! Fifo.enqueue(&fifo, Type *elem) +fn void? Fifo.enqueue(&fifo, Type *elem) { if (fifo.count >= fifo.arr.len) { - return FifoErr.FULL?; + return FULL?; } usz in = (fifo.out + fifo.count) % fifo.arr.len; fifo.arr[in] = *elem; fifo.count++; } -fn Type*! Fifo.dequeue(&fifo) +fn Type*? Fifo.dequeue(&fifo) { if (fifo.count == 0) { - return FifoErr.EMPTY?; + return EMPTY?; } Type *ret = &fifo.arr[fifo.out]; fifo.count--; diff --git a/src/main.c3 b/src/main.c3 index 7b710fd..e0c1737 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -7,7 +7,7 @@ import std::time; import std::collections::ringbuffer; import std::core::string; -def Times = ringbuffer::RingBuffer(); +alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]}; fn void Times.print_stats(×) { @@ -40,7 +40,7 @@ fn TimeStats Times.get_stats(×) } avg = (NanoDuration)((ulong)avg/128.0); - return TimeStats{.min = min, .max = max, .avg = avg}; + return {.min = min, .max = max, .avg = avg}; } @@ -115,12 +115,12 @@ void main() macro rl::Color ugui::Color.conv(color) { - return rl::Color{.r = color.r, .g = color.g, .b = color.b, .a = color.a}; + return {.r = color.r, .g = color.g, .b = color.b, .a = color.a}; } macro rl::Rectangle Rect.conv(rect) { - return rl::Rectangle{.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; + return {.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; } fn int main(String[] args) @@ -169,11 +169,13 @@ fn int main(String[] args) mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL); ui.input_mod_keys(mod); + /* for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) { ZString kname = rl::getKeyName(key); if (kname == null) continue; ui.input_text_unicode(kname.str_view()); } + */ /* Start Input Handling */ @@ -203,29 +205,29 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; - ui.div_begin("main", Rect{.w=-100})!!; - {| + ui.div_begin("main", {.w=-100})!!; + { ui.layout_set_column()!!; - if (ui.button("button0", Rect{0,0,30,30}, toggle)!!.mouse_press) { + if (ui.button("button0", {0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; } //ui.layout_next_column()!!; - if (ui.button("button1", Rect{0,0,30,30})!!.mouse_press) { + if (ui.button("button1", {0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; - if (ui.button("button2", Rect{0,0,30,30})!!.mouse_release) { + if (ui.button("button2", {0,0,30,30})!!.mouse_release) { io::printn("release button2"); } ui.layout_set_row()!!; ui.layout_next_row()!!; static float rf, gf, bf, af; - ui.slider_ver("slider_r", Rect{0,0,30,100}, &rf)!!; - ui.slider_ver("slider_g", Rect{0,0,30,100}, &gf)!!; - ui.slider_ver("slider_b", Rect{0,0,30,100}, &bf)!!; - ui.slider_ver("slider_a", Rect{0,0,30,100}, &af)!!; + ui.slider_ver("slider_r", {0,0,30,100}, &rf)!!; + ui.slider_ver("slider_g", {0,0,30,100}, &gf)!!; + ui.slider_ver("slider_b", {0,0,30,100}, &bf)!!; + ui.slider_ver("slider_a", {0,0,30,100}, &af)!!; ui.layout_next_column()!!; ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; @@ -235,8 +237,8 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", Point{}, &check, "tick")!!; - ui.toggle("toggle1", "", Point{}, &toggle)!!; + ui.checkbox("check1", "", {}, &check, "tick")!!; + ui.toggle("toggle1", "", {}, &toggle)!!; /* ui.layout_set_column()!!; @@ -248,33 +250,33 @@ fn int main(String[] args) ui.layout_next_row()!!; ui.button_label(" E ")!!; */ - |}; + }; ui.draw_sprite("sprite1", "tux")!!; ui.div_end()!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; - {| + { ui.layout_set_column()!!; static float slider2 = 0.5; - if (ui.slider_ver("slider", Rect{0,0,30,100}, &slider2)!!.update) { + if (ui.slider_ver("slider", {0,0,30,100}, &slider2)!!.update) { io::printfn("other slider: %f", slider2); } - ui.button("button0", Rect{0,0,50,50})!!; - ui.button("button1", Rect{0,0,50,50})!!; - ui.button("button2", Rect{0,0,50,50})!!; - ui.button("button3", Rect{0,0,50,50})!!; + ui.button("button0", {0,0,50,50})!!; + ui.button("button1", {0,0,50,50})!!; + ui.button("button2", {0,0,50,50})!!; + ui.button("button3", {0,0,50,50})!!; if (toggle) { - ui.button("button4", Rect{0,0,50,50})!!; - ui.button("button5", Rect{0,0,50,50})!!; - ui.button("button6", Rect{0,0,50,50})!!; - ui.button("button7", Rect{0,0,50,50})!!; + ui.button("button4", {0,0,50,50})!!; + ui.button("button5", {0,0,50,50})!!; + ui.button("button6", {0,0,50,50})!!; + ui.button("button7", {0,0,50,50})!!; } ui.layout_next_column()!!; ui.layout_set_row()!!; static float f1, f2; - ui.slider_hor("hs1", Rect{0,0,100,30}, &f1)!!; - ui.slider_hor("hs2", Rect{0,0,100,30}, &f2)!!; - |}; + ui.slider_hor("hs1", {0,0,100,30}, &f1)!!; + ui.slider_hor("hs2", {0,0,100,30}, &f2)!!; + }; ui.div_end()!!; // Timings counter @@ -282,12 +284,12 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", Rect{0, ui.height-100, -300, 100})!!; - {| + ui.div_begin("fps", {0, ui.height-100, -300, 100})!!; + { ui.layout_set_column()!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; - |}; + }; ui.div_end()!!; ui.frame_end()!!; @@ -341,15 +343,15 @@ fn int main(String[] args) rl::endShaderMode(); } else if (cmd.sprite.texture_id == sprite_id) { // FIXME: THIS CODE IS SHIT, REAL DOO DOO - {| + { if (cmd.sprite.type == SpriteType.SPRITE_MSDF) { rl::beginShaderMode(msdf_shader); defer rl::endShaderMode(); - rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); + rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); } else { - rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); + rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); } - |}; + }; } else { io::printfn("unknown texture id: %d", cmd.sprite.texture_id); } diff --git a/src/ugui_atlas.c3 b/src/ugui_atlas.c3 index 8745fd7..7f3a40b 100644 --- a/src/ugui_atlas.c3 +++ b/src/ugui_atlas.c3 @@ -2,10 +2,7 @@ module ugui; import std::io; -fault UgAtlasError { - CANNOT_PLACE, - INVALID_TYPE, -} +faultdef CANNOT_PLACE, INVALID_TYPE; enum AtlasType { ATLAS_GRAYSCALE, @@ -33,7 +30,39 @@ macro usz AtlasType.bpp(type) } } -fn void! Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height) +macro typeid AtlasType.underlying(type) +{ + switch (type) { + case ATLAS_GRAYSCALE: return char; + case ATLAS_R8G8B8A8: return uint; + } +} + +/* +// FIXME: in and out types are not always known at compile time +macro @pixel_convert(p, AtlasType $in, AtlasType $out) +{ + $if $in == $out: + return p; + $else + $switch + $case $in == ATLAS_R8G8B8A8 && $out == ATLAS_GRAYSCALE: + var r = ((p >> 0) & 0xff); + var g = ((p >> 8) & 0xff); + var b = ((p >> 16) & 0xff); + var a = ((p >> 24) & 0xff); + if (a == 0) return (char)0; + return (ATLAS_GRAYSCALE.underlying())(((float)r+g+b) / 3.0f); + $case $in == ATLAS_GRAYSCALE && $out == ATLAS_R8G8B8A8: + var x = (char)(p/3.0); + return (ATLAS_R8G8B8A8.underlying())(x|(x<<8)|(x<<16)|(255<<24)); + $default: $error "Unimplemented pixel format conversion"; + $endswitch + $endif +} +*/ + +fn void? Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height) { atlas.id = id; atlas.type = type; @@ -66,7 +95,7 @@ fn void Atlas.free(&atlas) // place a rect inside the atlas // uses a row first algorithm // TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ -fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride) +fn Point? Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride) { Point p; @@ -79,7 +108,7 @@ fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride) if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { p = atlas.row; } else { - return UgAtlasError.CANNOT_PLACE?; + return CANNOT_PLACE?; } } diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index cd76206..f9582bc 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -10,7 +10,7 @@ struct ElemButton { // draw a button, return the events on that button // FIXME: "state" should be renamed "active" to toggle between an usable button and // an inactive (greyed-out) button -fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) +fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) { Id id = ctx.gen_id(label)!; @@ -22,14 +22,14 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) if (elem.flags.is_new) { elem.type = ETYPE_BUTTON; } else if (elem.type != ETYPE_BUTTON) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } elem.bounds = ctx.position_element(parent, size, true); // if the bounds are null the element is outside the div view, // no interaction should occur so just return - if (elem.bounds.is_null()) { return ElemEvents{}; } + if (elem.bounds.is_null()) { return {}; } Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); @@ -45,7 +45,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) return elem.events; } -fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.max,short.max}, bool state = false) +fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool state = false) { Id id = ctx.gen_id(label)!; @@ -60,11 +60,11 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; Rect text_size = ctx.get_text_bounds(label)!; - Rect btn_size = text_size.add(Rect{0,0,10,10}); + Rect btn_size = text_size.add({0,0,10,10}); // 2. Layout elem.bounds = ctx.position_element(parent, btn_size, true); - if (elem.bounds.is_null()) { return ElemEvents{}; } + if (elem.bounds.is_null()) { return {}; } Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); @@ -86,9 +86,50 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m return elem.events; } +fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = "", bool state = false) +{ + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + if (elem.flags.is_new) { + elem.type = ETYPE_BUTTON; + } else if (elem.type != ETYPE_BUTTON) { + return WRONG_ELEMENT_TYPE?; + } + + Sprite* def_sprite = ctx.sprite_atlas.get(icon)!; + Sprite* on_sprite = ctx.sprite_atlas.get(on_icon) ?? &&(Sprite){}; + Rect max_size = def_sprite.rect().max(on_sprite.rect()); + + elem.bounds = ctx.position_element(parent, max_size, true); + + // if the bounds are null the element is outside the div view, + // no interaction should occur so just return + if (elem.bounds.is_null()) { return {}; } + + Color col = 0x0000ffffu.to_rgba(); + elem.events = ctx.get_elem_events(elem); + + Id tex_id = ctx.sprite_atlas.id; + if (state && on_icon != "") { + ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, type: on_sprite.type)!; + } else { + ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, type: def_sprite.type)!; + } + + // Draw the button + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + + return elem.events; +} + // FIXME: this should be inside the style const ushort DEFAULT_CHECKBOX_SIZE = 16; -fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) +fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) { Id id = ctx.gen_id(label)!; @@ -102,7 +143,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s if (elem.flags.is_new) { elem.type = ETYPE_BUTTON; } else if (elem.type != ETYPE_BUTTON) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } Rect size = {off.x, off.y, DEFAULT_CHECKBOX_SIZE, DEFAULT_CHECKBOX_SIZE}; @@ -116,7 +157,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); Color col; - if (tick_sprite != String{}) { + if (tick_sprite != {}) { col = ctx.style.bgcolor; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; if (*state) { @@ -135,7 +176,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s // FIXME: this should be inside the style const short DEFAULT_SWITCH_SIZE = 16; -fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* state) +fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* state) { Id id = ctx.gen_id(label)!; @@ -149,7 +190,7 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta if (elem.flags.is_new) { elem.type = ETYPE_BUTTON; } else if (elem.type != ETYPE_BUTTON) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } Rect size = {off.x, off.y, DEFAULT_SWITCH_SIZE*2, DEFAULT_SWITCH_SIZE}; @@ -171,6 +212,6 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta // Draw the button // FIXME: THIS IS SHIT ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!; - Rect t = elem.bounds.add(Rect{*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); + Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); ctx.push_rect(t, col, do_border: false, do_radius: true)!; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 9524dc3..eee78d4 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -68,7 +68,7 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd) // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) -fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) +fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) { Rect border = ctx.style.border; Rect padding = ctx.style.padding; @@ -101,7 +101,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo } // TODO: add texture id -fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) +fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) { Cmd cmd = { .type = CMD_SPRITE, @@ -114,7 +114,7 @@ fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color h ctx.push_cmd(&cmd)!; } -fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba()) +fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba()) { if (text.len == 0) { return; @@ -163,10 +163,10 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu } // FIXME: we never get here if an error was thrown before - ctx.push_scissor(Rect{})!; + ctx.push_scissor({})!; } -fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) +fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas) { Cmd up = { .type = CMD_UPDATE_ATLAS, @@ -181,7 +181,7 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) ctx.push_cmd(&up)!; } -fn void! Ctx.push_scissor(&ctx, Rect rect) +fn void? Ctx.push_scissor(&ctx, Rect rect) { Cmd sc = { .type = CMD_SCISSOR, diff --git a/src/ugui_core.c3 b/src/ugui_core.c3 index c330efe..8c4cf75 100644 --- a/src/ugui_core.c3 +++ b/src/ugui_core.c3 @@ -9,7 +9,7 @@ import std::core::string; // element ids are just long ints -def Id = usz; +alias Id = usz; enum ElemType { ETYPE_NONE, @@ -54,20 +54,15 @@ struct Elem { // relationships between elements are stored in a tree, it stores just the ids -def IdTree = vtree::VTree() @private; +alias IdTree = vtree::VTree{Id}; // elements themselves are kept in a cache const uint MAX_ELEMENTS = 256; -def ElemCache = cache::Cache() @private; +alias ElemCache = cache::Cache{Id, Elem, MAX_ELEMENTS}; -def CmdQueue = fifo::Fifo(); +alias CmdQueue = fifo::Fifo{Cmd}; -fault UgError { - INVALID_SIZE, - EVENT_UNSUPPORTED, - UNEXPECTED_ELEMENT, - WRONG_ELEMENT_TYPE, -} +faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE; const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; @@ -128,7 +123,7 @@ struct Ctx { } // return a pointer to the parent of the current active div -fn Elem*! Ctx.get_parent(&ctx) +fn Elem*? Ctx.get_parent(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; return ctx.cache.search(parent_id); @@ -140,7 +135,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9; // generate an id combining the hashes of the parent id and the label // with the Cantor pairing function -macro Id! Ctx.gen_id(&ctx, String label) +macro Id? Ctx.gen_id(&ctx, String label) { Id id1 = ctx.tree.get(ctx.active_div)!; Id id2 = label.hash(); @@ -153,7 +148,7 @@ macro Id! Ctx.gen_id(&ctx, String label) // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly -fn Elem*! Ctx.get_elem(&ctx, Id id) +fn Elem*? Ctx.get_elem(&ctx, Id id) { Elem empty_elem; bool is_new; @@ -170,10 +165,10 @@ fn Elem*! Ctx.get_elem(&ctx, Id id) // THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE macro Elem* Ctx.find_elem(&ctx, Id id) { - Elem*! elem; + Elem*? elem; elem = ctx.cache.search(id); if (catch elem) { - return &&Elem{}; + return &&(Elem){}; } return elem; } @@ -194,7 +189,7 @@ macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private return ctx.cache.search(id); } -fn void! Ctx.init(&ctx) +fn void? Ctx.init(&ctx) { ctx.tree.init(MAX_ELEMENTS)!; defer catch { (void)ctx.tree.free(); } @@ -208,9 +203,9 @@ fn void! Ctx.init(&ctx) ctx.active_div = 0; // TODO: add style config - ctx.style.margin = Rect{2, 2, 2, 2}; - ctx.style.border = Rect{2, 2, 2, 2}; - ctx.style.padding = Rect{1, 1, 1, 1}; + ctx.style.margin = {2, 2, 2, 2}; + ctx.style.border = {2, 2, 2, 2}; + ctx.style.padding = {1, 1, 1, 1}; ctx.style.radius = 5; ctx.style.bgcolor = 0x282828ffu.to_rgba(); ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba(); @@ -226,7 +221,7 @@ fn void Ctx.free(&ctx) (void)ctx.sprite_atlas.free(); } -fn void! Ctx.frame_begin(&ctx) +fn void? Ctx.frame_begin(&ctx) { // 2. Get the root element from the cache and update it @@ -268,7 +263,7 @@ fn void! Ctx.frame_begin(&ctx) // TODO: add a background color taken from a theme or config } -fn void! Ctx.frame_end(&ctx) +fn void? Ctx.frame_end(&ctx) { Elem* root = ctx.get_elem_by_tree_idx(0)!; root.div.layout = LAYOUT_ROW; diff --git a/src/ugui_div.c3 b/src/ugui_div.c3 index da175cf..bff69a3 100644 --- a/src/ugui_div.c3 +++ b/src/ugui_div.c3 @@ -28,7 +28,7 @@ struct ElemDiv { // if the width or height are negative the width or height will be calculated based on the children size // sort similar to a flexbox, and the minimum size is set by the negative of the width or height // FIXME: there is a bug if the size.w or size.h == -0 -fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) +fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) { Id id = ctx.gen_id(label)!; @@ -42,7 +42,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo if (elem.flags.is_new) { elem.type = ETYPE_DIV; } else if (elem.type != ETYPE_DIV) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; @@ -55,16 +55,16 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo .h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h, }; elem.bounds = ctx.position_element(parent, wanted_size); - elem.div.children_bounds = Rect{}; + elem.div.children_bounds = {}; // update the ctx scissor ctx.div_scissor = elem.bounds; ctx.push_scissor(elem.bounds)!; // 4. Fill the div fields - elem.div.origin_c = Point{ + elem.div.origin_c = { .x = elem.bounds.x, - .y = elem.bounds.y, + .y = elem.bounds.y }; elem.div.origin_r = elem.div.origin_c; elem.div.layout = parent.div.layout; @@ -79,7 +79,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo // TODO: check resizeable } -fn void! Ctx.div_end(&ctx) +fn void? Ctx.div_end(&ctx) { // swap the children bounds Elem* parent = ctx.get_parent()!; diff --git a/src/ugui_font.c3 b/src/ugui_font.c3 index 535432d..5afe033 100644 --- a/src/ugui_font.c3 +++ b/src/ugui_font.c3 @@ -4,13 +4,15 @@ import schrift; import grapheme; import std::collections::map; import std::core::mem; +import std::core::mem::allocator; import std::io; import std::ascii; // unicode code point, different type for a different hash -def Codepoint = uint; +alias Codepoint = uint; +//macro uint Codepoint.hash(self) => ((uint)self).hash(); /* width and height of a glyph contain the kering advance * (u,v) @@ -40,14 +42,9 @@ struct Glyph { } const uint FONT_CACHED = 255; -def GlyphTable = map::HashMap() @private; +alias GlyphTable = map::HashMap{Codepoint, Glyph}; -fault UgFontError { - TTF_LOAD_FAILED, - MISSING_GLYPH, - BAD_GLYPH_METRICS, - RENDER_ERROR, -} +faultdef TTF_LOAD_FAILED, MISSING_GLYPH, BAD_GLYPH_METRICS, RENDER_ERROR; struct Font { schrift::Sft sft; @@ -61,14 +58,14 @@ struct Font { bool should_update; // should send update_atlas command, resets at frame_end() } -fn void! Font.load(&font, String name, ZString path, uint height, float scale) +fn void? Font.load(&font, String name, ZString path, uint height, float scale) { - font.table.new_init(capacity: FONT_CACHED); + font.table.init(allocator::heap(), capacity: FONT_CACHED); font.id = name.hash(); font.size = height*scale; - font.sft = schrift::Sft{ + font.sft = { .xScale = (double)font.size, .yScale = (double)font.size, .flags = schrift::SFT_DOWNWARD_Y, @@ -77,7 +74,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale) font.sft.font = schrift::loadfile(path); if (font.sft.font == null) { font.table.free(); - return UgFontError.TTF_LOAD_FAILED?; + return TTF_LOAD_FAILED?; } schrift::SftLMetrics lmetrics; @@ -98,13 +95,13 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale) } } -fn Glyph*! Font.get_glyph(&font, Codepoint code) +fn Glyph*? Font.get_glyph(&font, Codepoint code) { - Glyph*! gp; + Glyph*? gp; gp = font.table.get_ref(code); if (catch excuse = gp) { - if (excuse != SearchResult.MISSING) { + if (excuse != NOT_FOUND) { return excuse?; } } else { @@ -117,12 +114,12 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code) schrift::SftGlyph gid; schrift::SftGMetrics gmtx; - if (schrift::lookup(&font.sft, code, &gid) < 0) { - return UgFontError.MISSING_GLYPH?; + if (schrift::lookup(&font.sft, (SftUChar)code, &gid) < 0) { + return MISSING_GLYPH?; } if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) { - return UgFontError.BAD_GLYPH_METRICS?; + return BAD_GLYPH_METRICS?; } schrift::SftImage img = { @@ -132,7 +129,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code) char[] pixels = mem::new_array(char, (usz)img.width * img.height); img.pixels = pixels; if (schrift::render(&font.sft, gid, img) < 0) { - return UgFontError.RENDER_ERROR?; + return RENDER_ERROR?; } glyph.code = code; @@ -163,7 +160,7 @@ fn void Font.free(&font) schrift::freefont(font.sft.font); } -fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0) +fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0) { return ctx.font.load(name, path, height, scale); } @@ -174,7 +171,7 @@ fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale fn Codepoint str_to_codepoint(char[] str, usz* off) { Codepoint cp; - isz b = grapheme::decode_utf8(str, str.len, &cp); + isz b = grapheme::decode_utf8(str, str.len, (uint*)&cp); if (b == 0 || b > str.len) { return 0; } @@ -182,7 +179,7 @@ fn Codepoint str_to_codepoint(char[] str, usz* off) return cp; } -fn Rect! Ctx.get_text_bounds(&ctx, String text) +fn Rect? Ctx.get_text_bounds(&ctx, String text) { Rect text_bounds; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; @@ -219,7 +216,7 @@ fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds) short dw = bounds.w - text_bounds.w; short dh = bounds.h - text_bounds.h; - return Point{.x = dw/2, .y = dh/2}; + return {.x = dw/2, .y = dh/2}; } // TODO: check if the font is present in the context diff --git a/src/ugui_input.c3 b/src/ugui_input.c3 index 1911d9f..abad51c 100644 --- a/src/ugui_input.c3 +++ b/src/ugui_input.c3 @@ -45,10 +45,10 @@ const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true}; const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true}; const ModKeys KMOD_ALT = {.lalt = true, .ralt = true}; const ModKeys KMOD_GUI = {.lgui = true, .rgui = true}; -const ModKeys KMOD_NONE = ModKeys{}; +const ModKeys KMOD_NONE = {}; const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max); -const MouseButtons BTN_NONE = MouseButtons{}; +const MouseButtons BTN_NONE = {}; const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max); const MouseButtons BTN_LEFT = {.btn_left = true}; const MouseButtons BTN_MIDDLE = {.btn_middle = true}; @@ -72,10 +72,10 @@ fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String keys) } // Window size was changed -fn void! Ctx.input_window_size(&ctx, short width, short height) +fn void? Ctx.input_window_size(&ctx, short width, short height) { if (width <= 0 || height <= 0) { - return UgError.INVALID_SIZE?; + return INVALID_SIZE?; } ctx.input.events.resize = ctx.width != width || ctx.height != height; ctx.width = width; diff --git a/src/ugui_layout.c3 b/src/ugui_layout.c3 index 3a2ac9a..feba610 100644 --- a/src/ugui_layout.c3 +++ b/src/ugui_layout.c3 @@ -7,71 +7,71 @@ enum Layout { LAYOUT_ABSOLUTE, } -fn void! Ctx.layout_set_row(&ctx) +fn void? Ctx.layout_set_row(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { // what? - return UgError.UNEXPECTED_ELEMENT?; + return UNEXPECTED_ELEMENT?; } parent.div.layout = LAYOUT_ROW; } -fn void! Ctx.layout_set_column(&ctx) +fn void? Ctx.layout_set_column(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { // what? - return UgError.UNEXPECTED_ELEMENT?; + return UNEXPECTED_ELEMENT?; } parent.div.layout = LAYOUT_COLUMN; } -fn void! Ctx.layout_set_floating(&ctx) +fn void? Ctx.layout_set_floating(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { // what? - return UgError.UNEXPECTED_ELEMENT?; + return UNEXPECTED_ELEMENT?; } parent.div.layout = LAYOUT_FLOATING; } -fn void! Ctx.layout_next_row(&ctx) +fn void? Ctx.layout_next_row(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { - return UgError.UNEXPECTED_ELEMENT?; + return UNEXPECTED_ELEMENT?; } - parent.div.origin_r = Point{ + parent.div.origin_r = { .x = parent.bounds.x, .y = parent.div.children_bounds.bottom_right().y, }; parent.div.origin_c = parent.div.origin_r; } -fn void! Ctx.layout_next_column(&ctx) +fn void? Ctx.layout_next_column(&ctx) { Id parent_id = ctx.tree.get(ctx.active_div)!; Elem *parent = ctx.cache.search(parent_id)!; if (parent.type != ETYPE_DIV) { - return UgError.UNEXPECTED_ELEMENT?; + return UNEXPECTED_ELEMENT?; } - parent.div.origin_c = Point{ + parent.div.origin_c = { .x = parent.div.children_bounds.bottom_right().x, .y = parent.bounds.y, }; @@ -124,7 +124,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect return rect; default: // error - return Rect{}; + return {}; } // 2. Compute the parent's view @@ -166,11 +166,11 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) child_occupied = child_occupied.grow(rect.size()); // 4. Update the parent's origin - div.origin_r = Point{ + div.origin_r = { .x = child_occupied.bottom_right().x, .y = origin.y, }; - div.origin_c = Point{ + div.origin_c = { .x = origin.x, .y = child_occupied.bottom_right().y, }; @@ -191,6 +191,6 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false) if (child_placement.collides(parent_view)) { return child_placement.off(parent.get_view_off().neg()); } else { - return Rect{}; + return {}; } } diff --git a/src/ugui_shapes.c3 b/src/ugui_shapes.c3 index 39a782c..ebef525 100644 --- a/src/ugui_shapes.c3 +++ b/src/ugui_shapes.c3 @@ -19,7 +19,7 @@ macro bool Rect.contains(Rect a, Rect b) // returns the intersection of a and b macro Rect Rect.intersection(Rect a, Rect b) { - return Rect{ + return { .x = (short)max(a.x, b.x), .y = (short)max(a.y, b.y), .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), @@ -39,7 +39,7 @@ macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0; // returns the element-wise addition of r1 and r2 macro Rect Rect.add(Rect r1, Rect r2) { - return Rect{ + return { .x = r1.x + r2.x, .y = r1.y + r2.y, .w = r1.w + r2.w, @@ -50,7 +50,7 @@ macro Rect Rect.add(Rect r1, Rect r2) // returns the element-wise subtraction of r1 and r2 macro Rect Rect.sub(Rect r1, Rect r2) { - return Rect{ + return { .x = r1.x - r2.x, .y = r1.y - r2.y, .w = r1.w - r2.w, @@ -61,7 +61,7 @@ macro Rect Rect.sub(Rect r1, Rect r2) // returns the element-wise multiplication of r1 and r2 macro Rect Rect.mul(Rect r1, Rect r2) { - return Rect{ + return { .x = r1.x * r2.x, .y = r1.y * r2.y, .w = r1.w * r2.w, @@ -71,7 +71,7 @@ macro Rect Rect.mul(Rect r1, Rect r2) macro Point Rect.position(Rect r) { - return Point{ + return { .x = r.x, .y = r.y, }; @@ -79,7 +79,7 @@ macro Point Rect.position(Rect r) macro Point Rect.size(Rect r) { - return Point{ + return { .x = r.w, .y = r.h, }; @@ -87,7 +87,7 @@ macro Point Rect.size(Rect r) macro Rect Rect.max(Rect a, Rect b) { - return Rect{ + return { .x = max(a.x, b.x), .y = max(a.y, b.y), .w = max(a.w, b.w), @@ -97,7 +97,7 @@ macro Rect Rect.max(Rect a, Rect b) macro Rect Rect.min(Rect a, Rect b) { - return Rect{ + return { .x = min(a.x, b.x), .y = min(a.y, b.y), .w = min(a.w, b.w), @@ -108,7 +108,7 @@ macro Rect Rect.min(Rect a, Rect b) // Offset a rect by a point macro Rect Rect.off(Rect r, Point p) { - return Rect{ + return { .x = r.x + p.x, .y = r.y + p.y, .w = r.w, @@ -119,7 +119,7 @@ macro Rect Rect.off(Rect r, Point p) // Resize a rect width and height macro Rect Rect.grow(Rect r, Point p) { - return Rect{ + return { .x = r.x, .y = r.y, .w = r.w + p.x, @@ -130,7 +130,7 @@ macro Rect Rect.grow(Rect r, Point p) // Return the bottom-right corner of a rectangle macro Point Rect.bottom_right(Rect r) { - return Point{ + return { .x = r.x + r.w, .y = r.y + r.h, }; @@ -153,7 +153,7 @@ macro bool Point.in_rect(Point p, Rect r) macro Point Point.add(Point a, Point b) { - return Point{ + return { .x = a.x + b.x, .y = a.y + b.y, }; @@ -161,17 +161,17 @@ macro Point Point.add(Point a, Point b) macro Point Point.sub(Point a, Point b) { - return Point{ + return { .x = a.x - b.x, .y = a.y - b.y, }; } -macro Point Point.neg(Point p) => Point{-p.x, -p.y}; +macro Point Point.neg(Point p) => {-p.x, -p.y}; macro Point Point.max(Point a, Point b) { - return Point{ + return { .x = max(a.x, b.x), .y = max(a.y, b.y), }; @@ -179,7 +179,7 @@ macro Point Point.max(Point a, Point b) macro Point Point.min(Point a, Point b) { - return Point{ + return { .x = min(a.x, b.x), .y = min(a.y, b.y), }; @@ -195,7 +195,7 @@ struct Color{ macro Color uint.to_rgba(uint u) { - return Color{ + return { .r = (char)((u >> 24) & 0xff), .g = (char)((u >> 16) & 0xff), .b = (char)((u >> 8) & 0xff), diff --git a/src/ugui_slider.c3 b/src/ugui_slider.c3 index 1d76376..cdcccf8 100644 --- a/src/ugui_slider.c3 +++ b/src/ugui_slider.c3 @@ -16,7 +16,7 @@ struct ElemSlider { <* @require value != null *> -fn ElemEvents! Ctx.slider_hor(&ctx, +fn ElemEvents? Ctx.slider_hor(&ctx, String label, Rect size, float* value, @@ -35,7 +35,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, if (elem.flags.is_new) { elem.type = ETYPE_SLIDER; } else if (elem.type != ETYPE_SLIDER) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } // 2. Layout @@ -79,7 +79,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx, * | | * +-+ */ -fn ElemEvents! Ctx.slider_ver(&ctx, +fn ElemEvents? Ctx.slider_ver(&ctx, String label, Rect size, float* value, @@ -98,7 +98,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx, if (elem.flags.is_new) { elem.type = ETYPE_SLIDER; } else if (elem.type != ETYPE_SLIDER) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } // 2. Layout diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 index b4a83f2..1f94b27 100644 --- a/src/ugui_sprite.c3 +++ b/src/ugui_sprite.c3 @@ -1,5 +1,6 @@ module ugui; +import std::core::mem::allocator; import std::collections::map; import std::io; import mqoi; @@ -20,7 +21,7 @@ struct Sprite { ushort w, h; } -def SpriteMap = map::HashMap(); +alias SpriteMap = map::HashMap{Id, Sprite}; struct SpriteAtlas { Id id; @@ -34,20 +35,20 @@ struct ElemSprite { } // name: some examples are "icons" or "images" -fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) +fn void? SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) { // FIXME: for now only R8G8B8A8 format is supported if (type != ATLAS_R8G8B8A8) { - return UgAtlasError.INVALID_TYPE?; + return INVALID_TYPE?; } this.id = name.hash(); this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!; - this.sprites.new_init(capacity: SRITES_PER_ATLAS); + this.sprites.init(allocator::heap(), capacity: SRITES_PER_ATLAS); this.should_update = false; } -fn void! SpriteAtlas.free(&this) +fn void? SpriteAtlas.free(&this) { this.atlas.free(); this.sprites.free(); @@ -55,7 +56,7 @@ fn void! SpriteAtlas.free(&this) // FIXME: this should throw an error when a different pixel format than the atlas' is used // or convert from the source's pixel format to the atlas' -fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) +fn Sprite*? SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) { Sprite s; s.id = name.hash(); @@ -70,18 +71,21 @@ fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixel return this.sprites.get_ref(s.id); } -fn Sprite*! SpriteAtlas.get(&this, String name) +fn Sprite*? SpriteAtlas.get(&this, String name) { Id id = name.hash(); return this.sprites.get_ref(id); } -fn Sprite*! SpriteAtlas.get_by_id(&this, Id id) +fn Sprite*? SpriteAtlas.get_by_id(&this, Id id) { return this.sprites.get_ref(id); } -fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h) +macro Rect Sprite.rect(s) => {0,0,s.w,s.h}; +macro Rect Sprite.uv(s) => {s.u,s.v,s.w,s.h}; + +fn void? Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h) { ctx.sprite_atlas.init(name, type, w, h)!; } @@ -91,12 +95,12 @@ fn Id Ctx.get_sprite_atlas_id(&ctx, String name) return name.hash(); } -fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL) +fn void? Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL) { ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!; } -fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) +fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) { mqoi::Desc image_desc; uint w, h; @@ -108,7 +112,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t mqoi::desc_push(&image_desc, file.read_byte()!); } if (mqoi::desc_verify(&image_desc, &w, &h) != 0) { - return IoError.FILE_NOT_VALID?; + return io::FILE_NOT_VALID?; } mqoi::Dec dec; @@ -123,7 +127,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t mqoi::dec_push(&dec, file.read_byte()!); while ((px = mqoi::dec_pop(&dec)) != null) { - pixels[idx..idx+3] = px.value; + pixels[idx..idx+3] = px.value[..]; idx += 4; } } @@ -131,7 +135,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!; } -fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) +fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) { Id id = ctx.gen_id(label)!; @@ -143,7 +147,7 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) if (elem.flags.is_new) { elem.type = ETYPE_SPRITE; } else if (elem.type != ETYPE_SPRITE) { - return UgError.WRONG_ELEMENT_TYPE?; + return WRONG_ELEMENT_TYPE?; } Sprite* sprite = ctx.sprite_atlas.get(name)!; @@ -163,11 +167,9 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) return ctx.push_sprite(elem.bounds, uv, tex_id)!; } -fn void! Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) +fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) { Sprite* sprite = ctx.sprite_atlas.get(name)!; - Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; - //Rect bounds = Rect{ 0, 0, sprite.w, sprite.h }.off(off); Id tex_id = ctx.sprite_atlas.id; - return ctx.push_sprite(bounds, uv, tex_id, type: sprite.type)!; + return ctx.push_sprite(bounds, sprite.uv(), tex_id, type: sprite.type)!; } diff --git a/src/ugui_text.c3 b/src/ugui_text.c3 index 18628e0..dea4562 100644 --- a/src/ugui_text.c3 +++ b/src/ugui_text.c3 @@ -6,7 +6,7 @@ struct ElemText { char* str; } -fn void! Ctx.text_unbounded(&ctx, String label, String text) +fn void? Ctx.text_unbounded(&ctx, String label, String text) { Id id = ctx.gen_id(label)!; diff --git a/src/vtree.c3 b/src/vtree.c3 index 8cdcff7..0a1e851 100644 --- a/src/vtree.c3 +++ b/src/vtree.c3 @@ -1,4 +1,4 @@ -module vtree(); +module vtree{ElemType}; import std::core::mem; import std::io; @@ -9,13 +9,7 @@ struct VTree { isz[] refs, ordered_refs; } -fault VTreeError { - CANNOT_SHRINK, - INVALID_REFERENCE, - TREE_FULL, - REFERENCE_NOT_PRESENT, - INVALID_ARGUMENT, -} +faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT; macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } @@ -27,11 +21,11 @@ macro @zero() $if $assignable(0, ElemType): return 0; $else - return ElemType{}; + return {}; $endif } -fn void! VTree.init(&tree, usz size) +fn void? VTree.init(&tree, usz size) { tree.vector = mem::new_array(ElemType, size); defer catch { (void)mem::free(tree.vector); } @@ -74,7 +68,7 @@ fn void VTree.pack(&tree) tree.vector[free_spot] = tree.vector[i]; tree.refs[free_spot] = tree.refs[i]; - tree.vector[i] = @zero(); + tree.vector[i] = {}; tree.refs[i] = -1; // and move all references @@ -90,18 +84,18 @@ fn void VTree.pack(&tree) } } -fn void! VTree.resize(&tree, usz newsize) +fn void? VTree.resize(&tree, usz newsize) { // return error when shrinking with too many elements if (newsize < tree.elements) { - return VTreeError.CANNOT_SHRINK?; + return CANNOT_SHRINK?; } // pack the vector when shrinking to avoid data loss if ((int)newsize < tree.size()) { // FIXME: packing destroys all references to elements of vec // so shrinking may cause dangling pointers - return VTreeError.CANNOT_SHRINK?; + return CANNOT_SHRINK?; } usz old_size = tree.size(); @@ -122,22 +116,22 @@ fn void! VTree.resize(&tree, usz newsize) } // add an element to the tree, return it's ref -fn isz! VTree.add(&tree, ElemType elem, isz parent) +fn isz? VTree.add(&tree, ElemType elem, isz parent) { // invalid parent if (!tree.ref_is_valid(parent)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } // no space left if (tree.elements >= tree.size()) { - return VTreeError.TREE_FULL?; + return TREE_FULL?; } // check if the parent exists // if there are no elements in the tree the first add will set the root if (!tree.ref_is_present(parent) && tree.elements != 0) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return REFERENCE_NOT_PRESENT?; } // get the first free spot @@ -149,7 +143,7 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent) } } if (free_spot < 0) { - return VTreeError.TREE_FULL?; + return TREE_FULL?; } // finally add the element @@ -162,10 +156,10 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent) // prune the tree starting from the ref // returns the number of pruned elements -fn usz! VTree.prune(&tree, isz ref) +fn usz? VTree.prune(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { @@ -196,10 +190,10 @@ fn usz VTree.nuke(&tree) } // find the size of the subtree starting from ref -fn usz! VTree.subtree_size(&tree, isz ref) +fn usz? VTree.subtree_size(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { @@ -218,20 +212,20 @@ fn usz! VTree.subtree_size(&tree, isz ref) } // iterate through the first level children, use a cursor like strtok_r -fn isz! VTree.children_it(&tree, isz parent, isz *cursor) +fn isz? VTree.children_it(&tree, isz parent, isz *cursor) { if (cursor == null) { - return VTreeError.INVALID_ARGUMENT?; + return INVALID_ARGUMENT?; } // if the cursor is out of bounds then we are done for sure if (!tree.ref_is_valid(*cursor)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } // same for the parent, if it's invalid it can't have children if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } // find the first child, update the cursor and return the ref @@ -256,10 +250,10 @@ fn isz! VTree.children_it(&tree, isz parent, isz *cursor) * / \ [6] * [4] [5] */ -fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) +fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor) { if (cursor == null) { - return VTreeError.INVALID_ARGUMENT?; + return INVALID_ARGUMENT?; } isz[] queue = tree.ordered_refs; @@ -303,27 +297,27 @@ fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) return -1; } -fn isz! VTree.parentof(&tree, isz ref) +fn isz? VTree.parentof(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return REFERENCE_NOT_PRESENT?; } return tree.refs[ref]; } -fn ElemType! VTree.get(&tree, isz ref) +fn ElemType? VTree.get(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return VTreeError.INVALID_REFERENCE?; + return INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { - return VTreeError.REFERENCE_NOT_PRESENT?; + return REFERENCE_NOT_PRESENT?; } return tree.vector[ref]; From 2380c7693c0436b9c5202e6ca38111ead7c02669 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 21 May 2025 23:38:59 +0200 Subject: [PATCH 093/250] add sdl3 dependency --- project.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/project.json b/project.json index 66150e9..8de760f 100644 --- a/project.json +++ b/project.json @@ -1,8 +1,8 @@ { "langrev": "1", "warnings": ["no-unused"], - "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], - "dependencies": ["raylib55", "schrift", "grapheme", "mqoi"], + "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"], + "dependencies": ["raylib55", "schrift", "grapheme", "mqoi", "sdl3"], "features": [], "authors": ["Alessandro Mauri "], "version": "0.1.0", From 712ce506311f377a9f24478273bab08c78281d94 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 1 Jun 2025 16:44:31 +0200 Subject: [PATCH 094/250] A lot of work * moved all ugui code to lib/ugui.c3l and made it a library/module * started work on a sdl3 renderer, with shaders etc * added the new sdl3.c3l library as a dependency * makefile is for the renderer --- .gitignore | 2 + Makefile | 3 + lib/ugui.c3l/LICENSE | 0 lib/ugui.c3l/README.md | 1 + lib/ugui.c3l/linux-x64/.gitkeep | 0 lib/ugui.c3l/manifest.json | 11 + lib/ugui.c3l/scripts/.gitkeep | 0 {src => lib/ugui.c3l/src}/cache.c3 | 0 {src => lib/ugui.c3l/src}/fifo.c3 | 0 {src => lib/ugui.c3l/src}/ugui_atlas.c3 | 0 {src => lib/ugui.c3l/src}/ugui_button.c3 | 0 {src => lib/ugui.c3l/src}/ugui_cmd.c3 | 0 {src => lib/ugui.c3l/src}/ugui_core.c3 | 0 {src => lib/ugui.c3l/src}/ugui_div.c3 | 0 {src => lib/ugui.c3l/src}/ugui_font.c3 | 0 {src => lib/ugui.c3l/src}/ugui_input.c3 | 0 {src => lib/ugui.c3l/src}/ugui_layout.c3 | 0 {src => lib/ugui.c3l/src}/ugui_shapes.c3 | 0 {src => lib/ugui.c3l/src}/ugui_slider.c3 | 0 {src => lib/ugui.c3l/src}/ugui_sprite.c3 | 0 {src => lib/ugui.c3l/src}/ugui_text.c3 | 0 {src => lib/ugui.c3l/src}/vtree.c3 | 0 project.json | 2 +- resources/shaders/compiled/rect.frag.spv | Bin 0 -> 836 bytes resources/shaders/compiled/rect.vert.spv | Bin 0 -> 2764 bytes resources/shaders/source/font.frag.glsl | 18 + resources/shaders/source/font.vert.glsl | 26 + resources/shaders/source/rect.frag.glsl | 9 + resources/shaders/source/rect.vert.glsl | 25 + scripts/compile_shaders.sh | 41 ++ src/renderer.c3 | 628 +++++++++++++++++++++++ test_renderer.c3 | 73 +++ 32 files changed, 838 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 lib/ugui.c3l/LICENSE create mode 100644 lib/ugui.c3l/README.md create mode 100644 lib/ugui.c3l/linux-x64/.gitkeep create mode 100644 lib/ugui.c3l/manifest.json create mode 100644 lib/ugui.c3l/scripts/.gitkeep rename {src => lib/ugui.c3l/src}/cache.c3 (100%) rename {src => lib/ugui.c3l/src}/fifo.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_atlas.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_button.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_cmd.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_core.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_div.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_font.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_input.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_layout.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_shapes.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_slider.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_sprite.c3 (100%) rename {src => lib/ugui.c3l/src}/ugui_text.c3 (100%) rename {src => lib/ugui.c3l/src}/vtree.c3 (100%) create mode 100644 resources/shaders/compiled/rect.frag.spv create mode 100644 resources/shaders/compiled/rect.vert.spv create mode 100644 resources/shaders/source/font.frag.glsl create mode 100644 resources/shaders/source/font.vert.glsl create mode 100644 resources/shaders/source/rect.frag.glsl create mode 100644 resources/shaders/source/rect.vert.glsl create mode 100755 scripts/compile_shaders.sh create mode 100644 src/renderer.c3 create mode 100644 test_renderer.c3 diff --git a/.gitignore b/.gitignore index 0af3826..042d5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ build/* **/.ccls-cache perf.data* +*.rdc +test_renderer diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a19a0d7 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* + scripts/compile_shaders.sh + c3c compile-run -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 diff --git a/lib/ugui.c3l/LICENSE b/lib/ugui.c3l/LICENSE new file mode 100644 index 0000000..e69de29 diff --git a/lib/ugui.c3l/README.md b/lib/ugui.c3l/README.md new file mode 100644 index 0000000..151118a --- /dev/null +++ b/lib/ugui.c3l/README.md @@ -0,0 +1 @@ +Welcome to the ugui library. diff --git a/lib/ugui.c3l/linux-x64/.gitkeep b/lib/ugui.c3l/linux-x64/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/lib/ugui.c3l/manifest.json b/lib/ugui.c3l/manifest.json new file mode 100644 index 0000000..8211665 --- /dev/null +++ b/lib/ugui.c3l/manifest.json @@ -0,0 +1,11 @@ +{ + "provides" : "ugui", + "sources" : [ "src/**" ], + "targets" : { + "linux-x64" : { + "link-args" : [], + "dependencies" : ["schrift", "grapheme", "mqoi"], + "linked-libraries" : [] + } + } +} \ No newline at end of file diff --git a/lib/ugui.c3l/scripts/.gitkeep b/lib/ugui.c3l/scripts/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/src/cache.c3 b/lib/ugui.c3l/src/cache.c3 similarity index 100% rename from src/cache.c3 rename to lib/ugui.c3l/src/cache.c3 diff --git a/src/fifo.c3 b/lib/ugui.c3l/src/fifo.c3 similarity index 100% rename from src/fifo.c3 rename to lib/ugui.c3l/src/fifo.c3 diff --git a/src/ugui_atlas.c3 b/lib/ugui.c3l/src/ugui_atlas.c3 similarity index 100% rename from src/ugui_atlas.c3 rename to lib/ugui.c3l/src/ugui_atlas.c3 diff --git a/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 similarity index 100% rename from src/ugui_button.c3 rename to lib/ugui.c3l/src/ugui_button.c3 diff --git a/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 similarity index 100% rename from src/ugui_cmd.c3 rename to lib/ugui.c3l/src/ugui_cmd.c3 diff --git a/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 similarity index 100% rename from src/ugui_core.c3 rename to lib/ugui.c3l/src/ugui_core.c3 diff --git a/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 similarity index 100% rename from src/ugui_div.c3 rename to lib/ugui.c3l/src/ugui_div.c3 diff --git a/src/ugui_font.c3 b/lib/ugui.c3l/src/ugui_font.c3 similarity index 100% rename from src/ugui_font.c3 rename to lib/ugui.c3l/src/ugui_font.c3 diff --git a/src/ugui_input.c3 b/lib/ugui.c3l/src/ugui_input.c3 similarity index 100% rename from src/ugui_input.c3 rename to lib/ugui.c3l/src/ugui_input.c3 diff --git a/src/ugui_layout.c3 b/lib/ugui.c3l/src/ugui_layout.c3 similarity index 100% rename from src/ugui_layout.c3 rename to lib/ugui.c3l/src/ugui_layout.c3 diff --git a/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 similarity index 100% rename from src/ugui_shapes.c3 rename to lib/ugui.c3l/src/ugui_shapes.c3 diff --git a/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 similarity index 100% rename from src/ugui_slider.c3 rename to lib/ugui.c3l/src/ugui_slider.c3 diff --git a/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 similarity index 100% rename from src/ugui_sprite.c3 rename to lib/ugui.c3l/src/ugui_sprite.c3 diff --git a/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 similarity index 100% rename from src/ugui_text.c3 rename to lib/ugui.c3l/src/ugui_text.c3 diff --git a/src/vtree.c3 b/lib/ugui.c3l/src/vtree.c3 similarity index 100% rename from src/vtree.c3 rename to lib/ugui.c3l/src/vtree.c3 diff --git a/project.json b/project.json index 8de760f..21e419c 100644 --- a/project.json +++ b/project.json @@ -2,7 +2,7 @@ "langrev": "1", "warnings": ["no-unused"], "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"], - "dependencies": ["raylib55", "schrift", "grapheme", "mqoi", "sdl3"], + "dependencies": ["raylib55", "sdl3", "ugui"], "features": [], "authors": ["Alessandro Mauri "], "version": "0.1.0", diff --git a/resources/shaders/compiled/rect.frag.spv b/resources/shaders/compiled/rect.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..3cc8f056785b496a3fffddf27bc63dc76cb9c2bd GIT binary patch literal 836 zcmZ`%QHv8X5FW4ByI#(oilA>2>C4^2ZF_KvP|g>TzEmps1D0hs)?iG^Zg!!Ff6ia! zi{SUA?oqG?CX;XG%giJ*gQLl*$OAc-v0TV*J&_|=1hx~LL>9|W%dCl2cKd2Z#)S;1 z66cYe$}!GE$hX3J+7;v>@X`9 z_1eU1ZZ@FE+$#$PGrifw4)r3+;dX}r_3D{Z~*OnIxz;O2)J zxU-}1H{Jyo>bX)~uvL2D<(2xb&hA|Znj-x`9~63(FIKC?@_k-zHhB}b&g3jDuPp!9 zc1FM(?eVNHU0dxFhG6XXa|pzq-lxQQi0-h*NG*$$S>y#U7cqT3eu^$~VqRkBtnmB{ zJLf_}p2IqVCZ=CwKZ7QZ_W57T8fc^c9GWxH`-z!zXFE>%1eo^MsoLF)0;U|lP!s(l dpgs5 literal 0 HcmV?d00001 diff --git a/resources/shaders/compiled/rect.vert.spv b/resources/shaders/compiled/rect.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..439d76d10af3d355ad240dc1fee43841633781e9 GIT binary patch literal 2764 zcmZ{lTW=dh6vvn5($u9brL^Tz!ldD1lVoj&q!dirrfCTeL5WnUua>bl>5A>HiWemQzwsW&f^?$eIhX&r&dg@+!s^8!cq6zRTnWAnChK}|L7G6iMR7r} zfAH)e%!^*QwcP-5HJD@bm}^1B^9%ZY*@;tx&qbS}Z$uA8Pem6MpO|5N5aro$(2eqX z{-V>12HL$WL$?S=(Vz%Vk~|T1F8I^u|4WYe*6Z%D|9#dQCehI#Q-nO~xhO3LBx}bYmooTx|+mkNKlL zCFzW_VNuPa!tJ=`y89tcdvSUqp;2?gG(OG-FWvJv`l+7{3inIpQap;fn{Fgc%Y^Ls zxK;VJH4Tj)!^8c9r|oXP-_DD1616${4> z>+v;fU(nwb?RFu=>IF`8@LY?DZr89! zNQ?i9e({IDrC)ul11GvIEj=RR?<8L;W%M^CliP`i2mVd%=)f;aW`^h>{I6=qhQ3wG z%n#j~WPINe5s&!SwL1|DJM!CJn#8*y9$);-|Mraij(F;a%-{N_o|^RJq95u(=Dg6+ zPpBPV=5t5C^-?Cra(SN(gAF2XQ#PAn7XPvMAb7QBeR3DDFA2}i*%0%de=)q9$s)(!n)0G{ ze7OUBQ|Q6uYAIVRJEL8BaYpZmIA_il+m7Op>!t`>CYgA*N}c(=D;Yn|bk^@Z$=J;A zeevl2P}RiwKtwF`yV7wFJ`_SS5&kHlNeoVV5N#ACDEE8@|a-(B%m z^Q_;h^w`YrWAW&$=1;^E3;nBd9-oRwZ#{e_o*sxx56EDw2jmAHJ5&9#Y4tM%15q{`-2i$%9 zx#um>rik3sgbdzl;@nyE#C?Ax;;u7SV(*F`i7eNi_{aYtW)Aj=2s{{a@y5_uF76cT KAH9(KqJIJIdSEF4 literal 0 HcmV?d00001 diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl new file mode 100644 index 0000000..1e43b1a --- /dev/null +++ b/resources/shaders/source/font.frag.glsl @@ -0,0 +1,18 @@ +#version 330 core + +// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels +uniform ivec2 viewsize; +uniform ivec2 texturesize; + +// texture uv coordinate in texture space +in vec2 uv; +uniform sampler2DRect ts; + +const vec3 textcolor = vec3(1.0, 1.0, 1.0); + + +void main() +{ + //gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f); + gl_FragColor = vec4(textcolor, texture(ts, uv)); +} diff --git a/resources/shaders/source/font.vert.glsl b/resources/shaders/source/font.vert.glsl new file mode 100644 index 0000000..8d429cb --- /dev/null +++ b/resources/shaders/source/font.vert.glsl @@ -0,0 +1,26 @@ +#version 330 core + +// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels +uniform ivec2 viewsize; +uniform ivec2 texturesize; + +// both position and and uv are in pixels, they where converted to floats when +// passed to the shader +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 txcoord; + +out vec2 uv; + +void main() +{ + vec2 v = vec2(float(viewsize.x), float(viewsize.y)); + +// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f); + vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); + vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); + + gl_Position = pos; + // since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to + // be normalized + uv = vec2(txcoord.x, txcoord.y); +} diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl new file mode 100644 index 0000000..3466a16 --- /dev/null +++ b/resources/shaders/source/rect.frag.glsl @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec4 col; +layout(location = 0) out vec4 fragColor; + +void main() +{ + fragColor = col; +} \ No newline at end of file diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl new file mode 100644 index 0000000..abe367a --- /dev/null +++ b/resources/shaders/source/rect.vert.glsl @@ -0,0 +1,25 @@ +#version 450 + +layout(set = 1, binding = 0) uniform Viewport { + ivec2 view; + ivec2 off; +}; + +layout(location = 0) in vec2 position; +layout(location = 1) in vec2 uv; +layout(location = 2) in ivec4 color; + +layout(location = 0) out vec4 col; + +void main() +{ + //vec2 shift = vec2(0); + vec2 shift; + shift.x = float(off.x) / view.x; + shift.y = -(float(off.y) / view.y); + + vec2 pos = position + shift; + + gl_Position = vec4(pos.x, pos.y, 0.0, 1.0); + col = vec4(color) / 255.0; +} diff --git a/scripts/compile_shaders.sh b/scripts/compile_shaders.sh new file mode 100755 index 0000000..336cdf1 --- /dev/null +++ b/scripts/compile_shaders.sh @@ -0,0 +1,41 @@ +#!/bin/sh + +source_directory="./resources/shaders/source" +compiled_directory="./resources/shaders/compiled" +#vulkan_version="1.0" + +mkdir -p "$compiled_directory" +rm -f "$compiled_directory"/* + +echo "Compiling from $source_directory -> $compiled_directory" + +for file in "$source_directory"/*; do + [ -f "$file" ] || continue # Skip non-files + filename=$(basename "$file") + + # Extract filename parts using POSIX parameter expansion + shader_language="${filename##*.}" + stage_part="${filename%.*}" # Remove extension + base_name="${stage_part%.*}" # Remove stage + stage="${stage_part#"$base_name"}" + stage="${stage#.}" # Remove leading dot + + # Skip if not in base.stage.glsl format + [ "$shader_language" = "glsl" ] && [ -n "$base_name" ] && [ -n "$stage" ] || continue + + # Handle HLSL rejection + if [ "$shader_language" != "glsl" ]; then + echo "Error: Only GLSL shaders are supported" >&2 + exit 1 + fi + + # Compile based on shader stage + case "$stage" in + frag|vert) + echo "$stage $filename > $base_name.$stage.spv" + glslc -O0 -g -fshader-stage="$stage" "$file" -o "$compiled_directory/$base_name.$stage.spv" + ;; + esac +done + +tree "$compiled_directory" \ No newline at end of file diff --git a/src/renderer.c3 b/src/renderer.c3 new file mode 100644 index 0000000..99f10a5 --- /dev/null +++ b/src/renderer.c3 @@ -0,0 +1,628 @@ +module sdlrenderer::ren; + +// 2D renderer for ugui, based on SDL3 using the new GPU API +// TODO: use unreachable() instead of the combo eprintfn();exit(1); since it does the same thing +// but also adds a helpful stack trace + +import std::io; +import std::core::mem; +import sdl3::sdl; +import libc; +import std::collections::list; + +struct Shader { + sdl::GPUShader* frag; + sdl::GPUShader* vert; + uint id; +} + +struct Pipeline { + sdl::GPUGraphicsPipeline* pipeline; + uint id; +} + +struct Texture { + sdl::GPUTexture* texture; + sdl::GPUSampler* sampler; + ushort width, height; + uint id; +} + +// gpu buffer that contains a single quad +struct QuadBuffer { + sdl::GPUBuffer* vert_buf; + sdl::GPUBuffer* idx_buf; + bool initialized; +} + +alias ShaderList = List{Shader}; +alias PipelineList = List{Pipeline}; +alias TextureList = List{Texture}; + +struct Renderer { + sdl::Window* win; + sdl::GPUDevice* gpu; + QuadBuffer quad_buffer; + ShaderList shaders; + PipelineList pipelines; + TextureList textures; +} + +// how each vertex is represented in the gpu +struct Vertex @packed { + struct pos { + float x, y; + } + struct uv { + float u, v; + } + struct col { // FIXME: this is shit + union { + char r, g, b, a; + char[4] arr; + uint u; + } + } +} + +struct Quad @packed { + struct vertices { + Vertex v1,v2,v3,v4; + } + struct indices { + short i1,i2,i3,i4,i5,i6; + } +} + +const int DEBUG = 1; + +fn void Renderer.init(&self, ZString title) +{ + // set wayland hint automagically +$if DEBUG == 0: + bool has_wayland = false; + for (int i = 0; i < sdl::get_num_video_drivers(); i++) { + ZString driver = sdl::get_video_driver(i); + if (driver.str_view() == "wayland") { + has_wayland = true; + break; + } + } + + if (has_wayland) { + sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland"); + } +$else + sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); +$endif + + + sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1"); + + // init subsystems + if (!sdl::init(INIT_VIDEO)) { + io::eprintfn("sdl error: %s", sdl::get_error()); + libc::exit(1); + } + + // create the window + self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN); + if (self.win == null) { + io::eprintfn("sdl error: %s", sdl::get_error()); + libc::exit(1); + } + + // get the gpu device handle + self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan"); + if (self.gpu == null) { + io::eprintfn("failed to create gpu device: %s", sdl::get_error()); + libc::exit(1); + } + + if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) { + io::eprintfn("failed to claim window for use with gpu: %s", sdl::get_error()); + libc::exit(1); + } + + // initialize the quad buffer + self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof} + ); + if (self.quad_buffer.vert_buf == null) { + io::eprintfn("failed to initialize quad buffer (vertex): %s", sdl::get_error()); + libc::exit(1); + } + + self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof} + ); + if (self.quad_buffer.idx_buf == null) { + io::eprintfn("failed to initialize quad buffer (index): %s", sdl::get_error()); + libc::exit(1); + } + + self.quad_buffer.initialized = true; +} + +fn void Renderer.free(&self) +{ + foreach (&s: self.shaders) { + sdl::release_gpu_shader(self.gpu, s.frag); + sdl::release_gpu_shader(self.gpu, s.vert); + } + self.shaders.free(); + + foreach (&p: self.pipelines) { + sdl::release_gpu_graphics_pipeline(self.gpu, p.pipeline); + } + self.pipelines.free(); + + sdl::release_window_from_gpu_device(self.gpu, self.win); + sdl::destroy_gpu_device(self.gpu); + sdl::destroy_window(self.win); + sdl::quit(); +} + +fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code, char[] frag_code, uint textures, uint uniforms) +{ + Shader s; + s.id = name.hash(); + + if (vert_code.len == 0 || frag_code.len == 0) { + unreachable("vertex shader and fragment shader cannot be empty"); + } + + if (vert_code.len > 0) { + // FIXME: these should be passed by parameter and/or automatically determined by parsing + // the shader code + GPUShaderCreateInfo shader_info = { + .code = vert_code.ptr, + .code_size = vert_code.len, + .entrypoint = "main", + .format = GPU_SHADERFORMAT_SPIRV, + .stage = GPU_SHADERSTAGE_VERTEX, + .num_samplers = 0, + .num_uniform_buffers = uniforms, + .num_storage_buffers = 0, + .num_storage_textures = 0 + }; + + s.vert = sdl::create_gpu_shader(self.gpu, &shader_info); + if (s.vert == null) { + io::eprintfn("failed to create gpu vertex shader: %s", sdl::get_error()); + libc::exit(1); + } + } + + if (frag_code.len > 0) { + // FIXME: these should be passed by parameter and/or automatically determined by parsing + // the shader code + GPUShaderCreateInfo shader_info = { + .code = frag_code.ptr, + .code_size = frag_code.len, + .entrypoint = "main", + .format = GPU_SHADERFORMAT_SPIRV, + .stage = GPU_SHADERSTAGE_FRAGMENT, + .num_samplers = textures, + .num_uniform_buffers = 0, + .num_storage_buffers = 0, + .num_storage_textures = 0 + }; + + s.frag = sdl::create_gpu_shader(self.gpu, &shader_info); + if (s.frag == null) { + io::eprintfn("failed to create gpu fragment shader: %s", sdl::get_error()); + libc::exit(1); + } + } + + // push the shader into the list + self.shaders.push(s); +} + +fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_path, String frag_path, uint textures, uint uniforms) +{ + if (vert_path == "" || frag_path == "") { + unreachable("need both a vertex shader and fragment shader path"); + } + + char[] vert_code; + char[] frag_code; + + // create vertex shader + if (vert_path != "") { + vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); + file::load_buffer(vert_path, vert_code)!!; + } + + // create fragment shader + if (frag_path != "") { + frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); + file::load_buffer(frag_path, frag_code)!!; + } + + self.load_spirv_shader_from_mem(name, vert_code, frag_code, textures, uniforms); + + if (vert_code.ptr) mem::free(vert_code); + if (frag_code.ptr) mem::free(frag_code); +} + +fn Shader* ShaderList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&s: self) { + if (s.id == id) { + return s; + } + } + return null; +} + +fn Pipeline* PipelineList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&p: self) { + if (p.id == id) { + return p; + } + } + return null; +} + +fn Texture* TextureList.get_from_name(&self, String name) +{ + uint id = name.hash(); + foreach(&t: self) { + if (t.id == id) { + return t; + } + } + return null; +} + + +// this describes what we want to draw, since for drawing different things we have to change +// the GPUPrimitiveType and GPURasterizerState for the pipeline. +enum PipelineType : (GPUPrimitiveType primitive_type, GPURasterizerState raster_state) { + RECT = {GPU_PRIMITIVETYPE_TRIANGLELIST, {.fill_mode = GPU_FILLMODE_FILL, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, + SPRITE = {GPU_PRIMITIVETYPE_TRIANGLELIST, {.fill_mode = GPU_FILLMODE_FILL, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, + LINE = {GPU_PRIMITIVETYPE_LINELIST, {.fill_mode = GPU_FILLMODE_LINE, .cull_mode = GPU_CULLMODE_NONE, .front_face = GPU_FRONTFACE_COUNTER_CLOCKWISE}}, +} + +// create a graphics pipeline to draw to the window using a set of vertex/fragment shaders +// the pipeline is pushed into the renderer's pipeline list and it will have the same id as +// the shader set. +fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) +{ + Shader *s = self.shaders.get_from_name(shader_name); + if (s == null) { + io::eprintfn("error in creating pipeline: no shader named %s", shader_name); + libc::exit(1); + } + + GPUGraphicsPipelineCreateInfo ci = { + .vertex_shader = s.vert, + .fragment_shader = s.frag, + // This structure specifies how the vertex buffer looks in memory, what it contains + // and what is passed where to the gpu. Each vertex has three attributes, position, + // color and uv coordinates. Since this is a 2D pixel-based renderer the position + // is represented by two floats, the color as 32 bit rgba and the uv also as intgers. + .vertex_input_state = { + // the description of each vertex buffer, for now I use only one buffer + .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){{ + .slot = 0, + .pitch = Vertex.sizeof, + .input_rate = GPU_VERTEXINPUTRATE_VERTEX, + .instance_step_rate = 0, + }}, + .num_vertex_buffers = 1, + // the description of each vertex, each vertex has three properties + .vertex_attributes = (GPUVertexAttribute[]){ + { // at location zero there is the position of the vertex + .location = 0, + .buffer_slot = 0, // only one buffer so always slot zero + .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .offset = Vertex.pos.offsetof, + }, + { // at location one there are the uv coordinates + .location = 1, + .buffer_slot = 0, + .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .offset = Vertex.uv.offsetof, + }, + { // at location two there is the color + .location = 2, + .buffer_slot = 0, + .format = GPU_VERTEXELEMENTFORMAT_UBYTE4, // 4x8bit unsigned rgba format + .offset = Vertex.col.offsetof, + } + }, + .num_vertex_attributes = 3, + }, + // the pipeline's primitive type and rasterizer state differs based on what needs to + // be drawn + .primitive_type = type.primitive_type, + .rasterizer_state = type.raster_state, + .multisample_state = {}, // no multisampling, all zeroes + .depth_stencil_state = {}, // no stencil test, all zeroes + .target_info = { // the target (texture) description + .color_target_descriptions = (GPUColorTargetDescription[]){{ + // rendering happens to the window, so get it's format + .format = sdl::get_gpu_swapchain_texture_format(self.gpu, self.win), + .blend_state = { + // alpha blending on everything + // https://en.wikipedia.org/wiki/Alpha_compositing + .src_color_blendfactor = GPU_BLENDFACTOR_SRC_ALPHA, + .dst_color_blendfactor = GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .color_blend_op = GPU_BLENDOP_ADD, + .src_alpha_blendfactor = GPU_BLENDFACTOR_SRC_ALPHA, + .dst_alpha_blendfactor = GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, + .alpha_blend_op = GPU_BLENDOP_ADD, + .enable_blend = true, + // color write mask is not enabled so all rgba channels are written to + }, + }}, + .num_color_targets = 1, + .depth_stencil_format = {}, // FIXME: no stencil, no depth buffering + .has_depth_stencil_target = false, + }, + }; + + // create the pipeline and add it to the pipeline list + Pipeline p = { + .id = s.id, + .pipeline = sdl::create_gpu_graphics_pipeline(self.gpu, &ci), + }; + + if (p.pipeline == null) { + io::eprintfn("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + self.pipelines.push(p); +} + +enum TextureType : (GPUTextureFormat format) { + FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT, + JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT +} + +// create a new gpu texture from a pixel buffer, the format has to be specified +// the new texture s given an id and pushed into a texture list +fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height) +{ + uint id = name.hash(); + + // the texture description + GPUTextureCreateInfo tci = { + .type = GPU_TEXTURETYPE_2D, + .format = type.format, + // all textures are used with samplers, which means read-only textures that contain data to be sampled + .usage = GPU_TEXTUREUSAGE_SAMPLER, + .width = width, + .height = height, + .layer_count_or_depth = 1, + .num_levels = 0, // no mip maps + // .sample_count not used since the texture is not a render target + }; + + GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci); + if (texture == null) { + io::eprintfn("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + // the sampler description, how the texture should be sampled + GPUSamplerCreateInfo sci = { + .min_filter = GPU_FILTER_LINEAR, // linear interpolation for textures + .mag_filter = GPU_FILTER_LINEAR, + .mipmap_mode = GPU_SAMPLERMIPMAPMODE_NEAREST, + .address_mode_u = GPU_SAMPLERADDRESSMODE_REPEAT, // tiling textures + .address_mode_v = GPU_SAMPLERADDRESSMODE_REPEAT, + .address_mode_w = GPU_SAMPLERADDRESSMODE_REPEAT, + // everything else is not used and not needed + }; + + GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci); + if (sampler == null) { + io::eprintfn("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + libc::exit(1); + } + + Texture t = { + .id = id, + .texture = texture, + .sampler = sampler, + }; + self.textures.push(t); + + // upload the texture data + self.update_texture(name, pixels, width, height); +} + +fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0) +{ + Texture* t = self.textures.get_from_name(name); + if (t == null || t.texture == null) { + io::eprintf("failed updating texture: no texture named %s", name); + libc::exit(1); + } + GPUTexture* texture = t.texture; + + // FIXME: do a better job at validating the copy + if (x > t.width || y > t.height) { + io::eprintf("failed updating texture: attempting to copy outside of the texture region", name); + libc::exit(1); + } + + // upload image data + GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmdbuf == null) { + io::eprintfn("failed to upload texture data at acquiring command buffer: %s", sdl::get_error()); + libc::exit(1); + } + GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf); + if (copypass == null) { + io::eprintfn("failed to upload texture data at beginning copy pass: %s", sdl::get_error()); + libc::exit(1); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len} + ); + if (buf == null) { + io::eprintfn("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (gpu_mem == null) { + io::eprintfn("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + // copy the data to the driver's memory + gpu_mem[:pixels.len] = pixels[..]; + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + // upload the data to gpu memory + sdl::upload_to_gpu_texture(copypass, + &&(GPUTextureTransferInfo){.transfer_buffer = buf, .offset = 0}, + &&(GPUTextureRegion){.texture = texture, .x = x, .y = y, .w = width, .h = height, .d = 1}, + false + ); + + sdl::end_gpu_copy_pass(copypass); + if (!sdl::submit_gpu_command_buffer(cmdbuf)) { + io::eprintfn("failed to upload texture data at command buffer submission: %s", sdl::get_error()); + libc::exit(1); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); +} + + +macro void Vertex.norm(&p, float w, float h) +{ + p.pos.x = p.pos.x * 2.0 / w - 1.0; + p.pos.y = -(p.pos.y * 2.0 / h - 1.0); +} + +// an highly inefficient way to draw a single quad, no batching, per-quad upload +fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name) +{ + // upload the quad data to the gpu + if (self.quad_buffer.initialized == false) { + io::eprintfn("quad buffer not initialized"); + libc::exit(1); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (buf == null) { + io::eprintfn("failed to create gpu transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (quad == null) { + io::eprintfn("failed to map gpu transfer buffer: %s", sdl::get_error()); + libc::exit(1); + } + + /* v1 v4 + * +-------------+ + * | _/| + * | _/ | + * | 1 _/ | + * | _/ | + * | _/ | + * | _/ 2 | + * |/ | + * +-------------+ + * v2 v3 + */ + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .col.u = color}; + quad.vertices.v2 = {.pos = {.x = x, .y = (float)y+h}, .col.u = color}; + quad.vertices.v3 = {.pos = {.x = (float)x+w, .y = (float)y+h}, .col.u = color}; + quad.vertices.v4 = {.pos = {.x = (float)x+w, .y = y}, .col.u = color}; + + quad.vertices.v1.norm(640.0, 480.0); + quad.vertices.v2.norm(640.0, 480.0); + quad.vertices.v3.norm(640.0, 480.0); + quad.vertices.v4.norm(640.0, 480.0); + + // triangle 1 + quad.indices.i1 = 0; // v1 + quad.indices.i2 = 1; // v2 + quad.indices.i3 = 3; // v4 + // triangle 2 + quad.indices.i4 = 1; // v2 + quad.indices.i5 = 2; // v3 + quad.indices.i6 = 3; // v4 + + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + io::eprintfn("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + libc::exit(1); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, + &&(GPUBufferRegion){.buffer = self.quad_buffer.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, + &&(GPUBufferRegion){.buffer = self.quad_buffer.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); + sdl::wait_for_gpu_idle(self.gpu); + +/* + // now finally draw the quad + // if we are not in a render pass then we can't render shit + if (self.render_cmd == null) { + unreachable("start rendering first before trying to render a quad"); + } + + // FIXME: this could be done at the start of rendering + GPUTexture* t; + if (!sdl::wait_and_acquire_gpu_swapchain_texture(self.render_cmd, self.win, &t, null, null)) { + unreachable("failed to acquire swapchain texture: %s", sdl::get_error()); + } + + // TODO: begin render pass + + Pipeline* p = self.pipelines.get_from_name(shader_name); + if (p == null) { + unreachable("no pipeline named: %s", shader_name); + } + + // bind the data + sdl::bind_gpu_graphics_pipeline(self.render_pass, pipeline); + sdl::bind_gpu_vertex_buffer(self.render_pass, 0, + &&(GPUBufferBinding){.buffer = self.quad_buffer.vert_buf, .offset = 0}, 1 + ); + sdl::bind_gpu_index_buffer(self.render_pass, 0, + &&(GPUBufferBinding){.buffer = self.quad_buffer.idx_buf, .offset = 0}, 1 + ); + + sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, 0, 0, 0); +*/ +} + +// TODO: fn Renderer.draw_quad, it has to use a vertex buffer and an index buffer +// TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture +// TODO: fn Renderer.begin_render +// TODO: fn Renderer.end_render \ No newline at end of file diff --git a/test_renderer.c3 b/test_renderer.c3 new file mode 100644 index 0000000..63f0d5d --- /dev/null +++ b/test_renderer.c3 @@ -0,0 +1,73 @@ +import sdlrenderer::ren; +import std::io; +import std::thread; +import sdl3::sdl; + +struct Viewsize @align(16) { + int w, h; + int ox, oy; +} + +fn int main() +{ + ren::Renderer ren; + ren.init("test window"); + + ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); + ren.create_pipeline("rect shader", RECT); + + for (int i = 0; i < 10; i++) { + + ren.draw_rect(100,100,100,100,0xff00ff00,""); + + GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); + GPUTexture* swapchain_texture; + sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); + + // FIXME: if doing damage tracking DO NOT clear the screen + GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf, + &&(GPUColorTargetInfo){ + .texture = swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, + .load_op = GPU_LOADOP_CLEAR, // clear the screen at the start of the render pass + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + + if (pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; + if (p == null) { + unreachable("no pipeline"); + } + + sdl::bind_gpu_graphics_pipeline(pass, p); + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + Viewsize v = {.w = 640, .h = 480}; + v.ox = 50*i; + v.oy = 50*i; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); + + sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); + + sdl::end_gpu_render_pass(pass); + sdl::submit_gpu_command_buffer(cmdbuf); + + thread::sleep_ms(250); + } + + ren.free(); + return 0; +} \ No newline at end of file From 24bc2c67bcd4c42e5d9763671c964340ebf86f08 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 09:16:51 +0200 Subject: [PATCH 095/250] changed the pipeline to use 16 bit int as coords thank you renderdoc for making me feel less stupid --- .gitignore | 1 + resources/shaders/compiled/rect.frag.spv | Bin 836 -> 0 bytes resources/shaders/compiled/rect.vert.spv | Bin 2764 -> 0 bytes resources/shaders/source/rect.vert.glsl | 13 +++++++----- src/renderer.c3 | 26 +++++++---------------- 5 files changed, 17 insertions(+), 23 deletions(-) delete mode 100644 resources/shaders/compiled/rect.frag.spv delete mode 100644 resources/shaders/compiled/rect.vert.spv diff --git a/.gitignore b/.gitignore index 042d5c7..eb64e0d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ build/* perf.data* *.rdc test_renderer +resources/shaders/compiled/** diff --git a/resources/shaders/compiled/rect.frag.spv b/resources/shaders/compiled/rect.frag.spv deleted file mode 100644 index 3cc8f056785b496a3fffddf27bc63dc76cb9c2bd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 836 zcmZ`%QHv8X5FW4ByI#(oilA>2>C4^2ZF_KvP|g>TzEmps1D0hs)?iG^Zg!!Ff6ia! zi{SUA?oqG?CX;XG%giJ*gQLl*$OAc-v0TV*J&_|=1hx~LL>9|W%dCl2cKd2Z#)S;1 z66cYe$}!GE$hX3J+7;v>@X`9 z_1eU1ZZ@FE+$#$PGrifw4)r3+;dX}r_3D{Z~*OnIxz;O2)J zxU-}1H{Jyo>bX)~uvL2D<(2xb&hA|Znj-x`9~63(FIKC?@_k-zHhB}b&g3jDuPp!9 zc1FM(?eVNHU0dxFhG6XXa|pzq-lxQQi0-h*NG*$$S>y#U7cqT3eu^$~VqRkBtnmB{ zJLf_}p2IqVCZ=CwKZ7QZ_W57T8fc^c9GWxH`-z!zXFE>%1eo^MsoLF)0;U|lP!s(l dpgs5 diff --git a/resources/shaders/compiled/rect.vert.spv b/resources/shaders/compiled/rect.vert.spv deleted file mode 100644 index 439d76d10af3d355ad240dc1fee43841633781e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2764 zcmZ{lTW=dh6vvn5($u9brL^Tz!ldD1lVoj&q!dirrfCTeL5WnUua>bl>5A>HiWemQzwsW&f^?$eIhX&r&dg@+!s^8!cq6zRTnWAnChK}|L7G6iMR7r} zfAH)e%!^*QwcP-5HJD@bm}^1B^9%ZY*@;tx&qbS}Z$uA8Pem6MpO|5N5aro$(2eqX z{-V>12HL$WL$?S=(Vz%Vk~|T1F8I^u|4WYe*6Z%D|9#dQCehI#Q-nO~xhO3LBx}bYmooTx|+mkNKlL zCFzW_VNuPa!tJ=`y89tcdvSUqp;2?gG(OG-FWvJv`l+7{3inIpQap;fn{Fgc%Y^Ls zxK;VJH4Tj)!^8c9r|oXP-_DD1616${4> z>+v;fU(nwb?RFu=>IF`8@LY?DZr89! zNQ?i9e({IDrC)ul11GvIEj=RR?<8L;W%M^CliP`i2mVd%=)f;aW`^h>{I6=qhQ3wG z%n#j~WPINe5s&!SwL1|DJM!CJn#8*y9$);-|Mraij(F;a%-{N_o|^RJq95u(=Dg6+ zPpBPV=5t5C^-?Cra(SN(gAF2XQ#PAn7XPvMAb7QBeR3DDFA2}i*%0%de=)q9$s)(!n)0G{ ze7OUBQ|Q6uYAIVRJEL8BaYpZmIA_il+m7Op>!t`>CYgA*N}c(=D;Yn|bk^@Z$=J;A zeevl2P}RiwKtwF`yV7wFJ`_SS5&kHlNeoVV5N#ACDEE8@|a-(B%m z^Q_;h^w`YrWAW&$=1;^E3;nBd9-oRwZ#{e_o*sxx56EDw2jmAHJ5&9#Y4tM%15q{`-2i$%9 zx#um>rik3sgbdzl;@nyE#C?Ax;;u7SV(*F`i7eNi_{aYtW)Aj=2s{{a@y5_uF76cT KAH9(KqJIJIdSEF4 diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl index abe367a..0fab590 100644 --- a/resources/shaders/source/rect.vert.glsl +++ b/resources/shaders/source/rect.vert.glsl @@ -5,21 +5,24 @@ layout(set = 1, binding = 0) uniform Viewport { ivec2 off; }; -layout(location = 0) in vec2 position; -layout(location = 1) in vec2 uv; +layout(location = 0) in ivec2 position; +layout(location = 1) in ivec2 uv; layout(location = 2) in ivec4 color; layout(location = 0) out vec4 col; void main() { - //vec2 shift = vec2(0); + // per vertex shift vec2 shift; shift.x = float(off.x) / view.x; shift.y = -(float(off.y) / view.y); - vec2 pos = position + shift; + // vertex position + vec2 pos; + pos.x = float(position.x)*2.0 / view.x - 1.0; + pos.y = -(float(position.y)*2.0 / view.y - 1.0); - gl_Position = vec4(pos.x, pos.y, 0.0, 1.0); + gl_Position = vec4(pos+shift, 0.0, 1.0); col = vec4(color) / 255.0; } diff --git a/src/renderer.c3 b/src/renderer.c3 index 99f10a5..a62c9c6 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -51,10 +51,10 @@ struct Renderer { // how each vertex is represented in the gpu struct Vertex @packed { struct pos { - float x, y; + short x, y; } struct uv { - float u, v; + short u, v; } struct col { // FIXME: this is shit union { @@ -321,13 +321,13 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) { // at location zero there is the position of the vertex .location = 0, .buffer_slot = 0, // only one buffer so always slot zero - .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .format = GPU_VERTEXELEMENTFORMAT_SHORT2, .offset = Vertex.pos.offsetof, }, { // at location one there are the uv coordinates .location = 1, .buffer_slot = 0, - .format = GPU_VERTEXELEMENTFORMAT_FLOAT2, + .format = GPU_VERTEXELEMENTFORMAT_SHORT2, .offset = Vertex.uv.offsetof, }, { // at location two there is the color @@ -500,12 +500,6 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, } -macro void Vertex.norm(&p, float w, float h) -{ - p.pos.x = p.pos.x * 2.0 / w - 1.0; - p.pos.y = -(p.pos.y * 2.0 / h - 1.0); -} - // an highly inefficient way to draw a single quad, no batching, per-quad upload fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name) { @@ -541,15 +535,11 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color * +-------------+ * v2 v3 */ - quad.vertices.v1 = {.pos = {.x = x, .y = y}, .col.u = color}; - quad.vertices.v2 = {.pos = {.x = x, .y = (float)y+h}, .col.u = color}; - quad.vertices.v3 = {.pos = {.x = (float)x+w, .y = (float)y+h}, .col.u = color}; - quad.vertices.v4 = {.pos = {.x = (float)x+w, .y = y}, .col.u = color}; + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .col.u = color}; + quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .col.u = color}; + quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color}; + quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color}; - quad.vertices.v1.norm(640.0, 480.0); - quad.vertices.v2.norm(640.0, 480.0); - quad.vertices.v3.norm(640.0, 480.0); - quad.vertices.v4.norm(640.0, 480.0); // triangle 1 quad.indices.i1 = 0; // v1 From 6c5acd6f230c2935221107c7ae7c236443fd8295 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 18:15:12 +0200 Subject: [PATCH 096/250] enable foreach for FIFO --- lib/ugui.c3l/src/fifo.c3 | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/ugui.c3l/src/fifo.c3 b/lib/ugui.c3l/src/fifo.c3 index b4c49d4..59802e5 100644 --- a/lib/ugui.c3l/src/fifo.c3 +++ b/lib/ugui.c3l/src/fifo.c3 @@ -44,3 +44,23 @@ fn Type*? Fifo.dequeue(&fifo) fifo.out = (fifo.out + 1) % fifo.arr.len; return ret; } + +macro Type Fifo.get(&fifo, usz i) @operator([]) +{ + return fifo.arr[(fifo.out + i) % fifo.arr.len]; +} + +fn void Fifo.set(&fifo, usz i, Type val) @operator([]=) +{ + fifo.arr[(fifo.out + i) % fifo.arr.len] = val; +} + +macro Type* Fifo.get_ref(&fifo, usz i) @operator(&[]) +{ + return &fifo.arr[(fifo.out + i) % fifo.arr.len]; +} + +macro usz Fifo.len(&fifo) @operator(len) +{ + return fifo.count; +} \ No newline at end of file From f344c989db83caaff255bb103bb7ab26aa6d68e9 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 18:15:33 +0200 Subject: [PATCH 097/250] test different draw calls for one render pass (failed) --- test_renderer.c3 | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/test_renderer.c3 b/test_renderer.c3 index 63f0d5d..fb5d7de 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -18,7 +18,6 @@ fn int main() for (int i = 0; i < 10; i++) { - ren.draw_rect(100,100,100,100,0xff00ff00,""); GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); GPUTexture* swapchain_texture; @@ -47,6 +46,9 @@ fn int main() unreachable("render pass creation went wrong: %s", sdl::get_error()); } + // rect 1 + ren.draw_rect(100,100,100,100,0xff00ff00,""); + GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; if (p == null) { unreachable("no pipeline"); @@ -62,6 +64,21 @@ fn int main() sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); +/* + // rect 2 + ren.draw_rect(0,0,50,50,0xffff0000,""); + + sdl::bind_gpu_graphics_pipeline(pass, p); + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + Viewsize w = {.w = 640, .h = 480}; + w.ox = 25*i; + //w.oy = 25*i; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &w, Viewsize.sizeof); + + sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); +*/ + sdl::end_gpu_render_pass(pass); sdl::submit_gpu_command_buffer(cmdbuf); From c9b74aebc78766c1627e55e5cf65e85c5928438f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 18:15:46 +0200 Subject: [PATCH 098/250] implement z index in command buffer --- lib/ugui.c3l/src/ugui_button.c3 | 20 ++++++------- lib/ugui.c3l/src/ugui_cmd.c3 | 51 ++++++++++++++++++++------------- lib/ugui.c3l/src/ugui_core.c3 | 13 ++++++++- lib/ugui.c3l/src/ugui_div.c3 | 7 +++-- lib/ugui.c3l/src/ugui_slider.c3 | 8 +++--- lib/ugui.c3l/src/ugui_sprite.c3 | 5 ++-- lib/ugui.c3l/src/ugui_text.c3 | 2 +- 7 files changed, 66 insertions(+), 40 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index f9582bc..81b817f 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -40,7 +40,7 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) } // Draw the button - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; return elem.events; } @@ -80,8 +80,8 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s Point off = ctx.center_text(text_size, elem.bounds); text_size.x += off.x; text_size.y += off.y; - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; - ctx.push_string(text_size, label)!; + ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; + ctx.push_string(text_size, label, parent.div.z_index)!; return elem.events; } @@ -116,13 +116,13 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = Id tex_id = ctx.sprite_atlas.id; if (state && on_icon != "") { - ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, type: on_sprite.type)!; + ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!; } else { - ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, type: def_sprite.type)!; + ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!; } // Draw the button - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; return elem.events; } @@ -159,7 +159,7 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s Color col; if (tick_sprite != {}) { col = ctx.style.bgcolor; - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; if (*state) { ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; } @@ -170,7 +170,7 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s col = 0xff00ffffu.to_rgba(); } // Draw the button - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; } } @@ -211,7 +211,7 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta } // Draw the button // FIXME: THIS IS SHIT - ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!; + ctx.push_rect(elem.bounds, ctx.style.bgcolor, parent.div.z_index, do_border: true, do_radius: true)!; Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); - ctx.push_rect(t, col, do_border: false, do_radius: true)!; + ctx.push_rect(t, col, parent.div.z_index, do_border: false, do_radius: true)!; } diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index eee78d4..51120bb 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -1,6 +1,7 @@ module ugui; import std::ascii; +import std::io; // command type enum CmdType { @@ -37,8 +38,9 @@ struct CmdScissor { } // command structure -struct Cmd { +struct Cmd (Printable) { CmdType type; + int z_index; union { CmdRect rect; CmdUpdateAtlas update_atlas; @@ -47,6 +49,12 @@ struct Cmd { } } +// implement the Printable interface +fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic +{ + return f.printf("Cmd{ type: %s, z_index: %d }", cmd.type, cmd.z_index); +} + macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max}) { bool no_area = rect.w <= 0 || rect.h <= 0; @@ -54,8 +62,9 @@ macro bool cull_rect(Rect rect, Rect clip = {0,0,short.max,short.max}) } // FIXME: this whole thing could be done at compile time, maybe -macro Ctx.push_cmd(&ctx, Cmd *cmd) +macro Ctx.push_cmd(&ctx, Cmd *cmd, int z_index) { + cmd.z_index = z_index; Rect rect; switch (cmd.type) { case CMD_RECT: rect = cmd.rect.rect; @@ -66,9 +75,18 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd) return ctx.cmd_queue.enqueue(cmd); } +fn void? Ctx.push_scissor(&ctx, Rect rect, int z_index) +{ + Cmd sc = { + .type = CMD_SCISSOR, + .scissor.rect = rect.intersection(ctx.div_scissor), + }; + ctx.push_cmd(&sc, z_index)!; +} + // FIXME: is this really the best solution? // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) -fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) +fn void? Ctx.push_rect(&ctx, Rect rect, Color color, int z_index, bool do_border = false, bool do_padding = false, bool do_radius = false) { Rect border = ctx.style.border; Rect padding = ctx.style.padding; @@ -82,7 +100,7 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo .rect.color = border_color, .rect.radius = do_radius ? radius : 0, }; - ctx.push_cmd(&cmd)!; + ctx.push_cmd(&cmd, z_index)!; } Cmd cmd = { @@ -97,11 +115,11 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo .rect.radius = do_radius ? radius : 0, }; if (cull_rect(cmd.rect.rect, ctx.div_scissor)) return; - ctx.push_cmd(&cmd)!; + ctx.push_cmd(&cmd, z_index)!; } // TODO: add texture id -fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) +fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) { Cmd cmd = { .type = CMD_SPRITE, @@ -111,16 +129,16 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color h .sprite.texture_id = texture_id, .sprite.hue = hue, }; - ctx.push_cmd(&cmd)!; + ctx.push_cmd(&cmd, z_index)!; } -fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba()) +fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue = 0xffffffffu.to_rgba()) { if (text.len == 0) { return; } - ctx.push_scissor(bounds)!; + ctx.push_scissor(bounds, z_index)!; short baseline = (short)ctx.font.ascender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; @@ -152,7 +170,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu .h = gp.h, }; // push the sprite only if it collides with the bounds - if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, hue)!; + if (!cull_rect(gb, bounds)) ctx.push_sprite(gb, gt, texture_id, z_index, hue)!; line_len += gp.adv; } else if (cp == '\n'){ orig.y += line_height + line_gap; @@ -163,7 +181,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu } // FIXME: we never get here if an error was thrown before - ctx.push_scissor({})!; + ctx.push_scissor({}, z_index)!; } fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas) @@ -178,14 +196,7 @@ fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas) .bpp = (ushort)atlas.type.bpp(), }, }; - ctx.push_cmd(&up)!; + // update the atlases before everything else + ctx.push_cmd(&up, -1)!; } -fn void? Ctx.push_scissor(&ctx, Rect rect) -{ - Cmd sc = { - .type = CMD_SCISSOR, - .scissor.rect = rect.intersection(ctx.div_scissor), - }; - ctx.push_cmd(&sc)!; -} diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 8c4cf75..8078218 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -244,6 +244,7 @@ fn void? Ctx.frame_begin(&ctx) }, .div = { .layout = LAYOUT_ROW, + .z_index = 0, .children_bounds = { .w = ctx.width, .h = ctx.height, @@ -263,6 +264,8 @@ fn void? Ctx.frame_begin(&ctx) // TODO: add a background color taken from a theme or config } +const int DEBUG = 1; + fn void? Ctx.frame_end(&ctx) { Elem* root = ctx.get_elem_by_tree_idx(0)!; @@ -285,10 +288,12 @@ fn void? Ctx.frame_end(&ctx) ctx.sprite_atlas.should_update = false; } -$if 1: +// debug +$if DEBUG == 1: // draw mouse position Cmd cmd = { .type = CMD_RECT, + .z_index = int.max, // hopefully over everything else .rect.rect = { .x = ctx.input.mouse.pos.x - 2, .y = ctx.input.mouse.pos.y - 2, @@ -298,6 +303,12 @@ $if 1: .rect.color = 0xff00ffffu.to_rgba() }; ctx.cmd_queue.enqueue(&cmd)!; + + // dump the command buffer + io::printn("Command Buffer Dump:"); + foreach(idx, c: ctx.cmd_queue) { + io::printfn("\t [%d] = {%s}", idx, c); + } $endif } diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index bff69a3..d4dcae6 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -18,6 +18,7 @@ struct ElemDiv { bool on; float value; } + int z_index; Rect children_bounds; // current frame children bounds Rect pcb; // previous frame children bounds Point origin_r, origin_c; @@ -46,6 +47,8 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo } elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; + elem.div.z_index = parent.div.z_index + 1; + io::printn(elem.div.z_index); // 2. layout the element Rect wanted_size = { @@ -59,7 +62,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo // update the ctx scissor ctx.div_scissor = elem.bounds; - ctx.push_scissor(elem.bounds)!; + ctx.push_scissor(elem.bounds, elem.div.z_index)!; // 4. Fill the div fields elem.div.origin_c = { @@ -71,7 +74,7 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo // Add the background to the draw stack bool do_border = parent.div.layout == LAYOUT_FLOATING; - ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: do_border)!; + ctx.push_rect(elem.bounds, ctx.style.bgcolor, elem.div.z_index, do_border: do_border)!; elem.events = ctx.get_elem_events(elem); diff --git a/lib/ugui.c3l/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 index cdcccf8..a161389 100644 --- a/lib/ugui.c3l/src/ugui_slider.c3 +++ b/lib/ugui.c3l/src/ugui_slider.c3 @@ -61,8 +61,8 @@ fn ElemEvents? Ctx.slider_hor(&ctx, } // Draw the slider background and handle - ctx.push_rect(elem.bounds, bgcolor)!; - ctx.push_rect(elem.slider.handle, handlecolor)!; + ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!; + ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!; return elem.events; } @@ -124,8 +124,8 @@ fn ElemEvents? Ctx.slider_ver(&ctx, } // Draw the slider background and handle - ctx.push_rect(elem.bounds, bgcolor)!; - ctx.push_rect(elem.slider.handle, handlecolor)!; + ctx.push_rect(elem.bounds, bgcolor, parent.div.z_index)!; + ctx.push_rect(elem.slider.handle, handlecolor, parent.div.z_index)!; return elem.events; } diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 1f94b27..0aa913f 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -164,12 +164,13 @@ fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) Id tex_id = ctx.sprite_atlas.id; - return ctx.push_sprite(elem.bounds, uv, tex_id)!; + return ctx.push_sprite(elem.bounds, uv, tex_id, parent.div.z_index)!; } fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) { + Elem *parent = ctx.get_parent()!; Sprite* sprite = ctx.sprite_atlas.get(name)!; Id tex_id = ctx.sprite_atlas.id; - return ctx.push_sprite(bounds, sprite.uv(), tex_id, type: sprite.type)!; + return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!; } diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index dea4562..bf04077 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -26,5 +26,5 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text) elem.bounds = ctx.position_element(parent, text_size, true); if (elem.bounds.is_null()) { return; } - ctx.push_string(elem.bounds, text)!; + ctx.push_string(elem.bounds, text, parent.div.z_index)!; } From ac3fcae649692908b5bcf05fb13110ad6640c2cf Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 22:03:14 +0200 Subject: [PATCH 099/250] sorting the command buffer --- lib/ugui.c3l/src/fifo.c3 | 33 +++++++++++++++++++++++++++---- lib/ugui.c3l/src/ugui_cmd.c3 | 6 ++++++ lib/ugui.c3l/src/ugui_core.c3 | 17 ++++++++++------ lib/ugui.c3l/src/ugui_div.c3 | 1 - lib/ugui.c3l/src/ugui_input.c3 | 8 ++++---- lib/ugui.c3l/src/vtree.c3 | 36 ++++++++++++++++++---------------- 6 files changed, 69 insertions(+), 32 deletions(-) diff --git a/lib/ugui.c3l/src/fifo.c3 b/lib/ugui.c3l/src/fifo.c3 index 59802e5..d9fc52e 100644 --- a/lib/ugui.c3l/src/fifo.c3 +++ b/lib/ugui.c3l/src/fifo.c3 @@ -1,8 +1,10 @@ +module fifo::faults; +faultdef FULL, EMPTY; + module fifo{Type}; import std::core::mem; - -faultdef FULL, EMPTY; +import std::sort; // TODO: specify the allocator @@ -27,7 +29,7 @@ fn void Fifo.free(&fifo) fn void? Fifo.enqueue(&fifo, Type *elem) { if (fifo.count >= fifo.arr.len) { - return FULL?; + return fifo::faults::FULL?; } usz in = (fifo.out + fifo.count) % fifo.arr.len; fifo.arr[in] = *elem; @@ -37,7 +39,7 @@ fn void? Fifo.enqueue(&fifo, Type *elem) fn Type*? Fifo.dequeue(&fifo) { if (fifo.count == 0) { - return EMPTY?; + return fifo::faults::EMPTY?; } Type *ret = &fifo.arr[fifo.out]; fifo.count--; @@ -63,4 +65,27 @@ macro Type* Fifo.get_ref(&fifo, usz i) @operator(&[]) macro usz Fifo.len(&fifo) @operator(len) { return fifo.count; +} + +fn void? Fifo.sort(&fifo) +{ + Type[] arr = mem::new_array(Type, fifo.count); + defer mem::free(arr); + + foreach(i, c: fifo) { + arr[i] = c; + } + + // doesn't keep ordering + //sort::quicksort(arr); + + // seems to keep the right order but we will never know... + // also since most things are already ordered the time is closer to O(n) than to O(n^2) + sort::insertionsort(arr); + + fifo.count = 0; + fifo.out = 0; + foreach (&c: arr) { + fifo.enqueue(c)!; + } } \ No newline at end of file diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index 51120bb..77468cb 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -49,6 +49,12 @@ struct Cmd (Printable) { } } +fn int Cmd.compare_to(Cmd a, Cmd b) +{ + if (a.z_index == b.z_index) return 0; + return a.z_index > b.z_index ? 1 : -1; +} + // implement the Printable interface fn usz? Cmd.to_format(Cmd* cmd, Formatter *f) @dynamic { diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 8078218..a1c7e2b 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -293,7 +293,7 @@ $if DEBUG == 1: // draw mouse position Cmd cmd = { .type = CMD_RECT, - .z_index = int.max, // hopefully over everything else + .z_index = int.max-1, // hopefully over everything else .rect.rect = { .x = ctx.input.mouse.pos.x - 2, .y = ctx.input.mouse.pos.y - 2, @@ -304,11 +304,16 @@ $if DEBUG == 1: }; ctx.cmd_queue.enqueue(&cmd)!; - // dump the command buffer - io::printn("Command Buffer Dump:"); - foreach(idx, c: ctx.cmd_queue) { - io::printfn("\t [%d] = {%s}", idx, c); - } +// dump the command buffer +// io::printn("Command Buffer Dump:"); +// foreach(idx, c: ctx.cmd_queue) { +// io::printfn("\t [%d] = {%s}", idx, c); +// } + ctx.cmd_queue.sort()!; +// io::printn("Sorted Command Buffer Dump:"); +// foreach(idx, c: ctx.cmd_queue) { +// io::printfn("\t [%d] = {%s}", idx, c); +// } $endif } diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index d4dcae6..4bfd45e 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -48,7 +48,6 @@ fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; elem.div.z_index = parent.div.z_index + 1; - io::printn(elem.div.z_index); // 2. layout the element Rect wanted_size = { diff --git a/lib/ugui.c3l/src/ugui_input.c3 b/lib/ugui.c3l/src/ugui_input.c3 index abad51c..5dcb12b 100644 --- a/lib/ugui.c3l/src/ugui_input.c3 +++ b/lib/ugui.c3l/src/ugui_input.c3 @@ -112,8 +112,8 @@ fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons) // Mouse was moved, report absolute position fn void Ctx.input_mouse_abs(&ctx, short x, short y) { - ctx.input.mouse.pos.x = math::clamp(x, 0u16, ctx.width); - ctx.input.mouse.pos.y = math::clamp(y, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(x, (short)0, ctx.width); + ctx.input.mouse.pos.y = math::clamp(y, (short)0, ctx.height); short dx, dy; dx = x - ctx.input.mouse.pos.x; @@ -135,8 +135,8 @@ fn void Ctx.input_mouse_delta(&ctx, short dx, short dy) mx = ctx.input.mouse.pos.x + dx; my = ctx.input.mouse.pos.y + dy; - ctx.input.mouse.pos.x = math::clamp(mx, 0u16, ctx.width); - ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height); + ctx.input.mouse.pos.x = math::clamp(mx, (short)0, ctx.width); + ctx.input.mouse.pos.y = math::clamp(my, (short)0, ctx.height); ctx.input.events.mouse_move = dx != 0 || dy != 0; } diff --git a/lib/ugui.c3l/src/vtree.c3 b/lib/ugui.c3l/src/vtree.c3 index 0a1e851..d18b749 100644 --- a/lib/ugui.c3l/src/vtree.c3 +++ b/lib/ugui.c3l/src/vtree.c3 @@ -1,3 +1,6 @@ +module vtree::faults; +faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT; + module vtree{ElemType}; import std::core::mem; @@ -9,7 +12,6 @@ struct VTree { isz[] refs, ordered_refs; } -faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT; macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } @@ -88,14 +90,14 @@ fn void? VTree.resize(&tree, usz newsize) { // return error when shrinking with too many elements if (newsize < tree.elements) { - return CANNOT_SHRINK?; + return vtree::faults::CANNOT_SHRINK?; } // pack the vector when shrinking to avoid data loss if ((int)newsize < tree.size()) { // FIXME: packing destroys all references to elements of vec // so shrinking may cause dangling pointers - return CANNOT_SHRINK?; + return vtree::faults::CANNOT_SHRINK?; } usz old_size = tree.size(); @@ -120,18 +122,18 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent) { // invalid parent if (!tree.ref_is_valid(parent)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } // no space left if (tree.elements >= tree.size()) { - return TREE_FULL?; + return vtree::faults::TREE_FULL?; } // check if the parent exists // if there are no elements in the tree the first add will set the root if (!tree.ref_is_present(parent) && tree.elements != 0) { - return REFERENCE_NOT_PRESENT?; + return vtree::faults::REFERENCE_NOT_PRESENT?; } // get the first free spot @@ -143,7 +145,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent) } } if (free_spot < 0) { - return TREE_FULL?; + return vtree::faults::TREE_FULL?; } // finally add the element @@ -159,7 +161,7 @@ fn isz? VTree.add(&tree, ElemType elem, isz parent) fn usz? VTree.prune(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { @@ -193,7 +195,7 @@ fn usz VTree.nuke(&tree) fn usz? VTree.subtree_size(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { @@ -215,17 +217,17 @@ fn usz? VTree.subtree_size(&tree, isz ref) fn isz? VTree.children_it(&tree, isz parent, isz *cursor) { if (cursor == null) { - return INVALID_ARGUMENT?; + return vtree::faults::INVALID_ARGUMENT?; } // if the cursor is out of bounds then we are done for sure if (!tree.ref_is_valid(*cursor)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } // same for the parent, if it's invalid it can't have children if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } // find the first child, update the cursor and return the ref @@ -253,7 +255,7 @@ fn isz? VTree.children_it(&tree, isz parent, isz *cursor) fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor) { if (cursor == null) { - return INVALID_ARGUMENT?; + return vtree::faults::INVALID_ARGUMENT?; } isz[] queue = tree.ordered_refs; @@ -300,11 +302,11 @@ fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor) fn isz? VTree.parentof(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { - return REFERENCE_NOT_PRESENT?; + return vtree::faults::REFERENCE_NOT_PRESENT?; } return tree.refs[ref]; @@ -313,11 +315,11 @@ fn isz? VTree.parentof(&tree, isz ref) fn ElemType? VTree.get(&tree, isz ref) { if (!tree.ref_is_valid(ref)) { - return INVALID_REFERENCE?; + return vtree::faults::INVALID_REFERENCE?; } if (!tree.ref_is_present(ref)) { - return REFERENCE_NOT_PRESENT?; + return vtree::faults::REFERENCE_NOT_PRESENT?; } return tree.vector[ref]; From 3002123ef74731f2e319f9b4894043cb013b5202 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 3 Jun 2025 22:36:18 +0200 Subject: [PATCH 100/250] removed bad error handling and replaced it with worse error handling --- src/renderer.c3 | 86 +++++++++++++++++-------------------------------- 1 file changed, 29 insertions(+), 57 deletions(-) diff --git a/src/renderer.c3 b/src/renderer.c3 index a62c9c6..1a246d4 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -101,27 +101,23 @@ $endif // init subsystems if (!sdl::init(INIT_VIDEO)) { - io::eprintfn("sdl error: %s", sdl::get_error()); - libc::exit(1); + unreachable("sdl error: %s", sdl::get_error()); } // create the window self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN); if (self.win == null) { - io::eprintfn("sdl error: %s", sdl::get_error()); - libc::exit(1); + unreachable("sdl error: %s", sdl::get_error()); } // get the gpu device handle self.gpu = sdl::create_gpu_device(GPU_SHADERFORMAT_SPIRV, true, "vulkan"); if (self.gpu == null) { - io::eprintfn("failed to create gpu device: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to create gpu device: %s", sdl::get_error()); } if (!sdl::claim_window_for_gpu_device(self.gpu, self.win)) { - io::eprintfn("failed to claim window for use with gpu: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to claim window for use with gpu: %s", sdl::get_error()); } // initialize the quad buffer @@ -129,16 +125,14 @@ $endif &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof} ); if (self.quad_buffer.vert_buf == null) { - io::eprintfn("failed to initialize quad buffer (vertex): %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error()); } self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof} ); if (self.quad_buffer.idx_buf == null) { - io::eprintfn("failed to initialize quad buffer (index): %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } self.quad_buffer.initialized = true; @@ -189,8 +183,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code s.vert = sdl::create_gpu_shader(self.gpu, &shader_info); if (s.vert == null) { - io::eprintfn("failed to create gpu vertex shader: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to create gpu vertex shader: %s", sdl::get_error()); } } @@ -211,8 +204,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code s.frag = sdl::create_gpu_shader(self.gpu, &shader_info); if (s.frag == null) { - io::eprintfn("failed to create gpu fragment shader: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to create gpu fragment shader: %s", sdl::get_error()); } } @@ -230,21 +222,16 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat char[] frag_code; // create vertex shader - if (vert_path != "") { - vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); - file::load_buffer(vert_path, vert_code)!!; - } + vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); + file::load_buffer(vert_path, vert_code)!!; + defer mem::free(vert_code); // create fragment shader - if (frag_path != "") { - frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); - file::load_buffer(frag_path, frag_code)!!; - } + frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); + file::load_buffer(frag_path, frag_code)!!; + defer mem::free(frag_code); self.load_spirv_shader_from_mem(name, vert_code, frag_code, textures, uniforms); - - if (vert_code.ptr) mem::free(vert_code); - if (frag_code.ptr) mem::free(frag_code); } fn Shader* ShaderList.get_from_name(&self, String name) @@ -296,8 +283,7 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) { Shader *s = self.shaders.get_from_name(shader_name); if (s == null) { - io::eprintfn("error in creating pipeline: no shader named %s", shader_name); - libc::exit(1); + unreachable("error in creating pipeline: no shader named %s", shader_name); } GPUGraphicsPipelineCreateInfo ci = { @@ -375,8 +361,7 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) }; if (p.pipeline == null) { - io::eprintfn("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error()); - libc::exit(1); + unreachable("failed to create pipeline (shaders: %s, type: %s): %s", shader_name, type.nameof, sdl::get_error()); } self.pipelines.push(p); @@ -408,8 +393,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci); if (texture == null) { - io::eprintfn("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); - libc::exit(1); + unreachable("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); } // the sampler description, how the texture should be sampled @@ -425,8 +409,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci); if (sampler == null) { - io::eprintfn("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); - libc::exit(1); + unreachable("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); } Texture t = { @@ -444,41 +427,35 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, { Texture* t = self.textures.get_from_name(name); if (t == null || t.texture == null) { - io::eprintf("failed updating texture: no texture named %s", name); - libc::exit(1); + unreachable("failed updating texture: no texture named %s", name); } GPUTexture* texture = t.texture; // FIXME: do a better job at validating the copy if (x > t.width || y > t.height) { - io::eprintf("failed updating texture: attempting to copy outside of the texture region", name); - libc::exit(1); + unreachable("failed updating texture: attempting to copy outside of the texture region", name); } // upload image data GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu); if (cmdbuf == null) { - io::eprintfn("failed to upload texture data at acquiring command buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload texture data at acquiring command buffer: %s", sdl::get_error()); } GPUCopyPass* copypass = sdl::begin_gpu_copy_pass(cmdbuf); if (copypass == null) { - io::eprintfn("failed to upload texture data at beginning copy pass: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload texture data at beginning copy pass: %s", sdl::get_error()); } GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = pixels.len} ); if (buf == null) { - io::eprintfn("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); } char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); if (gpu_mem == null) { - io::eprintfn("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); } // copy the data to the driver's memory gpu_mem[:pixels.len] = pixels[..]; @@ -493,8 +470,7 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, sdl::end_gpu_copy_pass(copypass); if (!sdl::submit_gpu_command_buffer(cmdbuf)) { - io::eprintfn("failed to upload texture data at command buffer submission: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload texture data at command buffer submission: %s", sdl::get_error()); } sdl::release_gpu_transfer_buffer(self.gpu, buf); } @@ -505,22 +481,19 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color { // upload the quad data to the gpu if (self.quad_buffer.initialized == false) { - io::eprintfn("quad buffer not initialized"); - libc::exit(1); + unreachable("quad buffer not initialized"); } GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} ); if (buf == null) { - io::eprintfn("failed to create gpu transfer buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); } Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); if (quad == null) { - io::eprintfn("failed to map gpu transfer buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); } /* v1 v4 @@ -554,8 +527,7 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); if (cmd == null) { - io::eprintfn("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); - libc::exit(1); + unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); } GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); From e3d87525d41b48b074ba5f55a08c40a3e3096d34 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 7 Jun 2025 10:35:08 +0200 Subject: [PATCH 101/250] draw multiple quads --- Makefile | 2 +- TODO | 12 +++++- src/renderer.c3 | 98 +++++++++++++++++++++++++++--------------------- test_renderer.c3 | 27 ++++--------- 4 files changed, 74 insertions(+), 65 deletions(-) diff --git a/Makefile b/Makefile index a19a0d7..5e0ca86 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* scripts/compile_shaders.sh - c3c compile-run -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 + c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 diff --git a/TODO b/TODO index 1817dd9..a3055f3 100644 --- a/TODO +++ b/TODO @@ -60,8 +60,8 @@ to maintain focus until mouse release (fix scroll bars) ## Commands [x] rect commads should have: - - border width - - border radius + - border width + - border radius [x] add a command to update an atlas [ ] New window command, useful for popups @@ -107,3 +107,11 @@ to maintain focus until mouse release (fix scroll bars) queried by the user for later use. This allows for smaller caches and in general reduces some load, since most of the stuff is recomputed for every frame. +## SDL3 Renderer + +- smart batching +- maybe use instancing since we are always drawing the same geometry. With instancing every + different quad could have its coulour, border and radius with much better performance than + issuing a draw call for every quad (and uploading it) + https://rastertek.com/dx11win10tut48.html + https://www.braynzarsoft.net/viewtutorial/q16390-33-instancing-with-indexed-primitives \ No newline at end of file diff --git a/src/renderer.c3 b/src/renderer.c3 index 1a246d4..f4f63fe 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -28,11 +28,13 @@ struct Texture { uint id; } -// gpu buffer that contains a single quad +// The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH +const int MAX_QUAD_BATCH = 16; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; bool initialized; + int count; } alias ShaderList = List{Shader}; @@ -93,6 +95,8 @@ $if DEBUG == 0: sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland"); } $else + // in debug mode set the video driver to X11 because renderdoc/ + // doesn't support debugging in wayland yet. sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); $endif @@ -120,16 +124,18 @@ $endif unreachable("failed to claim window for use with gpu: %s", sdl::get_error()); } + // // initialize the quad buffer + // ========================== self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu, - &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof} + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} ); if (self.quad_buffer.vert_buf == null) { unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error()); } self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, - &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof} + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} ); if (self.quad_buffer.idx_buf == null) { unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); @@ -476,9 +482,13 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, } -// an highly inefficient way to draw a single quad, no batching, per-quad upload -fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color, String shader_name) +// Push a quad into the quad buffer, return true on success and false on failure +fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color) { + if (self.quad_buffer.count >= MAX_QUAD_BATCH) { + return false; + } + // upload the quad data to the gpu if (self.quad_buffer.initialized == false) { unreachable("quad buffer not initialized"); @@ -512,13 +522,11 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .col.u = color}; quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color}; quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color}; - - - // triangle 1 + // triangle 1 indices quad.indices.i1 = 0; // v1 quad.indices.i2 = 1; // v2 quad.indices.i3 = 3; // v4 - // triangle 2 + // triangle 2 indices quad.indices.i4 = 1; // v2 quad.indices.i5 = 2; // v3 quad.indices.i6 = 3; // v4 @@ -532,15 +540,16 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); // upload vertices + QuadBuffer* qb = &self.quad_buffer; sdl::upload_to_gpu_buffer(cpy, &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, - &&(GPUBufferRegion){.buffer = self.quad_buffer.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, + &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, false ); // upload indices sdl::upload_to_gpu_buffer(cpy, &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, - &&(GPUBufferRegion){.buffer = self.quad_buffer.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, + &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, false ); @@ -551,40 +560,43 @@ fn void Renderer.draw_rect(&self, short x, short y, short w, short h, uint color sdl::release_gpu_transfer_buffer(self.gpu, buf); sdl::wait_for_gpu_idle(self.gpu); -/* - // now finally draw the quad - // if we are not in a render pass then we can't render shit - if (self.render_cmd == null) { - unreachable("start rendering first before trying to render a quad"); - } + qb.count++; - // FIXME: this could be done at the start of rendering - GPUTexture* t; - if (!sdl::wait_and_acquire_gpu_swapchain_texture(self.render_cmd, self.win, &t, null, null)) { - unreachable("failed to acquire swapchain texture: %s", sdl::get_error()); - } - - // TODO: begin render pass - - Pipeline* p = self.pipelines.get_from_name(shader_name); - if (p == null) { - unreachable("no pipeline named: %s", shader_name); - } - - // bind the data - sdl::bind_gpu_graphics_pipeline(self.render_pass, pipeline); - sdl::bind_gpu_vertex_buffer(self.render_pass, 0, - &&(GPUBufferBinding){.buffer = self.quad_buffer.vert_buf, .offset = 0}, 1 - ); - sdl::bind_gpu_index_buffer(self.render_pass, 0, - &&(GPUBufferBinding){.buffer = self.quad_buffer.idx_buf, .offset = 0}, 1 - ); - - sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, 0, 0, 0); -*/ + return true; +} + +// draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense +// to draw them one a the time +fn void Renderer.draw_quads(&self, GPURenderPass* pass) +{ + QuadBuffer* qb = &self.quad_buffer; + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = 0}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + + // we need instancing to not do this + for (int i = 0; i < qb.count; i++) { + sdl::draw_gpu_indexed_primitives(pass, 6, 1, i*6, i*4, 0); + } + + qb.count = 0; } -// TODO: fn Renderer.draw_quad, it has to use a vertex buffer and an index buffer // TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture // TODO: fn Renderer.begin_render -// TODO: fn Renderer.end_render \ No newline at end of file +// TODO: fn Renderer.end_render + +/// === NOTES === +/* 1. The uniform data is per-render pass. So you can do: + * - push uniform + * - draw 1 + * - draw 2 + * But not: + * - push uniform + * - draw + * - push new uniform + * - draw + * And not even: + * - draw + * - push uniform + * - draw + */ \ No newline at end of file diff --git a/test_renderer.c3 b/test_renderer.c3 index fb5d7de..6559bd1 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -47,7 +47,11 @@ fn int main() } // rect 1 - ren.draw_rect(100,100,100,100,0xff00ff00,""); + ren.push_quad(100,100,100,100,0xff00ff00); + // rect 2 + ren.push_quad(0,0,20,20,0xff0000ff); + // rect 3 + ren.push_quad(200,300,50,50,0xffff0000); GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; if (p == null) { @@ -55,29 +59,14 @@ fn int main() } sdl::bind_gpu_graphics_pipeline(pass, p); - sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1); - sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + Viewsize v = {.w = 640, .h = 480}; v.ox = 50*i; v.oy = 50*i; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); - sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); - -/* - // rect 2 - ren.draw_rect(0,0,50,50,0xffff0000,""); - - sdl::bind_gpu_graphics_pipeline(pass, p); - sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = ren.quad_buffer.vert_buf, .offset = 0}}, 1); - sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = ren.quad_buffer.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); - Viewsize w = {.w = 640, .h = 480}; - w.ox = 25*i; - //w.oy = 25*i; - sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &w, Viewsize.sizeof); - - sdl::draw_gpu_indexed_primitives(pass, 6, 1, 0, 0, 0); -*/ + ren.draw_quads(pass); sdl::end_gpu_render_pass(pass); sdl::submit_gpu_command_buffer(cmdbuf); From 6e65700f38cc310f11c2511514559234aa2cf16c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 7 Jun 2025 12:42:57 +0200 Subject: [PATCH 102/250] scary quads and nice sprites --- resources/shaders/source/font.frag.glsl | 18 ---- resources/shaders/source/font.vert.glsl | 26 ----- resources/shaders/source/sprite.frag.glsl | 14 +++ resources/shaders/source/sprite.vert.glsl | 22 ++++ src/renderer.c3 | 119 ++++++++++++++++++++-- test_renderer.c3 | 83 +++++++++++++-- 6 files changed, 218 insertions(+), 64 deletions(-) delete mode 100644 resources/shaders/source/font.frag.glsl delete mode 100644 resources/shaders/source/font.vert.glsl create mode 100644 resources/shaders/source/sprite.frag.glsl create mode 100644 resources/shaders/source/sprite.vert.glsl diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl deleted file mode 100644 index 1e43b1a..0000000 --- a/resources/shaders/source/font.frag.glsl +++ /dev/null @@ -1,18 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// texture uv coordinate in texture space -in vec2 uv; -uniform sampler2DRect ts; - -const vec3 textcolor = vec3(1.0, 1.0, 1.0); - - -void main() -{ - //gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f); - gl_FragColor = vec4(textcolor, texture(ts, uv)); -} diff --git a/resources/shaders/source/font.vert.glsl b/resources/shaders/source/font.vert.glsl deleted file mode 100644 index 8d429cb..0000000 --- a/resources/shaders/source/font.vert.glsl +++ /dev/null @@ -1,26 +0,0 @@ -#version 330 core - -// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels -uniform ivec2 viewsize; -uniform ivec2 texturesize; - -// both position and and uv are in pixels, they where converted to floats when -// passed to the shader -layout(location = 0) in vec2 position; -layout(location = 1) in vec2 txcoord; - -out vec2 uv; - -void main() -{ - vec2 v = vec2(float(viewsize.x), float(viewsize.y)); - -// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f); - vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y); - vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f); - - gl_Position = pos; - // since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to - // be normalized - uv = vec2(txcoord.x, txcoord.y); -} diff --git a/resources/shaders/source/sprite.frag.glsl b/resources/shaders/source/sprite.frag.glsl new file mode 100644 index 0000000..142cbf5 --- /dev/null +++ b/resources/shaders/source/sprite.frag.glsl @@ -0,0 +1,14 @@ +#version 450 + +layout(location = 0) in vec2 uv; +layout(location = 0) out vec4 fragColor; + +layout(set = 2, binding = 0) uniform sampler2D tx; + +void main() +{ + ivec2 ts = textureSize(tx, 0); + vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 real_uv = uv / fts; + fragColor = texture(tx, real_uv); +} \ No newline at end of file diff --git a/resources/shaders/source/sprite.vert.glsl b/resources/shaders/source/sprite.vert.glsl new file mode 100644 index 0000000..4ecba3d --- /dev/null +++ b/resources/shaders/source/sprite.vert.glsl @@ -0,0 +1,22 @@ +#version 450 + +layout(set = 1, binding = 0) uniform Viewport { + ivec2 view; + ivec2 not_needed; +}; + +layout(location = 0) in ivec2 position; +layout(location = 1) in ivec2 in_uv; +layout(location = 2) in ivec4 color; + +layout(location = 0) out vec2 out_uv; + +void main() +{ + vec2 pos; + pos.x = float(position.x)*2.0 / view.x - 1.0; + pos.y = -(float(position.y)*2.0 / view.y - 1.0); + gl_Position = vec4(pos, 0.0, 1.0); + + out_uv = vec2(float(in_uv.x), float(in_uv.y)); +} diff --git a/src/renderer.c3 b/src/renderer.c3 index f4f63fe..ebcfd54 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -29,12 +29,13 @@ struct Texture { } // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH -const int MAX_QUAD_BATCH = 16; +const int MAX_QUAD_BATCH = 128; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; bool initialized; int count; + int off; // the offset to draw from } alias ShaderList = List{Shader}; @@ -373,14 +374,15 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) self.pipelines.push(p); } +// NOTE: with TEXTUREUSAGE_SAMPLER the texture format cannot be intger _UINT so it has to be nermalized enum TextureType : (GPUTextureFormat format) { - FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UINT, - JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UINT + FULL_COLOR = GPU_TEXTUREFORMAT_R8G8B8A8_UNORM, + JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM } // create a new gpu texture from a pixel buffer, the format has to be specified // the new texture s given an id and pushed into a texture list -fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, ushort width, ushort height) +fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, uint width, uint height) { uint id = name.hash(); @@ -393,7 +395,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels .width = width, .height = height, .layer_count_or_depth = 1, - .num_levels = 0, // no mip maps + .num_levels = 1, // no mip maps so just one level // .sample_count not used since the texture is not a render target }; @@ -429,7 +431,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels self.update_texture(name, pixels, width, height); } -fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, ushort height, ushort x = 0, ushort y = 0) +fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) { Texture* t = self.textures.get_from_name(name); if (t == null || t.texture == null) { @@ -482,6 +484,87 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, ushort width, } +fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) { + if (self.quad_buffer.count >= MAX_QUAD_BATCH) { + return false; + } + + // upload the quad data to the gpu + if (self.quad_buffer.initialized == false) { + unreachable("quad buffer not initialized"); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (buf == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (quad == null) { + unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); + } + + /* v1 v4 + * +-------------+ + * | _/| + * | _/ | + * | 1 _/ | + * | _/ | + * | _/ | + * | _/ 2 | + * |/ | + * +-------------+ + * v2 v3 + */ + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}}; + quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}}; + quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}}; + quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}}; + // triangle 1 indices + quad.indices.i1 = 0; // v1 + quad.indices.i2 = 1; // v2 + quad.indices.i3 = 3; // v4 + // triangle 2 indices + quad.indices.i4 = 1; // v2 + quad.indices.i5 = 2; // v3 + quad.indices.i6 = 3; // v4 + + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + QuadBuffer* qb = &self.quad_buffer; + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::release_gpu_transfer_buffer(self.gpu, buf); + //sdl::wait_for_gpu_idle(self.gpu); + + qb.count++; + + return true; +} + // Push a quad into the quad buffer, return true on success and false on failure fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color) { @@ -558,7 +641,7 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } sdl::release_gpu_transfer_buffer(self.gpu, buf); - sdl::wait_for_gpu_idle(self.gpu); + //sdl::wait_for_gpu_idle(self.gpu); qb.count++; @@ -570,17 +653,27 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color fn void Renderer.draw_quads(&self, GPURenderPass* pass) { QuadBuffer* qb = &self.quad_buffer; - sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = 0}}, 1); - sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); + + if (qb.off == qb.count) return; + + sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); + sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); // we need instancing to not do this - for (int i = 0; i < qb.count; i++) { + for (int i = 0; i < qb.count - qb.off; i++) { sdl::draw_gpu_indexed_primitives(pass, 6, 1, i*6, i*4, 0); } - qb.count = 0; + qb.off = qb.count; } +fn void Renderer.reset_quads(&self) +{ + self.quad_buffer.count = 0; + self.quad_buffer.off = 0; +} + + // TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture // TODO: fn Renderer.begin_render // TODO: fn Renderer.end_render @@ -599,4 +692,8 @@ fn void Renderer.draw_quads(&self, GPURenderPass* pass) * - draw * - push uniform * - draw + * + * 2. The GPU buffers are read per-command-buffer and not per + * render pass. So I cannot override an element in the buffer + * before submitting the command buffer. */ \ No newline at end of file diff --git a/test_renderer.c3 b/test_renderer.c3 index 6559bd1..e9c6302 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -2,6 +2,8 @@ import sdlrenderer::ren; import std::io; import std::thread; import sdl3::sdl; +import std::compression::qoi; +import std::core::mem::allocator; struct Viewsize @align(16) { int w, h; @@ -13,18 +15,31 @@ fn int main() ren::Renderer ren; ren.init("test window"); + // TODO: these could be the same function ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); ren.create_pipeline("rect shader", RECT); - for (int i = 0; i < 10; i++) { - + // load the tux qoi image + QOIDesc img_desc; + char[] img_pixels = qoi::read(allocator::temp(), "resources/tux.qoi", &img_desc)!!; + // and put it in a texture + ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height); + // create a new pipeline to use the texture + ren.load_spirv_shader_from_file("sprite shader", "resources/shaders/compiled/sprite.vert.spv", "resources/shaders/compiled/sprite.frag.spv", 1, 1); + ren.create_pipeline("sprite shader", SPRITE); + for (int i = 0; i < 20; i++) { GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); GPUTexture* swapchain_texture; sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); - + + GPURenderPass* pass; + GPUGraphicsPipeline* p; + Viewsize v = {.w = 640, .h = 480}; + + // Colored Rectangles Render Pass // FIXME: if doing damage tracking DO NOT clear the screen - GPURenderPass* pass = sdl::begin_gpu_render_pass(cmdbuf, + pass = sdl::begin_gpu_render_pass(cmdbuf, &&(GPUColorTargetInfo){ .texture = swapchain_texture, .mip_level = 0, @@ -53,24 +68,74 @@ fn int main() // rect 3 ren.push_quad(200,300,50,50,0xffff0000); - GPUGraphicsPipeline* p = ren.pipelines.get_from_name("rect shader").pipeline; + p = ren.pipelines.get_from_name("rect shader").pipeline; if (p == null) { unreachable("no pipeline"); } sdl::bind_gpu_graphics_pipeline(pass, p); - Viewsize v = {.w = 640, .h = 480}; - v.ox = 50*i; - v.oy = 50*i; - + v.ox = 10*i; + v.oy = 10*i; sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); ren.draw_quads(pass); sdl::end_gpu_render_pass(pass); + // End Rectangle Render Pass + + + // Textured Rectangles Render Pass + pass = sdl::begin_gpu_render_pass(cmdbuf, + &&(GPUColorTargetInfo){ + .texture = swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, + .load_op = GPU_LOADOP_DONT_CARE, // clear the screen at the start of the render pass + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + + if (pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + p = ren.pipelines.get_from_name("sprite shader").pipeline; + if (p == null) { + unreachable("no pipeline"); + } + + sdl::bind_gpu_graphics_pipeline(pass, p); + // in this case it is not an offset but the texture size in pixels + v.ox = 54; + v.oy = 64; + sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); + + // bind the pipeline's sampler + ren::Texture* tx = ren.textures.get_from_name("tux"); + sdl::bind_gpu_fragment_samplers(pass, 0, + (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 + ); + + // tux + ren.push_sprite(300, 0, 54, 64, 0, 0); + + ren.draw_quads(pass); + + sdl::end_gpu_render_pass(pass); + // End Textured Rectangle Render Pass + sdl::submit_gpu_command_buffer(cmdbuf); + ren.reset_quads(); thread::sleep_ms(250); } From bd8c73ecd5818542f94a587c919c9dfdbb78ce7d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 12:30:25 +0200 Subject: [PATCH 103/250] sdl3.c3l as a submodule --- .gitmodules | 4 ++++ lib/sdl3.c3l | 1 + project.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 160000 lib/sdl3.c3l diff --git a/.gitmodules b/.gitmodules index e9200f8..487cebd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,7 @@ path = lib/schrift.c3l/thirdparty/libschrift url = https://github.com/tomolt/libschrift ignore = dirty +[submodule "lib/sdl3.c3l"] + path = lib/sdl3.c3l + url = https://git.alemauri.eu/alema/sdl3.c3l + ignore = dirty diff --git a/lib/sdl3.c3l b/lib/sdl3.c3l new file mode 160000 index 0000000..076355e --- /dev/null +++ b/lib/sdl3.c3l @@ -0,0 +1 @@ +Subproject commit 076355e2d126e7546e53663b97e8dec22667d34d diff --git a/project.json b/project.json index 21e419c..2baa29d 100644 --- a/project.json +++ b/project.json @@ -1,7 +1,7 @@ { "langrev": "1", "warnings": ["no-unused"], - "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries", "../sdl3.c3l"], + "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], "dependencies": ["raylib55", "sdl3", "ugui"], "features": [], "authors": ["Alessandro Mauri "], From 21aa70d3406a5f79a4d0cc9f8bc1caf6573ec56e Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 15:09:37 +0200 Subject: [PATCH 104/250] create binary directory before copying --- lib/grapheme.c3l/Makefile | 1 + lib/mini-qoi.c3l/Makefile | 1 + lib/schrift.c3l/Makefile | 1 + 3 files changed, 3 insertions(+) diff --git a/lib/grapheme.c3l/Makefile b/lib/grapheme.c3l/Makefile index f753fc0..09193f1 100644 --- a/lib/grapheme.c3l/Makefile +++ b/lib/grapheme.c3l/Makefile @@ -1,3 +1,4 @@ all: make -C thirdparty/libgrapheme + mkdir -p linux-x64 cp thirdparty/libgrapheme/libgrapheme.a linux-x64/libgrapheme.a diff --git a/lib/mini-qoi.c3l/Makefile b/lib/mini-qoi.c3l/Makefile index 53230c5..323ee89 100644 --- a/lib/mini-qoi.c3l/Makefile +++ b/lib/mini-qoi.c3l/Makefile @@ -1,4 +1,5 @@ all: thirdparty/mini-qoi/mqoi.a + mkdir -p linux-x64 cp thirdparty/mini-qoi/mqoi.a linux-x64/libmqoi.a thirdparty/mini-qoi/mqoi.a: diff --git a/lib/schrift.c3l/Makefile b/lib/schrift.c3l/Makefile index ff62ec4..1de46d5 100644 --- a/lib/schrift.c3l/Makefile +++ b/lib/schrift.c3l/Makefile @@ -1,3 +1,4 @@ all: make -C thirdparty/libschrift + mkdir -p linux-x64 cp thirdparty/libschrift/libschrift.a linux-x64/libschrift.a From 47eb3ff9076469c5815f2ada32ff375fac80b825 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 15:50:29 +0200 Subject: [PATCH 105/250] moved vendor libraries to lib/ as a submodule --- .gitmodules | 4 ++++ lib/vendor | 1 + project.json | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) create mode 160000 lib/vendor diff --git a/.gitmodules b/.gitmodules index 487cebd..6d70ec6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,3 +18,7 @@ path = lib/sdl3.c3l url = https://git.alemauri.eu/alema/sdl3.c3l ignore = dirty +[submodule "lib/vendor"] + path = lib/vendor + url = https://github.com/c3lang/vendor + ignore = dirty diff --git a/lib/vendor b/lib/vendor new file mode 160000 index 0000000..db00622 --- /dev/null +++ b/lib/vendor @@ -0,0 +1 @@ +Subproject commit db006221a8af625630fdb8b56707f3d07d4314a2 diff --git a/project.json b/project.json index 2baa29d..c2216f9 100644 --- a/project.json +++ b/project.json @@ -1,7 +1,7 @@ { "langrev": "1", "warnings": ["no-unused"], - "dependency-search-paths": ["lib", "../../Programs/Source/c3-vendor/libraries"], + "dependency-search-paths": ["lib", "lib/vendor/libraries"], "dependencies": ["raylib55", "sdl3", "ugui"], "features": [], "authors": ["Alessandro Mauri "], From 39bd7fb8bc2fa6b7008f0dd47ee851e225724bb2 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 15:51:16 +0200 Subject: [PATCH 106/250] removed module raylib.c3l (non vendor) --- .gitmodules | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.gitmodules b/.gitmodules index 6d70ec6..921026c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,7 +1,3 @@ -[submodule "lib/raylib.c3l"] - path = lib/raylib.c3l - url = https://github.com/NexushasTaken/raylib.c3l - ignore = dirty [submodule "lib/grapheme.c3l/thirdparty/libgrapheme"] path = lib/grapheme.c3l/thirdparty/libgrapheme url = git://git.suckless.org/libgrapheme From 6208711292926ef53632c3adefd03fe58c4f5e12 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 16:06:49 +0200 Subject: [PATCH 107/250] use the language's qoi decoder --- .gitmodules | 4 - lib/mini-qoi.c3l/Makefile | 7 -- lib/mini-qoi.c3l/libmqoi.c3i | 122 --------------------------- lib/mini-qoi.c3l/manifest.json | 9 -- lib/mini-qoi.c3l/project.json | 14 --- lib/mini-qoi.c3l/thirdparty/mini-qoi | 1 - lib/ugui.c3l/src/ugui_sprite.c3 | 33 +------- 7 files changed, 4 insertions(+), 186 deletions(-) delete mode 100644 lib/mini-qoi.c3l/Makefile delete mode 100644 lib/mini-qoi.c3l/libmqoi.c3i delete mode 100644 lib/mini-qoi.c3l/manifest.json delete mode 100644 lib/mini-qoi.c3l/project.json delete mode 160000 lib/mini-qoi.c3l/thirdparty/mini-qoi diff --git a/.gitmodules b/.gitmodules index 921026c..74cac9a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,10 +2,6 @@ path = lib/grapheme.c3l/thirdparty/libgrapheme url = git://git.suckless.org/libgrapheme ignore = dirty -[submodule "lib/mini-qoi.c3l/thirdparty/mini-qoi"] - path = lib/mini-qoi.c3l/thirdparty/mini-qoi - url = https://github.com/shraiwi/mini-qoi - ignore = dirty [submodule "lib/schrift.c3l/thirdparty/libschrift"] path = lib/schrift.c3l/thirdparty/libschrift url = https://github.com/tomolt/libschrift diff --git a/lib/mini-qoi.c3l/Makefile b/lib/mini-qoi.c3l/Makefile deleted file mode 100644 index 323ee89..0000000 --- a/lib/mini-qoi.c3l/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -all: thirdparty/mini-qoi/mqoi.a - mkdir -p linux-x64 - cp thirdparty/mini-qoi/mqoi.a linux-x64/libmqoi.a - -thirdparty/mini-qoi/mqoi.a: - gcc -O2 -c -o thirdparty/mini-qoi/mqoi.o thirdparty/mini-qoi/src/mini_qoi.c - ar -rc thirdparty/mini-qoi/mqoi.a thirdparty/mini-qoi/mqoi.o diff --git a/lib/mini-qoi.c3l/libmqoi.c3i b/lib/mini-qoi.c3l/libmqoi.c3i deleted file mode 100644 index 330145d..0000000 --- a/lib/mini-qoi.c3l/libmqoi.c3i +++ /dev/null @@ -1,122 +0,0 @@ -module mqoi; - -macro rgb_hash(Rgb px) => ((px.r * 3 + px.g * 5 + px.b * 7) & 0b00111111); -macro mqoi_rgba_hash(Rgba px) => ((px.r * 3 + px.g * 5 + px.b * 7 + px.a * 11) & 0b00111111); - -const uint MQOI_HEADER_SIZE = 14; - -const char MQOI_MASK_OP_2B = 0b11000000; -const char MQOI_MASK_OP_8B = 0b11111111; - -const char MQOI_MASK_OP_LUMA_DG = 0b00111111; -const char MQOI_MASK_OP_RUN = 0b00111111; - -// basic types - -enum DescErr : uint (uint value) { - MQOI_DESC_OK = 0, // The descriptor is valid - MQOI_DESC_INVALID_MAGIC = 1, // The magic value isn't correct - MQOI_DESC_INVALID_CHANNELS = 2, // The channel number isn't valid - MQOI_DESC_INVALID_COLORSPACE = 3, // The colorspace isn't valid -} - -enum Op : char (char value) { - MQOI_OP2_INDEX = (0b00 << 6), - MQOI_OP2_DIFF = (0b01 << 6), - MQOI_OP2_LUMA = (0b10 << 6), - MQOI_OP2_RUN = (0b11 << 6), - MQOI_OP8_RUN_RGB = (0b11111110), - MQOI_OP8_RUN_RGBA = (0b11111111), -} - -enum Channels : char (char value) { - MQOI_CHANNELS_RGB = 3, - MQOI_CHANNELS_RGBA = 4, -} - -enum Colorspace : char (char value) { - MQOI_COLORSPACE_SRGB = 0, - MQOI_COLORSPACE_LINEAR = 1, -} - -union Rgb { - struct { char r, g, b; } - char[3] value; -} - -union Rgba{ - struct { char r, g, b, a; } - char[4] value; -} - -struct Desc { - char head; - - char[4] magic; - char[4] width; // big-endian width - char[4] height; // big-endian height - char channels; - char colorspace; -} - -// ==== chunks ==== - -union Chunk { - struct { - char head; - union { - Rgb rgb; - Rgba rgba; - char drdb; - } - } - char[5] value; -} - -// ==== codecs ==== - -struct Encoding { - Rgba[64] hashtable; - Rgba prev_px; - Chunk working_chunk; - char working_chunk_size; -} - -struct Dec { - Rgba[64] hashtable; - Rgba prev_px; - Chunk curr_chunk; - bitstruct : char { - char curr_chunk_head : 0..3; - char curr_chunk_size : 4..7; - } - uint pix_left; -} - -// ==== utilities ==== - -fn void u32_write(uint * n, char * dest) @extern("mqoi_u32_write"); -fn void u32_read(char * src, uint * n) @extern("mqoi_u32_read"); - -// ==== Desc ==== - -fn void desc_init(Desc* desc) @extern("mqoi_desc_init"); -fn void desc_push(Desc* desc, char byte) @extern("mqoi_desc_push"); -fn char* desc_pop(Desc* desc) @extern("mqoi_desc_pop"); -fn char desc_verify(Desc* desc, uint* w, uint* h) @extern("mqoi_desc_verify"); -fn bool desc_done(Desc* desc) @extern("mqoi_desc_done"); - -/* the encoder is still WIP -void mqoi_enc_init(mqoi_enc_t * enc); -void mqoi_enc_push(mqoi_enc_t * enc, Rgba * pix) -Chunk * mqoi_enc_pop(mqoi_enc_t * enc, char * size); -*/ - -// ==== Dec ==== - -fn void dec_init(Dec* dec, uint n_pix) @extern("mqoi_dec_init"); -fn void dec_push(Dec* dec, char byte) @extern("mqoi_dec_push"); -fn char dec_take(Dec* dec, char* bytes) @extern("mqoi_dec_take"); -fn Rgba* dec_pop(Dec* dec) @extern("mqoi_dec_pop"); -fn bool dec_done(Dec* dec) @extern("mqoi_dec_done"); - diff --git a/lib/mini-qoi.c3l/manifest.json b/lib/mini-qoi.c3l/manifest.json deleted file mode 100644 index 4043957..0000000 --- a/lib/mini-qoi.c3l/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "provides" : "mqoi", - "targets" : { - "linux-x64" : { - "dependencies" : [], - "linked-libraries" : ["mqoi", "c"] - } - } -} diff --git a/lib/mini-qoi.c3l/project.json b/lib/mini-qoi.c3l/project.json deleted file mode 100644 index cda4a4d..0000000 --- a/lib/mini-qoi.c3l/project.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "langrev": "1", - "warnings": [ "no-unused" ], - "dependency-search-paths": [ ".." ], - "dependencies": [ "mqoi" ], - "authors": [ "Alessandro Mauri ", "Shranav Palakurthi"], - "version": "0.1.0", - "sources": [ ], - "output": "build", - "target": "linux-x64", - "features": [], - "cpu": "generic", - "opt": "O0", -} diff --git a/lib/mini-qoi.c3l/thirdparty/mini-qoi b/lib/mini-qoi.c3l/thirdparty/mini-qoi deleted file mode 160000 index f8b5a8a..0000000 --- a/lib/mini-qoi.c3l/thirdparty/mini-qoi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8b5a8a4d2eeee68b52e143060b7412ba78b5077 diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 0aa913f..7dca870 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -3,7 +3,7 @@ module ugui; import std::core::mem::allocator; import std::collections::map; import std::io; -import mqoi; +import std::compression::qoi; const usz SRITES_PER_ATLAS = 64; @@ -102,37 +102,12 @@ fn void? Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, us fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) { - mqoi::Desc image_desc; - uint w, h; + QOIDesc desc; - File file = file::open(path, "rb")!; - defer (void) file.close(); - - while (!mqoi::desc_done(&image_desc)) { - mqoi::desc_push(&image_desc, file.read_byte()!); - } - if (mqoi::desc_verify(&image_desc, &w, &h) != 0) { - return io::FILE_NOT_VALID?; - } - - mqoi::Dec dec; - mqoi::Rgba* px; - - usz idx; - char[] pixels = mem::new_array(char, (usz)w*h*4); + char[] pixels = qoi::read(allocator::heap(), path, &desc, QOIChannels.RGBA)!; defer mem::free(pixels); - mqoi::dec_init(&dec, w*h); - while (!mqoi::dec_done(&dec)) { - mqoi::dec_push(&dec, file.read_byte()!); - - while ((px = mqoi::dec_pop(&dec)) != null) { - pixels[idx..idx+3] = px.value[..]; - idx += 4; - } - } - - ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!; + ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!; } fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) From c4c9716d618f80e11e82bad305e17f3c955512a8 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 9 Jun 2025 16:35:23 +0200 Subject: [PATCH 108/250] remove mqoi dependency in ugui manifest --- lib/ugui.c3l/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ugui.c3l/manifest.json b/lib/ugui.c3l/manifest.json index 8211665..813ed39 100644 --- a/lib/ugui.c3l/manifest.json +++ b/lib/ugui.c3l/manifest.json @@ -4,8 +4,8 @@ "targets" : { "linux-x64" : { "link-args" : [], - "dependencies" : ["schrift", "grapheme", "mqoi"], + "dependencies" : ["schrift", "grapheme"], "linked-libraries" : [] } } -} \ No newline at end of file +} From 3a0904023a1701cdb4d7c5e7ca390cf95b1bb47d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 10 Jun 2025 12:53:52 +0200 Subject: [PATCH 109/250] updated sdl3.c3l location --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 5e0ca86..3e59dce 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* scripts/compile_shaders.sh - c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir ../sdl3.c3l --lib sdl3 + c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir lib --lib sdl3 From 177e52b0d0a888e2870c86439089590491683e18 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 10 Jun 2025 12:54:03 +0200 Subject: [PATCH 110/250] rounded quads baby! --- resources/shaders/source/rect.frag.glsl | 50 ++++++++++++++++++++++++- resources/shaders/source/rect.vert.glsl | 8 ++++ src/renderer.c3 | 15 +++++--- test_renderer.c3 | 2 +- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl index 3466a16..b724988 100644 --- a/resources/shaders/source/rect.frag.glsl +++ b/resources/shaders/source/rect.frag.glsl @@ -1,9 +1,55 @@ #version 450 -layout(location = 0) in vec4 col; +layout(location = 0) in vec4 color; +layout(location = 1) in vec2 local_position; +layout(location = 2) in vec2 global_position; +layout(location = 3) in float radius; + layout(location = 0) out vec4 fragColor; +// SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels +float sdf_rr(vec2 p, vec2 center, vec2 half_size, float radius) { + // Translate fragment position to rectangle's coordinate system + p -= center; + // Adjust for rounded corners: shrink the rectangle by the radius + vec2 q = abs(p) - half_size + radius; + // Combine distance components: + // - max(q, 0.0) handles regions outside the rounded corners + // - min(max(q.x, q.y), 0.0) handles regions inside the rectangle + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; +} + void main() { - fragColor = col; + // local_position are normalized coordinates in the rectangle, passed from the + // vertex shader + /* + * Window + * +-----------------------+ + * | (1,1) | + * | +----------x | + * | | | | + * | | | | + * | | Rect | | + * | | | | + * | | | | + * | x----------+ | + * | (-1,-1) | + * | | + * +-----------------------+ + */ + + vec2 dx = dFdx(local_position); + vec2 dy = dFdy(local_position); + // Conversion from normalized coordinates to pixels + vec2 norm_to_px = 1.0 / vec2(length(dx), length(dy)); + + vec2 centerpoint = global_position - local_position * norm_to_px; + // the half size of the rectangle is also norm_to_px + vec2 half_size = 1.0 * norm_to_px; + + float distance = sdf_rr(global_position, centerpoint, half_size, radius); + float alpha = 1.0 - smoothstep(0.0, 1.0, max(distance, 0.0)); + + fragColor = vec4(color.rgb, color.a * alpha); } \ No newline at end of file diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl index 0fab590..b52a60d 100644 --- a/resources/shaders/source/rect.vert.glsl +++ b/resources/shaders/source/rect.vert.glsl @@ -10,6 +10,9 @@ layout(location = 1) in ivec2 uv; layout(location = 2) in ivec4 color; layout(location = 0) out vec4 col; +layout(location = 1) out vec2 local_position; +layout(location = 2) out vec2 global_position; +layout(location = 3) out float radius; void main() { @@ -24,5 +27,10 @@ void main() pos.y = -(float(position.y)*2.0 / view.y - 1.0); gl_Position = vec4(pos+shift, 0.0, 1.0); + + local_position = vec2(sign(uv)); + global_position = gl_Position.xy; + radius = abs(float(uv.x)); + col = vec4(color) / 255.0; } diff --git a/src/renderer.c3 b/src/renderer.c3 index ebcfd54..a65864a 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -566,7 +566,7 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, } // Push a quad into the quad buffer, return true on success and false on failure -fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color) +fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) { if (self.quad_buffer.count >= MAX_QUAD_BATCH) { return false; @@ -600,11 +600,14 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color * |/ | * +-------------+ * v2 v3 - */ - quad.vertices.v1 = {.pos = {.x = x, .y = y}, .col.u = color}; - quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .col.u = color}; - quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .col.u = color}; - quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .col.u = color}; + */ + // the wanted radius is pushed into the uv coordinates, the vertex shader then extracts the absolute value + // and passes it to the fragment shader, then it uses the sign to give the fragment shader local coordinates + // into the quad. + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = -radius, .v = +radius}, .col.u = color}; + quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = -radius, .v = -radius}, .col.u = color}; + quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = +radius, .v = -radius}, .col.u = color}; + quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = +radius, .v = +radius}, .col.u = color}; // triangle 1 indices quad.indices.i1 = 0; // v1 quad.indices.i2 = 1; // v2 diff --git a/test_renderer.c3 b/test_renderer.c3 index e9c6302..d5fd95b 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -62,7 +62,7 @@ fn int main() } // rect 1 - ren.push_quad(100,100,100,100,0xff00ff00); + ren.push_quad(100,100,100,100,0xff00ff00, 20); // rect 2 ren.push_quad(0,0,20,20,0xff0000ff); // rect 3 From 2014c67bfd3885967173616277f18408c6f7d9bb Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 10 Jun 2025 23:02:44 +0200 Subject: [PATCH 111/250] tweak renderer API --- Makefile | 2 +- resources/shaders/source/font.frag.glsl | 21 + resources/shaders/source/msdf.frag.glsl | 35 ++ resources/shaders/source/rect.frag.glsl | 4 + resources/shaders/source/rect.vert.glsl | 8 +- resources/shaders/source/sprite.frag.glsl | 4 + resources/shaders/source/sprite.vert.glsl | 3 +- resources/tux_inv.qoi | Bin 0 -> 4045 bytes src/renderer.c3 | 462 +++++++++++++++------- test_renderer.c3 | 132 ++----- 10 files changed, 424 insertions(+), 247 deletions(-) create mode 100644 resources/shaders/source/font.frag.glsl create mode 100644 resources/shaders/source/msdf.frag.glsl create mode 100644 resources/tux_inv.qoi diff --git a/Makefile b/Makefile index 3e59dce..4f151b9 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,3 @@ test_renderer: test_renderer.c3 src/renderer.c3 resources/shaders/source/* scripts/compile_shaders.sh - c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir lib --lib sdl3 + c3c compile -g -O0 test_renderer.c3 src/renderer.c3 --libdir lib --lib sdl3 --lib ugui diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl new file mode 100644 index 0000000..4c5f3c8 --- /dev/null +++ b/resources/shaders/source/font.frag.glsl @@ -0,0 +1,21 @@ +#version 450 + +layout(set = 3, binding = 0) uniform Viewport { + ivec2 view; +}; +layout(set = 2, binding = 0) uniform sampler2D tx; + +layout(location = 0) in vec2 uv; +layout(location = 1) in vec4 color; + +layout(location = 0) out vec4 fragColor; + +void main() +{ + ivec2 ts = textureSize(tx, 0); + vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 real_uv = uv / fts; + + vec4 opacity = texture(tx, real_uv); + fragColor = vec4(color.rgb, color.a*opacity.r); +} \ No newline at end of file diff --git a/resources/shaders/source/msdf.frag.glsl b/resources/shaders/source/msdf.frag.glsl new file mode 100644 index 0000000..e4ef9e5 --- /dev/null +++ b/resources/shaders/source/msdf.frag.glsl @@ -0,0 +1,35 @@ +#version 450 + +layout(set = 3, binding = 0) uniform Viewport { + ivec2 view; +}; +layout(set = 2, binding = 0) uniform sampler2D tx; + +const float PX_RANGE = 4.0f; + +layout(location = 0) in vec2 uv; +layout(location = 1) in vec4 color; + +layout(location = 0) out vec4 fragColor; + +float screen_px_range(vec2 uv) { + vec2 unit_range = vec2(PX_RANGE)/vec2(textureSize(tx, 0)); + vec2 texel_size = vec2(1.0)/fwidth(uv); + return max(0.5*dot(unit_range, texel_size), 1.0); +} + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + ivec2 ts = textureSize(tx, 0); + vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 real_uv = uv / fts; + + vec3 msd = texture(tx, real_uv).rgb; + float sd = median(msd.r, msd.g, msd.b); + float distance = screen_px_range(real_uv)*(sd - 0.5); + float opacity = clamp(distance + 0.5, 0.0, 1.0); + fragColor = color * opacity; +} \ No newline at end of file diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl index b724988..f89cbae 100644 --- a/resources/shaders/source/rect.frag.glsl +++ b/resources/shaders/source/rect.frag.glsl @@ -1,5 +1,9 @@ #version 450 +layout(set = 3, binding = 0) uniform Viewport { + ivec2 view; +}; + layout(location = 0) in vec4 color; layout(location = 1) in vec2 local_position; layout(location = 2) in vec2 global_position; diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl index b52a60d..2c08c7c 100644 --- a/resources/shaders/source/rect.vert.glsl +++ b/resources/shaders/source/rect.vert.glsl @@ -2,7 +2,6 @@ layout(set = 1, binding = 0) uniform Viewport { ivec2 view; - ivec2 off; }; layout(location = 0) in ivec2 position; @@ -16,17 +15,12 @@ layout(location = 3) out float radius; void main() { - // per vertex shift - vec2 shift; - shift.x = float(off.x) / view.x; - shift.y = -(float(off.y) / view.y); - // vertex position vec2 pos; pos.x = float(position.x)*2.0 / view.x - 1.0; pos.y = -(float(position.y)*2.0 / view.y - 1.0); - gl_Position = vec4(pos+shift, 0.0, 1.0); + gl_Position = vec4(pos, 0.0, 1.0); local_position = vec2(sign(uv)); global_position = gl_Position.xy; diff --git a/resources/shaders/source/sprite.frag.glsl b/resources/shaders/source/sprite.frag.glsl index 142cbf5..789d546 100644 --- a/resources/shaders/source/sprite.frag.glsl +++ b/resources/shaders/source/sprite.frag.glsl @@ -1,5 +1,9 @@ #version 450 +layout(set = 3, binding = 0) uniform Viewport { + ivec2 view; +}; + layout(location = 0) in vec2 uv; layout(location = 0) out vec4 fragColor; diff --git a/resources/shaders/source/sprite.vert.glsl b/resources/shaders/source/sprite.vert.glsl index 4ecba3d..e0a476c 100644 --- a/resources/shaders/source/sprite.vert.glsl +++ b/resources/shaders/source/sprite.vert.glsl @@ -2,7 +2,6 @@ layout(set = 1, binding = 0) uniform Viewport { ivec2 view; - ivec2 not_needed; }; layout(location = 0) in ivec2 position; @@ -10,6 +9,7 @@ layout(location = 1) in ivec2 in_uv; layout(location = 2) in ivec4 color; layout(location = 0) out vec2 out_uv; +layout(location = 1) out vec4 out_color; void main() { @@ -19,4 +19,5 @@ void main() gl_Position = vec4(pos, 0.0, 1.0); out_uv = vec2(float(in_uv.x), float(in_uv.y)); + out_color = vec4(color) / 255.0; } diff --git a/resources/tux_inv.qoi b/resources/tux_inv.qoi new file mode 100644 index 0000000000000000000000000000000000000000..947fb28c06dd7b35c1d14ddd53ffba94cca43d11 GIT binary patch literal 4045 zcmXX}30M?I79LHqtDuNsHEJT*iWtFFjD~C)1w=(%O;%jN14X=0gCZ&_5_J%d>G4KH zZc%PgJl6HrIK#*-@k(Nhn~h>X373EjO!v%R`)b(!zUiu|djET`-dVk2{hCmfO65U6 zpZ8U&9~eC!GK)otWPoyqX69e{-%kE&;jbV0S~^n{WjZJBuBu~Zvm82v@3E3mw50f` zUh{vFSb9i%Uc;vrddX{5HAHF-5=)aMoIZW(vV^v_mI8_Ig{fX~ZZ@3wjaWJcXV0EJ zqCs)-J(x@#eAPtTvWKdgOIywtSXrWkn>TOZ&70TG*^)>bN;zlJP4$v1e?crCBO17( zezZVrQ7aZ7(k5!eBEJ8#>V=c2!<{>Kc5Bhl@JC-!kH?Q6VdKV) zd!!r*)zvS?iTb~B5$~yfa}sg&>J=@QKLpBXi-s7 zPO8!4)CK>uh@hZFxPJW_*>2UURqg~^E~c{ix)3o{rdHg6=e;sT)BcVW5$TO6B5Rb9g9<^GOR$8TNkh!H;siI zIdYgKX+>z_Z^J+0EgZcY(Qn*O_;Sv3xO(isisuvvG%zpm) zDMP=0{V_G<9QyzL9s2pS!Qt};^mcoayBj@6{09zy{Q-V~m*6^j3cS3$xYw;rl!;a* z@=NYshINOYW5|3X9Hzg6gIAjydbvJ^jmus1ba_C0>M~crb~KSp{RzLmGJ>QR@tB+2 zrO6|9t;)pkdAJcn78twtbc5{(J#1aB5%k$-S<5jatN}t`3r=1)k#s`uV3ZjICgDYF zQ-+a^@5N>J#D||%!)C-)l3haJ=jRS$=B9UW4K<$p7FGX|3C;6|kThIkHBF@ZnuIpE zE$iss6At6+Nb#zieeepY!{o>gj9h7)hnZ2op|QDDBnuWD*Px@LovUEx0Wg@HL9v@D zA~Grl;Yp1MjFB;WQ)kB2Q!bdei8zFI48<3#USQE0EoRRA3VC^X;(jgE>YbN0prqlv z)Wn7qD&%v9`Sa%^Xhjs39czWx1{q^lnKDKU&5gs{O}8*}mjBV?3DR9nR}@39*Wtv8 zc#%UJxJrGjrumgmt!dlBc&1Vejk_pe0kK_o>?uM|G^gZaI>QbIz)M)~F9^4@&OBJC> zE;WASN_?Pt#D_m|Uatv%=guv-y1MNrar(=WaBuLKOPWj4$33YI{ga#YxRjv1p|P4L zlAxsMKj;L{pFjHq0RaJ?f+0r|O6_h+7d4U=X=!O>4*ih3@jo{O_lJ>-W^ z6IoT&eMO|=cG6pTcwQRefwym)L@F^wMMc)!<`_B5>VpRy$;*sf&8n+kT3B7(8+K0P zAw2e^sv93452NuN%FD}8UtdoQ^gOMtGOJV^t9XixGUly_W5U*wU3Ttj|WUQv1&5RN3v_LtP|G0%_iLqV1|Gu9*Wt zly-igW)V-GJP}IHO2YTlnu^J+ckg~j#N^7m)}vH$+I=E>^yq;}L{wCiQ0#4`=Kk07 z!rjH1dzbx5Rmq3Ha7=6815{ky-Q9)aY{{9}@7lGC0Jm@7>?);S5G|1%6o6caff+E|i>+Y>1S6LZlRuR#G|K zdUnoR*~%JQWQI$n3Yyy#YBvvgrx!{JtT>+A+?UkWuSiK!x|Az<@xE=IO~T?XbxvoW zE-^FonU36JI9nw1258NBYLvyF>V*6F&}p(SbcPa;2gM?C$75=piPYpx=&dRzJt&aA z*kF3V`u-gZwv*XTeDn{X>K7TWU)NAgm>}Hq%$9^ojG7#UPriB!JFyvGE~Jv8)$Y-< z^vj*?%qN7|6EolF4mBGcN`=yH8cR#lWv#@xHD+|0l=S0Rv?y3Gs0R|GiNR;!ys!a% zrq&`*{Z=##!P_<&&H;ZgmxXVenTp>?49rj6)}MKA?bNb~n@j~vj5M8N6E=1}W8>G6 z!3NK3MX#yMQJ)`zotcUlKy_P<_ul&;;QLHzAbgkQ!#St{ef?@NG9qIhF-x?Zr@k;7t=}KX-1RK%6g2uGpIJ6eAXMZ`Ld*SbAo|&gAp3pH`D{ zE>NGng?$oGZ(%->EfFd`8Qb(eaHk%)fB$|Qy=275 zC5<1XretmL3SD4pu=h9{!TuKBa+!tRry|WJN1J|RAJ1)J=skt`m5;DwfBNkU_G1iZL2}W{Gv^-*Z_Ycb8##W z%s!5BOBH-P)I+HB=5J&EJT> zu584N9jz=Ru0zA-?(T?XZp$qH&z{PGc}p>6n*~W1YT)hdEtH&>5E&T>>e*zpSM@R$ zA8e-}Z$h6bHEGe&MVT6#+`ajca0zKFj3H<%o2PA)*{b6myR80Xb0s?P=undI;VhPX zVWDHFV_Ehdj9&*tq$+4>mtzgq%XDyX$UZ4?-}(KBh=|}Nk2g!vfQP@y2s&WIu!SU) z4x=BG6_^`W7y!4>Hh67q$LziBY-yaVu(c_sJQkJSF;t~v!G}$aY|=JLGPhNLQxGNF z9F}~0+u$xVaD&N;afh)I)s7#4H&+p3F2lW z=4j1qRgw%Aoo%|vG&v-ph^vs>{+1h6*IjLKhRlBxeeMmQbcb8|Z>*O-(MnzK1i!sxX|OjaAQ^thQtWtm)+!gk#uZpr1c4%FHk0#_-F_FnuQw zlcTVwuSi2`r^k6y*}%(qAb-u`pIlRV@M#d=S<9ibQyr>&E@N+@f(3^y z@Z8#gG3z`0vU1LxfWO9quvCS`7RcYRgaX!!nuEH4t}^P}4qv9uHfdPYDTP!|QaUKs z$U<}WSm3kOf^p#>D>1+PqwtLezDcyO^jpe1_OEB`FI3R!v7w)0y~A!|&uis}?6i)m zZ_HUURwP*vc-WF2bh;GF6U^9_t+2R%%XzGzfIjygu)BBJ`z-#d;>!-4mrt^oJaY>R zOCsU(4_J~fki^aDW|n+|ZlPDJOhI&YD0xM9%QT4_sZW9N!xJ-hXQKBXV9`<&?3V>0B`ho&-izD#aQ W+s+C~OxIaqnKDUL&OcG;>GnTC&06^Y literal 0 HcmV?d00001 diff --git a/src/renderer.c3 b/src/renderer.c3 index a65864a..cc0bab5 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -1,35 +1,57 @@ +module idlist{Type}; + +// extends the List type to search elements that have a type.id property +// TODO: check that type has an id + +import std::collections::list; + +alias IdList = List{Type}; + +macro Type* IdList.get_from_name(&self, String name) +{ + return self.get_from_id(name.hash()); +} + +macro Type* IdList.get_from_id(&self, id) +{ + foreach(&s: self) { + if (s.id == id) { + return s; + } + } + return null; +} + module sdlrenderer::ren; // 2D renderer for ugui, based on SDL3 using the new GPU API -// TODO: use unreachable() instead of the combo eprintfn();exit(1); since it does the same thing -// but also adds a helpful stack trace import std::io; import std::core::mem; import sdl3::sdl; -import libc; -import std::collections::list; +import idlist; +import ugui; struct Shader { sdl::GPUShader* frag; sdl::GPUShader* vert; - uint id; + ugui::Id id; } struct Pipeline { sdl::GPUGraphicsPipeline* pipeline; - uint id; + ugui::Id id; } struct Texture { sdl::GPUTexture* texture; sdl::GPUSampler* sampler; ushort width, height; - uint id; + ugui::Id id; } // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH -const int MAX_QUAD_BATCH = 128; +const int MAX_QUAD_BATCH = 256; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; @@ -38,17 +60,24 @@ struct QuadBuffer { int off; // the offset to draw from } -alias ShaderList = List{Shader}; -alias PipelineList = List{Pipeline}; -alias TextureList = List{Texture}; +alias ShaderList = IdList{Shader}; +alias PipelineList = IdList{Pipeline}; +alias TextureList = IdList{Texture}; struct Renderer { sdl::Window* win; sdl::GPUDevice* gpu; + sdl::GPURenderPass* render_pass; + sdl::GPUTexture* swapchain_texture; + sdl::GPUCommandBuffer* render_cmdbuf; + QuadBuffer quad_buffer; ShaderList shaders; PipelineList pipelines; TextureList textures; + + Id sprite_atlas_id; + Id font_atlas_id; } // how each vertex is represented in the gpu @@ -77,9 +106,13 @@ struct Quad @packed { } } +struct ViewsizeUniform @align(16) { + int w, h; +} + const int DEBUG = 1; -fn void Renderer.init(&self, ZString title) +fn void Renderer.init(&self, ZString title, uint width, uint height) { // set wayland hint automagically $if DEBUG == 0: @@ -96,13 +129,11 @@ $if DEBUG == 0: sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "wayland"); } $else - // in debug mode set the video driver to X11 because renderdoc/ + // in debug mode set the video driver to X11 because renderdoc // doesn't support debugging in wayland yet. sdl::set_hint(sdl::HINT_VIDEO_DRIVER, "x11"); -$endif - - sdl::set_hint(sdl::HINT_RENDER_GPU_DEBUG, "1"); +$endif // init subsystems if (!sdl::init(INIT_VIDEO)) { @@ -110,7 +141,7 @@ $endif } // create the window - self.win = sdl::create_window(title, 640, 480, WINDOW_RESIZABLE|WINDOW_VULKAN); + self.win = sdl::create_window(title, width, height, WINDOW_RESIZABLE|WINDOW_VULKAN); if (self.win == null) { unreachable("sdl error: %s", sdl::get_error()); } @@ -164,6 +195,19 @@ fn void Renderer.free(&self) sdl::quit(); } +fn void Renderer.resize_window(&self, uint width, uint height) +{ + sdl::set_window_size(self.win, width, height); +} + +fn void Renderer.get_window_size(&self, int* width, int* height) +{ + sdl::get_window_size_in_pixels(self.win, width, height); +} + + +// Both the vertex shader and fragment shader have an implicit uniform buffer at binding 0 that +// contains the viewport size. It is populated automatically at every begin_render() call fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code, char[] frag_code, uint textures, uint uniforms) { Shader s; @@ -183,7 +227,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code .format = GPU_SHADERFORMAT_SPIRV, .stage = GPU_SHADERSTAGE_VERTEX, .num_samplers = 0, - .num_uniform_buffers = uniforms, + .num_uniform_buffers = 1+uniforms, .num_storage_buffers = 0, .num_storage_textures = 0 }; @@ -204,7 +248,7 @@ fn void Renderer.load_spirv_shader_from_mem(&self, String name, char[] vert_code .format = GPU_SHADERFORMAT_SPIRV, .stage = GPU_SHADERSTAGE_FRAGMENT, .num_samplers = textures, - .num_uniform_buffers = 0, + .num_uniform_buffers = 1, .num_storage_buffers = 0, .num_storage_textures = 0 }; @@ -241,40 +285,6 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat self.load_spirv_shader_from_mem(name, vert_code, frag_code, textures, uniforms); } -fn Shader* ShaderList.get_from_name(&self, String name) -{ - uint id = name.hash(); - foreach(&s: self) { - if (s.id == id) { - return s; - } - } - return null; -} - -fn Pipeline* PipelineList.get_from_name(&self, String name) -{ - uint id = name.hash(); - foreach(&p: self) { - if (p.id == id) { - return p; - } - } - return null; -} - -fn Texture* TextureList.get_from_name(&self, String name) -{ - uint id = name.hash(); - foreach(&t: self) { - if (t.id == id) { - return t; - } - } - return null; -} - - // this describes what we want to draw, since for drawing different things we have to change // the GPUPrimitiveType and GPURasterizerState for the pipeline. enum PipelineType : (GPUPrimitiveType primitive_type, GPURasterizerState raster_state) { @@ -380,12 +390,29 @@ enum TextureType : (GPUTextureFormat format) { JUST_ALPHA = GPU_TEXTUREFORMAT_R8_UNORM } +macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixels, uint width, uint height) +{ + $switch $typeof(name_or_id): + $case usz: return self.new_texture_by_id(id, type, pixels, width, height); + $case String: return self.new_texture_by_id(name_or_id.hash(), type, pixels, width, height); + $default: unreachable("texture must have a name (String) or an id (usz)"); + $endswitch +} + +macro void Renderer.update_texture(&self, name_or_id, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) +{ + $switch $typeof(name_or_id): + $case usz: return self.update_texture_by_id(name_or_id, pixels, width, height, x, y); + $case String: return self.update_texture_by_id(name_or_id.hash(), pixels, width, height, x, y); + $default: unreachable("texture must have a name (String) or an id (usz)"); + $endswitch +} + + // create a new gpu texture from a pixel buffer, the format has to be specified // the new texture s given an id and pushed into a texture list -fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels, uint width, uint height) -{ - uint id = name.hash(); - +fn void Renderer.new_texture_by_id(&self, Id id, TextureType type, char[] pixels, uint width, uint height) +{ // the texture description GPUTextureCreateInfo tci = { .type = GPU_TEXTURETYPE_2D, @@ -401,7 +428,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels GPUTexture* texture = sdl::create_gpu_texture(self.gpu, &tci); if (texture == null) { - unreachable("failed to create texture (name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + unreachable("failed to create texture (id: %s, type: %s): %s", id, type.nameof, sdl::get_error()); } // the sampler description, how the texture should be sampled @@ -417,7 +444,7 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels GPUSampler* sampler = sdl::create_gpu_sampler(self.gpu, &sci); if (sampler == null) { - unreachable("failed to create sampler (texture name: %s, type: %s): %s", name, type.nameof, sdl::get_error()); + unreachable("failed to create sampler (texture id: %s, type: %s): %s", id, type.nameof, sdl::get_error()); } Texture t = { @@ -428,20 +455,20 @@ fn void Renderer.new_texture(&self, String name, TextureType type, char[] pixels self.textures.push(t); // upload the texture data - self.update_texture(name, pixels, width, height); + self.update_texture_by_id(id, pixels, width, height, 0, 0); } -fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) +fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, uint height, uint x, uint y) { - Texture* t = self.textures.get_from_name(name); + Texture* t = self.textures.get_from_id(id); if (t == null || t.texture == null) { - unreachable("failed updating texture: no texture named %s", name); + unreachable("failed updating texture: no texture with id %s", id); } GPUTexture* texture = t.texture; // FIXME: do a better job at validating the copy if (x > t.width || y > t.height) { - unreachable("failed updating texture: attempting to copy outside of the texture region", name); + unreachable("failed updating texture: attempting to copy outside of the texture region"); } // upload image data @@ -484,28 +511,9 @@ fn void Renderer.update_texture(&self, String name, char[] pixels, uint width, u } -fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) { - if (self.quad_buffer.count >= MAX_QUAD_BATCH) { - return false; - } - - // upload the quad data to the gpu - if (self.quad_buffer.initialized == false) { - unreachable("quad buffer not initialized"); - } - - GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} - ); - if (buf == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); - } - - Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); - if (quad == null) { - unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); - } - +fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) +{ + Quad quad; /* v1 v4 * +-------------+ * | _/| @@ -531,64 +539,13 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, quad.indices.i5 = 2; // v3 quad.indices.i6 = 3; // v4 - sdl::unmap_gpu_transfer_buffer(self.gpu, buf); - - GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); - if (cmd == null) { - unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); - } - GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); - - // upload vertices - QuadBuffer* qb = &self.quad_buffer; - sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, - &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, - false - ); - // upload indices - sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, - &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, - false - ); - - sdl::end_gpu_copy_pass(cpy); - if (!sdl::submit_gpu_command_buffer(cmd)) { - unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); - } - sdl::release_gpu_transfer_buffer(self.gpu, buf); - //sdl::wait_for_gpu_idle(self.gpu); - - qb.count++; - - return true; + return self.upload_quad(&quad); } // Push a quad into the quad buffer, return true on success and false on failure fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) { - if (self.quad_buffer.count >= MAX_QUAD_BATCH) { - return false; - } - - // upload the quad data to the gpu - if (self.quad_buffer.initialized == false) { - unreachable("quad buffer not initialized"); - } - - GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} - ); - if (buf == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); - } - - Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); - if (quad == null) { - unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); - } - + Quad quad; /* v1 v4 * +-------------+ * | _/| @@ -617,6 +574,34 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color quad.indices.i5 = 2; // v3 quad.indices.i6 = 3; // v4 + return self.upload_quad(&quad); +} + +fn bool Renderer.upload_quad(&self, Quad* source_quad) +{ + if (self.quad_buffer.count >= MAX_QUAD_BATCH || source_quad == null) { + return false; + } + + // upload the quad data to the gpu + if (self.quad_buffer.initialized == false) { + unreachable("quad buffer not initialized"); + } + + GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (buf == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + if (quad == null) { + unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); + } + + *quad = *source_quad; + sdl::unmap_gpu_transfer_buffer(self.gpu, buf); GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); @@ -653,18 +638,18 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color // draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense // to draw them one a the time -fn void Renderer.draw_quads(&self, GPURenderPass* pass) +fn void Renderer.draw_quads(&self) { QuadBuffer* qb = &self.quad_buffer; if (qb.off == qb.count) return; - sdl::bind_gpu_vertex_buffers(pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); - sdl::bind_gpu_index_buffer(pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); + sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); + sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); // we need instancing to not do this for (int i = 0; i < qb.count - qb.off; i++) { - sdl::draw_gpu_indexed_primitives(pass, 6, 1, i*6, i*4, 0); + sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, i*6, i*4, 0); } qb.off = qb.count; @@ -677,9 +662,107 @@ fn void Renderer.reset_quads(&self) } -// TODO: fn Renderer.draw_sprite, same as draw_quad but also bind the texture -// TODO: fn Renderer.begin_render -// TODO: fn Renderer.end_render +fn void Renderer.begin_render(&self, bool clear_screen) +{ + self.render_cmdbuf = sdl::acquire_gpu_command_buffer(self.gpu); + sdl::wait_and_acquire_gpu_swapchain_texture(self.render_cmdbuf, self.win, &self.swapchain_texture, null, null); + + // push the window size as a uniform + // TODO: maybe make this configurable and/or add more things + ViewsizeUniform v; + self.get_window_size(&v.w, &v.h); + sdl::push_gpu_vertex_uniform_data(self.render_cmdbuf, 0, &v, ViewsizeUniform.sizeof); + sdl::push_gpu_fragment_uniform_data(self.render_cmdbuf, 0, &v, ViewsizeUniform.sizeof); + + if (clear_screen) { + GPURenderPass* pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, + &&(GPUColorTargetInfo){ + .texture = self.swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 1.0, .g = 0.0, .b = 1.0, .a = 1.0}, + .load_op = GPU_LOADOP_CLEAR, // clear the screen at the start of the render pass + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + if (pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + sdl::end_gpu_render_pass(pass); + } +} + +fn void Renderer.end_render(&self) +{ + sdl::submit_gpu_command_buffer(self.render_cmdbuf); + self.reset_quads(); +} + +fn void Renderer.start_render_pass(&self, String pipeline_name) +{ + self.render_pass = sdl::begin_gpu_render_pass(self.render_cmdbuf, + &&(GPUColorTargetInfo){ + .texture = self.swapchain_texture, + .mip_level = 0, + .layer_or_depth_plane = 0, + .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, + .load_op = GPU_LOADOP_DONT_CARE, + .store_op = GPU_STOREOP_STORE, + .resolve_texture = null, + .resolve_mip_level = 0, + .resolve_layer = 0, + .cycle = false, + .cycle_resolve_texture = false + }, + 1, + null // huh + ); + + if (self.render_pass == null) { + unreachable("render pass creation went wrong: %s", sdl::get_error()); + } + + sdl::GPUGraphicsPipeline* p; + p = self.pipelines.get_from_name(pipeline_name).pipeline; + if (p == null) { + unreachable("no pipeline"); + } + + sdl::bind_gpu_graphics_pipeline(self.render_pass, p); +} + +fn void Renderer.end_render_pass(&self) +{ + sdl::end_gpu_render_pass(self.render_pass); +} + +fn void Renderer.bind_texture(&self, String texture_name) +{ + ren::Texture* tx = self.textures.get_from_name(texture_name); + sdl::bind_gpu_fragment_samplers(self.render_pass, 0, + (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 + ); +} + +fn void Renderer.set_scissor(&self, uint x, uint y, uint w, uint h) +{ + sdl::set_gpu_scissor(self.render_pass, &&(sdl::Rect){x,y,w,h}); +} + +fn void Renderer.reset_scissor(&self) +{ + int w, h; + sdl::get_window_size(self.win, &w, &h); + self.set_scissor(0, 0, w, h); +} /// === NOTES === /* 1. The uniform data is per-render pass. So you can do: @@ -699,4 +782,89 @@ fn void Renderer.reset_quads(&self) * 2. The GPU buffers are read per-command-buffer and not per * render pass. So I cannot override an element in the buffer * before submitting the command buffer. - */ \ No newline at end of file + */ + /// === END NOTES === + + +fn void Renderer.render_ugui(&self, CmdQueue* queue) +{ + io::printn("---------------------------------------"); + Cmd* last_command; + for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) { + if (last_command == null || last_command.type != cmd.type) { + self.end_command(last_command); + self.begin_command(cmd); + } + + switch (cmd.type) { + case CMD_RECT: + CmdRect r = cmd.rect; + self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); + io::printn(r); + case CMD_SPRITE: + // TODO: support hue in sprite + CmdSprite s = cmd.sprite; + self.push_sprite(s.rect.x, s.rect.y, s.rect.w, s.rect.h, s.texture_rect.x, s.texture_rect.y); + case CMD_UPDATE_ATLAS: + // TODO: verify the correct type + CmdUpdateAtlas u = cmd.update_atlas; + char[] pixels = u.raw_buffer[..u.width*u.height*u.bpp]; + self.update_texture(u.id, pixels, u.width, u.height); + case CMD_SCISSOR: break; // FIXME: ugui sends a scissor event before any rect event, this cannot be done and needs different handling + ugui::Rect s = cmd.scissor.rect; + self.set_scissor(s.x, s.y, s.w, s.h); + default: unreachable("unknown command: %s", cmd.type); + } + + last_command = cmd; + } + self.end_command(last_command); +} + +fn void Renderer.begin_command(&self, Cmd* cmd) +{ + if (cmd == null) return; + + switch (cmd.type) { + case CMD_RECT: + self.start_render_pass("UGUI_PIPELINE_RECT"); + case CMD_SPRITE: + // TODO: support multiple sprite and font atlases + CmdSprite s = cmd.sprite; + String pipeline; + String texture; + if (s.texture_id == self.sprite_atlas_id) { + switch (s.type) { + case SPRITE_NORMAL: pipeline = "UGUI_PIPELINE_SPRITE"; + case SPRITE_SDF: pipeline = "UGUI_PIPELINE_SPRITE_SDF"; + case SPRITE_MSDF: pipeline = "UGUI_PIPELINE_SPRITE_MSDF"; + case SPRITE_ANIMATED: unreachable("animated sprtes are unsupported for now"); + default: unreachable("unknown sprite type %s", s.type); + } + texture = "icons"; + } else if (s.texture_id == self.font_atlas_id) { + pipeline = "UGUI_PIPELINE_FONT"; + texture = "font1"; + } + self.start_render_pass(pipeline); + self.bind_texture(texture); + case CMD_UPDATE_ATLAS: break; + case CMD_SCISSOR: break; + default: unreachable("unknown command: %s", cmd.type); + } +} + +fn void Renderer.end_command(&self, Cmd* cmd) +{ + if (cmd == null) return; + + switch (cmd.type) { + case CMD_RECT: nextcase; + case CMD_SPRITE: + self.draw_quads(); + self.end_render_pass(); + case CMD_UPDATE_ATLAS: break; + case CMD_SCISSOR: break; + default: unreachable("unknown command: %s", cmd.type); + } +} diff --git a/test_renderer.c3 b/test_renderer.c3 index d5fd95b..38ead4c 100644 --- a/test_renderer.c3 +++ b/test_renderer.c3 @@ -5,18 +5,23 @@ import sdl3::sdl; import std::compression::qoi; import std::core::mem::allocator; -struct Viewsize @align(16) { - int w, h; - int ox, oy; -} +char[*] shader_rect_vert = $embed("resources/shaders/compiled/rect.vert.spv"); +char[*] shader_rect_frag = $embed("resources/shaders/compiled/rect.frag.spv"); + +char[*] shader_sprite_vert = $embed("resources/shaders/compiled/sprite.vert.spv"); +char[*] shader_sprite_frag = $embed("resources/shaders/compiled/sprite.frag.spv"); + +const uint WINDOW_WIDTH = 640; +const uint WINDOW_HEIGHT = 480; + fn int main() { ren::Renderer ren; - ren.init("test window"); + ren.init("test window", WINDOW_WIDTH, WINDOW_HEIGHT); // TODO: these could be the same function - ren.load_spirv_shader_from_file("rect shader", "resources/shaders/compiled/rect.vert.spv", "resources/shaders/compiled/rect.frag.spv", 0, 1); + ren.load_spirv_shader_from_mem("rect shader", &shader_rect_vert, &shader_rect_frag, 0, 0); ren.create_pipeline("rect shader", RECT); // load the tux qoi image @@ -25,41 +30,30 @@ fn int main() // and put it in a texture ren.new_texture("tux", FULL_COLOR, img_pixels, img_desc.width, img_desc.height); // create a new pipeline to use the texture - ren.load_spirv_shader_from_file("sprite shader", "resources/shaders/compiled/sprite.vert.spv", "resources/shaders/compiled/sprite.frag.spv", 1, 1); + ren.load_spirv_shader_from_mem("sprite shader", &shader_sprite_vert, &shader_sprite_frag, 1, 0); ren.create_pipeline("sprite shader", SPRITE); - for (int i = 0; i < 20; i++) { - GPUCommandBuffer* cmdbuf = sdl::acquire_gpu_command_buffer(ren.gpu); - GPUTexture* swapchain_texture; - sdl::wait_and_acquire_gpu_swapchain_texture(cmdbuf, ren.win, &swapchain_texture, null, null); - GPURenderPass* pass; - GPUGraphicsPipeline* p; - Viewsize v = {.w = 640, .h = 480}; + sdl::Event e; + bool quit = false; + for (usz i = 0; !quit; i++) { + + if (sdl::poll_event(&e)) { + if (e.type == EVENT_QUIT) { + quit = true; + } + } + + if (i == 300) { + io::printn("ciao!"); + img_pixels = qoi::read(allocator::temp(), "resources/tux_inv.qoi", &img_desc)!!; + ren.update_texture("tux", img_pixels, img_desc.width, img_desc.height); + } + + ren.begin_render(true); // Colored Rectangles Render Pass - // FIXME: if doing damage tracking DO NOT clear the screen - pass = sdl::begin_gpu_render_pass(cmdbuf, - &&(GPUColorTargetInfo){ - .texture = swapchain_texture, - .mip_level = 0, - .layer_or_depth_plane = 0, - .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, - .load_op = GPU_LOADOP_CLEAR, // clear the screen at the start of the render pass - .store_op = GPU_STOREOP_STORE, - .resolve_texture = null, - .resolve_mip_level = 0, - .resolve_layer = 0, - .cycle = false, - .cycle_resolve_texture = false - }, - 1, - null // huh - ); - - if (pass == null) { - unreachable("render pass creation went wrong: %s", sdl::get_error()); - } + ren.start_render_pass("rect shader"); // rect 1 ren.push_quad(100,100,100,100,0xff00ff00, 20); @@ -68,75 +62,31 @@ fn int main() // rect 3 ren.push_quad(200,300,50,50,0xffff0000); - p = ren.pipelines.get_from_name("rect shader").pipeline; - if (p == null) { - unreachable("no pipeline"); - } + //ren.set_scissor(0,50,200,300); - sdl::bind_gpu_graphics_pipeline(pass, p); + ren.draw_quads(); - v.ox = 10*i; - v.oy = 10*i; - sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); - - ren.draw_quads(pass); - - sdl::end_gpu_render_pass(pass); + ren.end_render_pass(); // End Rectangle Render Pass // Textured Rectangles Render Pass - pass = sdl::begin_gpu_render_pass(cmdbuf, - &&(GPUColorTargetInfo){ - .texture = swapchain_texture, - .mip_level = 0, - .layer_or_depth_plane = 0, - .clear_color = {.r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0}, - .load_op = GPU_LOADOP_DONT_CARE, // clear the screen at the start of the render pass - .store_op = GPU_STOREOP_STORE, - .resolve_texture = null, - .resolve_mip_level = 0, - .resolve_layer = 0, - .cycle = false, - .cycle_resolve_texture = false - }, - 1, - null // huh - ); - - if (pass == null) { - unreachable("render pass creation went wrong: %s", sdl::get_error()); - } - - p = ren.pipelines.get_from_name("sprite shader").pipeline; - if (p == null) { - unreachable("no pipeline"); - } - - sdl::bind_gpu_graphics_pipeline(pass, p); - // in this case it is not an offset but the texture size in pixels - v.ox = 54; - v.oy = 64; - sdl::push_gpu_vertex_uniform_data(cmdbuf, 1, &v, Viewsize.sizeof); - + ren.start_render_pass("sprite shader"); + // bind the pipeline's sampler - ren::Texture* tx = ren.textures.get_from_name("tux"); - sdl::bind_gpu_fragment_samplers(pass, 0, - (GPUTextureSamplerBinding[]){{.texture = tx.texture, .sampler = tx.sampler}}, 1 - ); + ren.bind_texture("tux"); // tux ren.push_sprite(300, 0, 54, 64, 0, 0); - ren.draw_quads(pass); + ren.reset_scissor(); - sdl::end_gpu_render_pass(pass); + ren.draw_quads(); + + ren.end_render_pass(); // End Textured Rectangle Render Pass - sdl::submit_gpu_command_buffer(cmdbuf); - - ren.reset_quads(); - thread::sleep_ms(250); + ren.end_render(); } ren.free(); From c4a3dd3a26c583250c96e3acd2e6ce900bd15c5c Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Jun 2025 18:56:57 +0200 Subject: [PATCH 112/250] tweak ugui api, add some usefult functions --- lib/ugui.c3l/src/ugui_core.c3 | 2 +- lib/ugui.c3l/src/ugui_font.c3 | 10 ++++++++++ lib/ugui.c3l/src/ugui_shapes.c3 | 8 +++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index a1c7e2b..efa804c 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -62,7 +62,7 @@ alias ElemCache = cache::Cache{Id, Elem, MAX_ELEMENTS}; alias CmdQueue = fifo::Fifo{Cmd}; -faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE; +faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE, WRONG_ID; const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; diff --git a/lib/ugui.c3l/src/ugui_font.c3 b/lib/ugui.c3l/src/ugui_font.c3 index 5afe033..23700c6 100644 --- a/lib/ugui.c3l/src/ugui_font.c3 +++ b/lib/ugui.c3l/src/ugui_font.c3 @@ -224,3 +224,13 @@ fn Id Ctx.get_font_id(&ctx, String label) { return (Id)label.hash(); } + +fn Atlas*? Ctx.get_font_atlas(&ctx, String name) +{ + // TODO: use the font name, for now there is only one font + if (name.hash() != ctx.font.id) { + return WRONG_ID?; + } + + return &ctx.font.atlas; +} diff --git a/lib/ugui.c3l/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 index ebef525..1bfb00a 100644 --- a/lib/ugui.c3l/src/ugui_shapes.c3 +++ b/lib/ugui.c3l/src/ugui_shapes.c3 @@ -193,7 +193,7 @@ struct Color{ char r, g, b, a; } -macro Color uint.to_rgba(uint u) +macro Color uint.to_rgba(u) { return { .r = (char)((u >> 24) & 0xff), @@ -202,3 +202,9 @@ macro Color uint.to_rgba(uint u) .a = (char)((u >> 0) & 0xff) }; } + +macro uint Color.to_uint(c) +{ + uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24); + return u; +} From 458c45d2b9bcc5d50d5da50b15826ed4ecd92d5b Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Jun 2025 19:49:43 +0200 Subject: [PATCH 113/250] using the new renderer --- lib/ugui.c3l/src/ugui_core.c3 | 16 +- project.json | 2 +- src/main.c3 | 311 +++++++++++++--------------------- src/renderer.c3 | 45 +++-- 4 files changed, 142 insertions(+), 232 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index efa804c..7c0f552 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -206,7 +206,7 @@ fn void? Ctx.init(&ctx) ctx.style.margin = {2, 2, 2, 2}; ctx.style.border = {2, 2, 2, 2}; ctx.style.padding = {1, 1, 1, 1}; - ctx.style.radius = 5; + ctx.style.radius = 12; ctx.style.bgcolor = 0x282828ffu.to_rgba(); ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba(); ctx.style.brcolor = 0xd79921ffu.to_rgba(); @@ -303,18 +303,10 @@ $if DEBUG == 1: .rect.color = 0xff00ffffu.to_rgba() }; ctx.cmd_queue.enqueue(&cmd)!; - -// dump the command buffer -// io::printn("Command Buffer Dump:"); -// foreach(idx, c: ctx.cmd_queue) { -// io::printfn("\t [%d] = {%s}", idx, c); -// } - ctx.cmd_queue.sort()!; -// io::printn("Sorted Command Buffer Dump:"); -// foreach(idx, c: ctx.cmd_queue) { -// io::printfn("\t [%d] = {%s}", idx, c); -// } $endif + + // sort the command buffer by the z-index + ctx.cmd_queue.sort()!; } <* diff --git a/project.json b/project.json index c2216f9..5f672d7 100644 --- a/project.json +++ b/project.json @@ -2,7 +2,7 @@ "langrev": "1", "warnings": ["no-unused"], "dependency-search-paths": ["lib", "lib/vendor/libraries"], - "dependencies": ["raylib55", "sdl3", "ugui"], + "dependencies": ["sdl3", "ugui"], "features": [], "authors": ["Alessandro Mauri "], "version": "0.1.0", diff --git a/src/main.c3 b/src/main.c3 index e0c1737..a2a5546 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -2,10 +2,11 @@ import std::io; import vtree; import cache; import ugui; -import raylib5::rl; import std::time; import std::collections::ringbuffer; import std::core::string; +import sdlrenderer::ren; +import sdl3::sdl; alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]}; @@ -44,84 +45,14 @@ fn TimeStats Times.get_stats(×) } -const ZString MSDF_FS = ` -#version 330 +const char[*] MSDF_FS_PATH = "resources/shaders/compiled/msdf.frag.spv"; +const char[*] SPRITE_FS_PATH = "resources/shaders/compiled/sprite.frag.spv"; +const char[*] FONT_FS_PATH = "resources/shaders/compiled/font.frag.spv"; +const char[*] RECT_FS_PATH = "resources/shaders/compiled/rect.frag.spv"; -in vec2 fragTexCoord; -out vec4 fragColor; -uniform sampler2D texture0; -uniform vec4 colDiffuse; +const char[*] SPRITE_VS_PATH = "resources/shaders/compiled/sprite.vert.spv"; +const char[*] RECT_VS_PATH = "resources/shaders/compiled/rect.vert.spv"; -const float pxRange = 4.0f; - -float screenPxRange() { - vec2 unitRange = vec2(pxRange)/vec2(textureSize(texture0, 0)); - vec2 screenTexSize = vec2(1.0)/fwidth(fragTexCoord); - return max(0.5*dot(unitRange, screenTexSize), 1.0); -} - -float median(float r, float g, float b) { - return max(min(r, g), min(max(r, g), b)); -} - -void main() { - vec3 msd = texture(texture0, fragTexCoord).rgb; - float sd = median(msd.r, msd.g, msd.b); - float screenPxDistance = screenPxRange()*(sd - 0.5); - float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); - fragColor = colDiffuse * opacity; -} -`; - -const ZString FONT_FS = ` -#version 330 - -// Input vertex attributes (from vertex shader) -in vec2 fragTexCoord; -in vec4 fragColor; - -// Input uniform values -uniform sampler2D texture0; -uniform vec4 colDiffuse; - -// Output fragment color -out vec4 finalColor; - -void main() -{ - vec4 alpha = texture(texture0, fragTexCoord); - finalColor = colDiffuse*fragColor; - finalColor.a *= alpha.r; -} -`; - -// SDF based rect rendering -const ZString RECT_FS = ` -#version 330 - -in vec2 fragTexCoord; -in vec4 fragColor; -out vec4 finalColor; - -uniform float radius; -uniform float border; -uniform vec2 size; - -void main() -{ - -} -`; - -macro rl::Color ugui::Color.conv(color) -{ - return {.r = color.r, .g = color.g, .b = color.b, .a = color.a}; -} - -macro rl::Rectangle Rect.conv(rect) -{ - return {.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; -} fn int main(String[] args) { @@ -129,18 +60,57 @@ fn int main(String[] args) ui.init()!!; defer ui.free(); - ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; - ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!; - ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; - ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; + ren::Renderer ren; + ren.init("Ugui Test", 640, 480); + defer ren.free(); + ui.input_window_size(640, 480)!!; + + // + // FONT LOADING + // + { + // import font in the ui context + ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; + + // create the rendering pipeline + ren.font_atlas_id = ui.get_font_id("font1"); + ren.load_spirv_shader_from_file("UGUI_PIPELINE_FONT", SPRITE_VS_PATH, FONT_FS_PATH, 1, 0); + ren.create_pipeline("UGUI_PIPELINE_FONT", SPRITE); + + // send the atlas to the gpu + Atlas* font_atlas = ui.get_font_atlas("font1")!!; + ren.new_texture("font1", JUST_ALPHA, font_atlas.buffer, font_atlas.width, font_atlas.height); + } + + // + // ICON LOADING + // + { + // create the atlas and upload some icons + ui.sprite_atlas_create("icons", AtlasType.ATLAS_R8G8B8A8, 512, 512)!!; + ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; + ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; + + // create the rendering pipelines + ren.sprite_atlas_id = ui.get_sprite_atlas_id("icons"); + // normal sprite pipeline + ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE", SPRITE_VS_PATH, SPRITE_FS_PATH, 1, 0); + ren.create_pipeline("UGUI_PIPELINE_SPRITE", SPRITE); + // msdf sprite pipeline + ren.load_spirv_shader_from_file("UGUI_PIPELINE_SPRITE_MSDF", SPRITE_VS_PATH, MSDF_FS_PATH, 1, 0); + ren.create_pipeline("UGUI_PIPELINE_SPRITE_MSDF", SPRITE); + + // upload the atlas to the gpu + Atlas atlas = ui.sprite_atlas.atlas; + ren.new_texture("icons", FULL_COLOR, atlas.buffer, atlas.width, atlas.height); + } + + // + // RECT PIPELINE + // + ren.load_spirv_shader_from_file("UGUI_PIPELINE_RECT", RECT_VS_PATH, RECT_FS_PATH, 0, 0); + ren.create_pipeline("UGUI_PIPELINE_RECT", RECT); - short width = 800; - short height = 450; - rl::setConfigFlags(rl::FLAG_WINDOW_RESIZABLE); - rl::initWindow(width, height, "Ugui Test"); - ui.input_window_size(width, height)!!; - rl::setTargetFPS(60); - rl::enableEventWaiting(); isz frame; bool toggle = true; @@ -148,25 +118,49 @@ fn int main(String[] args) Times ui_times; Times draw_times; - // font stuff - rl::Shader font_shader = rl::loadShaderFromMemory(null, FONT_FS); - rl::Image font_atlas; - rl::Texture2D font_texture; - ugui::Id font_id = ui.get_font_id("font1"); - // sprite stuff - rl::Shader msdf_shader = rl::loadShaderFromMemory(null, MSDF_FS); - rl::Image sprite_atlas; - rl::Texture2D sprite_texture; - ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); - - // Main loop - while (!rl::windowShouldClose()) { + // + // MAIN LOOP + // + sdl::Event e; + bool quit = false; + while (!quit) { clock.mark(); - + + // FIXME: modkeys input is broken ugui::ModKeys mod; - mod.rctrl = rl::isKeyDown(rl::KEY_RIGHT_CONTROL); - mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL); + while (sdl::poll_event(&e)) { + switch (e.type) { + case EVENT_QUIT: + quit = true; + case EVENT_KEY_DOWN: + mod.rctrl = !!(e.key.key == K_RCTRL); + mod.lctrl = !!(e.key.key == K_LCTRL); + case EVENT_WINDOW_RESIZED: + ui.input_window_size((short)e.window.data1, (short)e.window.data2)!!; + case EVENT_WINDOW_FOCUS_GAINED: + ui.input_changefocus(true); + case EVENT_WINDOW_FOCUS_LOST: + ui.input_changefocus(false); + case EVENT_MOUSE_MOTION: + ui.input_mouse_abs((short)e.motion.x, (short)e.motion.y); + case EVENT_MOUSE_WHEEL: + ui.input_mouse_wheel((short)e.wheel.integer_x, (short)e.wheel.integer_y); + case EVENT_MOUSE_BUTTON_DOWN: nextcase; + case EVENT_MOUSE_BUTTON_UP: + sdl::MouseButtonFlags mb = sdl::get_mouse_state(null, null); + ugui::MouseButtons b = { + .btn_left = !!(mb & BUTTON_LMASK), + .btn_right = !!(mb & BUTTON_RMASK), + .btn_middle = !!(mb & BUTTON_MMASK), + .btn_4 = !!(mb & BUTTON_X1MASK), + .btn_5 = !!(mb & BUTTON_X2MASK), + }; + ui.input_mouse_button(b); + default: + io::eprintfn("unhandled event: %s", e.type); + } + } ui.input_mod_keys(mod); /* @@ -177,33 +171,20 @@ fn int main(String[] args) } */ - - /* Start Input Handling */ - if (rl::isWindowResized()) { - width = (short)rl::getScreenWidth(); - height = (short)rl::getScreenHeight(); - ui.input_window_size(width, height)!!; - } - - ui.input_changefocus(rl::isWindowFocused()); - - rl::Vector2 mpos = rl::getMousePosition(); - ui.input_mouse_abs((short)mpos.x, (short)mpos.y); - rl::Vector2 mwheel = rl::getMouseWheelMoveV(); - ui.input_mouse_wheel((short)mwheel.x, (short)mwheel.y); - - ugui::MouseButtons buttons = { - .btn_left = rl::isMouseButtonDown(rl::MouseButton.LEFT), - .btn_right = rl::isMouseButtonDown(rl::MouseButton.RIGHT), - .btn_middle = rl::isMouseButtonDown(rl::MouseButton.MIDDLE), - }; - ui.input_mouse_button(buttons); /* End Input Handling */ /* Start UI Handling */ ui.frame_begin()!!; - if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; + if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; + +/* + ui.div_begin("main", ugui::DIV_FILL)!!; + ui.button("button0", {0,0,30,30})!!; + ui.draw_sprite("sprite1", "tux")!!; + ui.text_unbounded("text1", "Ciao")!!; + ui.div_end()!!; +*/ ui.div_begin("main", {.w=-100})!!; { @@ -292,86 +273,24 @@ fn int main(String[] args) }; ui.div_end()!!; + ui.frame_end()!!; /* End UI Handling */ ui_times.push(clock.mark()); //ui_times.print_stats(); - /* Start UI Drawing */ - rl::beginDrawing(); - // ClearBackground(BLACK); - for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) { - switch (cmd.type) { - case CmdType.CMD_RECT: - Rect r = cmd.rect.rect; - float rad = cmd.rect.radius; - // for some weird-ass reason the straight forward inverse formula does not work - float roundness = r.w > r.h ? (2.1f*rad)/(float)r.h : (2.1f*rad)/(float)r.w; - rl::drawRectangleRounded(cmd.rect.rect.conv(), roundness, 0, cmd.rect.color.conv()); - case CmdType.CMD_UPDATE_ATLAS: - if (cmd.update_atlas.id == font_id) { - //rl::unload_image(font_atlas); - font_atlas.data = cmd.update_atlas.raw_buffer; - font_atlas.width = cmd.update_atlas.width; - font_atlas.height = cmd.update_atlas.height; - font_atlas.mipmaps = 1; - font_atlas.format = rl::PixelFormat.UNCOMPRESSED_GRAYSCALE; - if (rl::isTextureValid(font_texture)) { - rl::unloadTexture(font_texture); - } - font_texture = rl::loadTextureFromImage(font_atlas); - } else if (cmd.update_atlas.id == sprite_id) { - sprite_atlas.data = cmd.update_atlas.raw_buffer; - sprite_atlas.width = cmd.update_atlas.width; - sprite_atlas.height = cmd.update_atlas.height; - sprite_atlas.mipmaps = 1; - sprite_atlas.format = rl::PixelFormat.UNCOMPRESSED_R8G8B8A8; - if (rl::isTextureValid(sprite_texture)) { - rl::unloadTexture(sprite_texture); - } - sprite_texture = rl::loadTextureFromImage(sprite_atlas); - } - case CmdType.CMD_SPRITE: - if (cmd.sprite.texture_id == font_id) { - rl::Vector2 position = { - .x = cmd.sprite.rect.x, - .y = cmd.sprite.rect.y, - }; - rl::beginShaderMode(font_shader); - rl::drawTextureRec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); - rl::endShaderMode(); - } else if (cmd.sprite.texture_id == sprite_id) { - // FIXME: THIS CODE IS SHIT, REAL DOO DOO - { - if (cmd.sprite.type == SpriteType.SPRITE_MSDF) { - rl::beginShaderMode(msdf_shader); - defer rl::endShaderMode(); - rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); - } else { - rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); - } - }; - } else { - io::printfn("unknown texture id: %d", cmd.sprite.texture_id); - } - case CmdType.CMD_SCISSOR: - if (cmd.scissor.rect.w == 0 && cmd.scissor.rect.h == 0) { - rl::endScissorMode(); - } else { - rl::beginScissorMode(cmd.scissor.rect.x, cmd.scissor.rect.y, cmd.scissor.rect.w, cmd.scissor.rect.h); - } - default: - io::printfn("Unknown cmd type: %s", cmd.type); - } - } + /* Start UI Drawing */ + ren.begin_render(true); + + ren.render_ugui(&ui.cmd_queue); + + ren.end_render(); + draw_times.push(clock.mark()); //draw_times.print_stats(); - rl::endDrawing(); /* End Drawing */ } - rl::closeWindow(); - return 0; } diff --git a/src/renderer.c3 b/src/renderer.c3 index cc0bab5..32e5f0d 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -55,6 +55,7 @@ const int MAX_QUAD_BATCH = 256; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; + sdl::GPUTransferBuffer* transfer_buffer; bool initialized; int count; int off; // the offset to draw from @@ -173,6 +174,13 @@ $endif unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } + self.quad_buffer.transfer_buffer = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + ); + if (self.quad_buffer.transfer_buffer == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + self.quad_buffer.initialized = true; } @@ -189,6 +197,8 @@ fn void Renderer.free(&self) } self.pipelines.free(); + // FIXME: release the quad buffer + sdl::release_window_from_gpu_device(self.gpu, self.win); sdl::destroy_gpu_device(self.gpu); sdl::destroy_window(self.win); @@ -511,7 +521,7 @@ fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, u } -fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v) +fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v, uint color = 0xffffffff) { Quad quad; /* v1 v4 @@ -526,10 +536,10 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, * +-------------+ * v2 v3 */ - quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}}; - quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}}; - quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}}; - quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}}; + quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}, .col.u = color}; + quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}, .col.u = color}; + quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}, .col.u = color}; + quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}, .col.u = color}; // triangle 1 indices quad.indices.i1 = 0; // v1 quad.indices.i2 = 1; // v2 @@ -582,27 +592,21 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) if (self.quad_buffer.count >= MAX_QUAD_BATCH || source_quad == null) { return false; } + QuadBuffer* qb = &self.quad_buffer; // upload the quad data to the gpu - if (self.quad_buffer.initialized == false) { + if (qb.initialized == false) { unreachable("quad buffer not initialized"); } - GPUTransferBuffer* buf = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} - ); - if (buf == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); - } - - Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, qb.transfer_buffer, true); if (quad == null) { unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); } *quad = *source_quad; - sdl::unmap_gpu_transfer_buffer(self.gpu, buf); + sdl::unmap_gpu_transfer_buffer(self.gpu, qb.transfer_buffer); GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); if (cmd == null) { @@ -611,15 +615,14 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); // upload vertices - QuadBuffer* qb = &self.quad_buffer; sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.vertices.offsetof}, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.transfer_buffer, .offset = Quad.vertices.offsetof}, &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, false ); // upload indices sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = buf, .offset = Quad.indices.offsetof}, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.transfer_buffer, .offset = Quad.indices.offsetof}, &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, false ); @@ -628,8 +631,6 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) if (!sdl::submit_gpu_command_buffer(cmd)) { unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } - sdl::release_gpu_transfer_buffer(self.gpu, buf); - //sdl::wait_for_gpu_idle(self.gpu); qb.count++; @@ -788,7 +789,6 @@ fn void Renderer.reset_scissor(&self) fn void Renderer.render_ugui(&self, CmdQueue* queue) { - io::printn("---------------------------------------"); Cmd* last_command; for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) { if (last_command == null || last_command.type != cmd.type) { @@ -800,11 +800,10 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue) case CMD_RECT: CmdRect r = cmd.rect; self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); - io::printn(r); case CMD_SPRITE: // TODO: support hue in sprite CmdSprite s = cmd.sprite; - self.push_sprite(s.rect.x, s.rect.y, s.rect.w, s.rect.h, s.texture_rect.x, s.texture_rect.y); + self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y); case CMD_UPDATE_ATLAS: // TODO: verify the correct type CmdUpdateAtlas u = cmd.update_atlas; From 00f5e7166619694f1e48274fd3ab0fe9d9b089f7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Jun 2025 19:56:43 +0200 Subject: [PATCH 114/250] enable cycling when mapping the transfer buffers to avoid corruption --- src/renderer.c3 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderer.c3 b/src/renderer.c3 index 32e5f0d..2c83406 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -112,6 +112,7 @@ struct ViewsizeUniform @align(16) { } const int DEBUG = 1; +const bool CYCLE = true; fn void Renderer.init(&self, ZString title, uint width, uint height) { @@ -498,7 +499,7 @@ fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, u unreachable("failed to upload texture data at creating the transfer buffer: %s", sdl::get_error()); } - char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, false); + char* gpu_mem = (char*)sdl::map_gpu_transfer_buffer(self.gpu, buf, CYCLE); if (gpu_mem == null) { unreachable("failed to upload texture data at mapping the transfer buffer: %s", sdl::get_error()); } @@ -599,7 +600,7 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) unreachable("quad buffer not initialized"); } - Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, qb.transfer_buffer, true); + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, qb.transfer_buffer, CYCLE); if (quad == null) { unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); } From 10ee643a0cef5272159384290370e32c369c0a2f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 12 Jun 2025 20:01:33 +0200 Subject: [PATCH 115/250] disable vsync --- src/renderer.c3 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer.c3 b/src/renderer.c3 index 2c83406..fdbd7b5 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -158,6 +158,9 @@ $endif unreachable("failed to claim window for use with gpu: %s", sdl::get_error()); } + // set swapchain parameters, like vsync + sdl::set_gpu_swapchain_parameters(self.gpu, self.win, GPU_SWAPCHAINCOMPOSITION_SDR, GPU_PRESENTMODE_IMMEDIATE); + // // initialize the quad buffer // ========================== From cda2f27a49f6061299d76387704df553c2d35ad4 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 14 Jun 2025 14:49:53 +0200 Subject: [PATCH 116/250] Batch uploads to the gpu --- src/main.c3 | 3 ++ src/renderer.c3 | 114 +++++++++++++++++++++++++++++++----------------- 2 files changed, 76 insertions(+), 41 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index a2a5546..4ca797f 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -268,6 +268,7 @@ fn int main(String[] args) ui.div_begin("fps", {0, ui.height-100, -300, 100})!!; { ui.layout_set_column()!!; + ui.text_unbounded("frame number", string::tformat("frame %d", frame))!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; }; @@ -290,6 +291,8 @@ fn int main(String[] args) draw_times.push(clock.mark()); //draw_times.print_stats(); /* End Drawing */ + + frame++; } return 0; diff --git a/src/renderer.c3 b/src/renderer.c3 index fdbd7b5..f1ec415 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -55,10 +55,17 @@ const int MAX_QUAD_BATCH = 256; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; - sdl::GPUTransferBuffer* transfer_buffer; - bool initialized; + + sdl::GPUTransferBuffer* vertex_ts; + sdl::GPUTransferBuffer* index_ts; + + Vertex[] vertex_ts_mapped; + short[] index_ts_mapped; + int count; int off; // the offset to draw from + + bool initialized; } alias ShaderList = IdList{Shader}; @@ -164,28 +171,47 @@ $endif // // initialize the quad buffer // ========================== - self.quad_buffer.vert_buf = sdl::create_gpu_buffer(self.gpu, + QuadBuffer* qb = &self.quad_buffer; + + // create the vertex and index buffer on the gpu + qb.vert_buf = sdl::create_gpu_buffer(self.gpu, &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} ); - if (self.quad_buffer.vert_buf == null) { + if (qb.vert_buf == null) { unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error()); } - self.quad_buffer.idx_buf = sdl::create_gpu_buffer(self.gpu, + qb.idx_buf = sdl::create_gpu_buffer(self.gpu, &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} ); - if (self.quad_buffer.idx_buf == null) { + if (qb.idx_buf == null) { unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } - self.quad_buffer.transfer_buffer = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} + // allocate the transfer buffers for the vertices and indices + qb.vertex_ts = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} ); - if (self.quad_buffer.transfer_buffer == null) { + if (qb.vertex_ts == null) { unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); } - self.quad_buffer.initialized = true; + qb.index_ts = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} + ); + if (qb.index_ts == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + + // map the transfer buffers + qb.vertex_ts_mapped = ((Vertex*)sdl::map_gpu_transfer_buffer(self.gpu, qb.vertex_ts, false))[:MAX_QUAD_BATCH]; + qb.index_ts_mapped = ((short*)sdl::map_gpu_transfer_buffer(self.gpu, qb.index_ts, false))[:MAX_QUAD_BATCH]; + if (qb.vertex_ts_mapped.ptr == null || qb.index_ts_mapped.ptr == null) { + unreachable("failed to map vertex or index buffers: %s", sdl::get_error()); + } + + + qb.initialized = true; } fn void Renderer.free(&self) @@ -591,6 +617,8 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color return self.upload_quad(&quad); } +// this does not upload a quad, but it simply copies the quad data to the correct transfer buffers. +// Data transfer to the GPU only happens in draw_quads() to save time fn bool Renderer.upload_quad(&self, Quad* source_quad) { if (self.quad_buffer.count >= MAX_QUAD_BATCH || source_quad == null) { @@ -603,38 +631,17 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) unreachable("quad buffer not initialized"); } - Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, qb.transfer_buffer, CYCLE); - if (quad == null) { - unreachable("failed to map gpu transfer buffer: %s", sdl::get_error()); - } + qb.vertex_ts_mapped[qb.count*4 + 0] = source_quad.vertices.v1; + qb.vertex_ts_mapped[qb.count*4 + 1] = source_quad.vertices.v2; + qb.vertex_ts_mapped[qb.count*4 + 2] = source_quad.vertices.v3; + qb.vertex_ts_mapped[qb.count*4 + 3] = source_quad.vertices.v4; - *quad = *source_quad; - - sdl::unmap_gpu_transfer_buffer(self.gpu, qb.transfer_buffer); - - GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); - if (cmd == null) { - unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); - } - GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); - - // upload vertices - sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = qb.transfer_buffer, .offset = Quad.vertices.offsetof}, - &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.count * Quad.vertices.sizeof, .size = Quad.vertices.sizeof}, - false - ); - // upload indices - sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = qb.transfer_buffer, .offset = Quad.indices.offsetof}, - &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.count * Quad.indices.sizeof, .size = Quad.indices.sizeof}, - false - ); - - sdl::end_gpu_copy_pass(cpy); - if (!sdl::submit_gpu_command_buffer(cmd)) { - unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); - } + qb.index_ts_mapped[qb.count*6 + 0] = source_quad.indices.i1; + qb.index_ts_mapped[qb.count*6 + 1] = source_quad.indices.i2; + qb.index_ts_mapped[qb.count*6 + 2] = source_quad.indices.i3; + qb.index_ts_mapped[qb.count*6 + 3] = source_quad.indices.i4; + qb.index_ts_mapped[qb.count*6 + 4] = source_quad.indices.i5; + qb.index_ts_mapped[qb.count*6 + 5] = source_quad.indices.i6; qb.count++; @@ -649,6 +656,31 @@ fn void Renderer.draw_quads(&self) if (qb.off == qb.count) return; + // upload quads + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.vertex_ts, .offset = qb.off * Quad.vertices.sizeof}, + &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.off * Quad.vertices.sizeof, .size = Quad.vertices.sizeof * (long)(qb.count - qb.off)}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.index_ts, .offset = qb.off * Quad.indices.sizeof}, + &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.off * Quad.indices.sizeof, .size = Quad.indices.sizeof * (long)(qb.count - qb.off)}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); From 0ef2bceeec005537a732965f11c99d1f1bd3f91d Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sat, 14 Jun 2025 15:09:34 +0200 Subject: [PATCH 117/250] handle scissor and vsync --- src/main.c3 | 2 +- src/renderer.c3 | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 4ca797f..6718998 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -61,7 +61,7 @@ fn int main(String[] args) defer ui.free(); ren::Renderer ren; - ren.init("Ugui Test", 640, 480); + ren.init("Ugui Test", 640, 480, true); defer ren.free(); ui.input_window_size(640, 480)!!; diff --git a/src/renderer.c3 b/src/renderer.c3 index f1ec415..a237fb1 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -51,7 +51,7 @@ struct Texture { } // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH -const int MAX_QUAD_BATCH = 256; +const int MAX_QUAD_BATCH = 2048; struct QuadBuffer { sdl::GPUBuffer* vert_buf; sdl::GPUBuffer* idx_buf; @@ -86,6 +86,8 @@ struct Renderer { Id sprite_atlas_id; Id font_atlas_id; + + uint scissor_x, scissor_y, scissor_w, scissor_h; } // how each vertex is represented in the gpu @@ -121,7 +123,7 @@ struct ViewsizeUniform @align(16) { const int DEBUG = 1; const bool CYCLE = true; -fn void Renderer.init(&self, ZString title, uint width, uint height) +fn void Renderer.init(&self, ZString title, uint width, uint height, bool vsync) { // set wayland hint automagically $if DEBUG == 0: @@ -166,7 +168,8 @@ $endif } // set swapchain parameters, like vsync - sdl::set_gpu_swapchain_parameters(self.gpu, self.win, GPU_SWAPCHAINCOMPOSITION_SDR, GPU_PRESENTMODE_IMMEDIATE); + GPUPresentMode present_mode = vsync ? GPU_PRESENTMODE_VSYNC : GPU_PRESENTMODE_IMMEDIATE; + sdl::set_gpu_swapchain_parameters(self.gpu, self.win, GPU_SWAPCHAINCOMPOSITION_SDR, present_mode); // // initialize the quad buffer @@ -845,9 +848,15 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue) CmdUpdateAtlas u = cmd.update_atlas; char[] pixels = u.raw_buffer[..u.width*u.height*u.bpp]; self.update_texture(u.id, pixels, u.width, u.height); - case CMD_SCISSOR: break; // FIXME: ugui sends a scissor event before any rect event, this cannot be done and needs different handling + case CMD_SCISSOR: ugui::Rect s = cmd.scissor.rect; - self.set_scissor(s.x, s.y, s.w, s.h); + if (s.x == 0 && s.y == 0 && s.w == 0 && s.h == 0) { + self.get_window_size((int*)&s.w, (int*)&s.h); + } + self.scissor_x = s.x; + self.scissor_y = s.y; + self.scissor_w = s.w; + self.scissor_h = s.h; default: unreachable("unknown command: %s", cmd.type); } @@ -863,6 +872,7 @@ fn void Renderer.begin_command(&self, Cmd* cmd) switch (cmd.type) { case CMD_RECT: self.start_render_pass("UGUI_PIPELINE_RECT"); + self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h); case CMD_SPRITE: // TODO: support multiple sprite and font atlases CmdSprite s = cmd.sprite; @@ -883,6 +893,7 @@ fn void Renderer.begin_command(&self, Cmd* cmd) } self.start_render_pass(pipeline); self.bind_texture(texture); + self.set_scissor(self.scissor_x, self.scissor_y, self.scissor_w, self.scissor_h); case CMD_UPDATE_ATLAS: break; case CMD_SCISSOR: break; default: unreachable("unknown command: %s", cmd.type); From 865c7dabaa1e0a6b54594cc58648a56fa5d75d34 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Jun 2025 00:26:27 +0200 Subject: [PATCH 118/250] indirect rendering --- src/main.c3 | 2 +- src/renderer.c3 | 60 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/main.c3 b/src/main.c3 index 6718998..3618f18 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -61,7 +61,7 @@ fn int main(String[] args) defer ui.free(); ren::Renderer ren; - ren.init("Ugui Test", 640, 480, true); + ren.init("Ugui Test", 640, 480, false); defer ren.free(); ui.input_window_size(640, 480)!!; diff --git a/src/renderer.c3 b/src/renderer.c3 index a237fb1..3e35a8b 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -53,14 +53,18 @@ struct Texture { // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH const int MAX_QUAD_BATCH = 2048; struct QuadBuffer { - sdl::GPUBuffer* vert_buf; - sdl::GPUBuffer* idx_buf; + sdl::GPUBuffer* vert_buf; // GPU vertex buffer + sdl::GPUBuffer* idx_buf; // GPU index buffer + sdl::GPUBuffer* ind_buf; // GPU indirect render commands buffer + // driver-side transfer buffers sdl::GPUTransferBuffer* vertex_ts; sdl::GPUTransferBuffer* index_ts; - + sdl::GPUTransferBuffer* indirect_ts; + // driver-side transfer buffer mappings Vertex[] vertex_ts_mapped; short[] index_ts_mapped; + sdl::GPUIndexedIndirectDrawCommand* indirect_ts_mapped; int count; int off; // the offset to draw from @@ -191,25 +195,45 @@ $endif unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } + qb.ind_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDIRECT, .size = GPUIndexedIndirectDrawCommand.sizeof * MAX_QUAD_BATCH} + ); + if (qb.ind_buf == null) { + unreachable("failed to initialize quad buffer (indirect commands): %s", sdl::get_error()); + } + + // allocate the transfer buffers for the vertices and indices qb.vertex_ts = sdl::create_gpu_transfer_buffer(self.gpu, &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} ); if (qb.vertex_ts == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + unreachable("failed to create gpu vertex transfer buffer: %s", sdl::get_error()); } qb.index_ts = sdl::create_gpu_transfer_buffer(self.gpu, &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} ); if (qb.index_ts == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + unreachable("failed to create gpu index transfer buffer: %s", sdl::get_error()); } + qb.indirect_ts = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){ + .usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, + .size = GPUIndexedIndirectDrawCommand.sizeof * MAX_QUAD_BATCH + } + ); + if (qb.indirect_ts == null) { + unreachable("failed to create gpu indirect command transfer buffer: %s", sdl::get_error()); + } + + // map the transfer buffers qb.vertex_ts_mapped = ((Vertex*)sdl::map_gpu_transfer_buffer(self.gpu, qb.vertex_ts, false))[:MAX_QUAD_BATCH]; qb.index_ts_mapped = ((short*)sdl::map_gpu_transfer_buffer(self.gpu, qb.index_ts, false))[:MAX_QUAD_BATCH]; - if (qb.vertex_ts_mapped.ptr == null || qb.index_ts_mapped.ptr == null) { + qb.indirect_ts_mapped = ((GPUIndexedIndirectDrawCommand*)sdl::map_gpu_transfer_buffer(self.gpu, qb.indirect_ts, false))[:MAX_QUAD_BATCH]; + if (qb.vertex_ts_mapped.ptr == null || qb.index_ts_mapped.ptr == null || qb.indirect_ts_mapped == null) { unreachable("failed to map vertex or index buffers: %s", sdl::get_error()); } @@ -646,6 +670,8 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) qb.index_ts_mapped[qb.count*6 + 4] = source_quad.indices.i5; qb.index_ts_mapped[qb.count*6 + 5] = source_quad.indices.i6; + qb.indirect_ts_mapped[qb.count] = {6, 1, qb.count*6, qb.count*4, 0}; + qb.count++; return true; @@ -678,19 +704,29 @@ fn void Renderer.draw_quads(&self) &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.off * Quad.indices.sizeof, .size = Quad.indices.sizeof * (long)(qb.count - qb.off)}, false ); + // upload commands + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.indirect_ts, .offset = qb.off * GPUIndexedIndirectDrawCommand.sizeof}, + &&(GPUBufferRegion){.buffer = qb.ind_buf, .offset = qb.off * GPUIndexedIndirectDrawCommand.sizeof, .size = GPUIndexedIndirectDrawCommand.sizeof * (long)(qb.count - qb.off)}, + false + ); sdl::end_gpu_copy_pass(cpy); if (!sdl::submit_gpu_command_buffer(cmd)) { unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } - sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); - sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); + //sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); + sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = 0}}, 1); + //sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); + sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); - // we need instancing to not do this - for (int i = 0; i < qb.count - qb.off; i++) { - sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, i*6, i*4, 0); - } + //// we need instancing to not do this + //for (int i = 0; i < qb.count - qb.off; i++) { + // sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, i*6, i*4, 0); + //} + + sdl::draw_gpu_indexed_primitives_indirect(self.render_pass, qb.ind_buf, qb.off * GPUIndexedIndirectDrawCommand.sizeof, qb.count - qb.off); qb.off = qb.count; } From f30db0dd47dedd1a33523a32e06d07ed2e206dd0 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Jun 2025 18:54:35 +0200 Subject: [PATCH 119/250] implemented instanced rendering --- lib/ugui.c3l/src/ugui_core.c3 | 4 +- resources/shaders/source/font.frag.glsl | 2 +- resources/shaders/source/msdf.frag.glsl | 2 +- resources/shaders/source/rect.frag.glsl | 50 +--- resources/shaders/source/rect.vert.glsl | 29 ++- resources/shaders/source/sprite.frag.glsl | 2 +- resources/shaders/source/sprite.vert.glsl | 19 +- src/main.c3 | 2 +- src/renderer.c3 | 283 ++++++++++++---------- 9 files changed, 190 insertions(+), 203 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 7c0f552..7db60a6 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -68,7 +68,7 @@ const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; const uint STACK_STEP = 10; const uint MAX_ELEMS = 128; -const uint MAX_CMDS = 256; +const uint MAX_CMDS = 2048; const uint ROOT_ID = 1; const uint TEXT_MAX = 64; @@ -197,7 +197,7 @@ fn void? Ctx.init(&ctx) ctx.cache.init()!; defer catch { (void)ctx.cache.free(); } - ctx.cmd_queue.init(MAX_ELEMENTS)!; + ctx.cmd_queue.init(MAX_CMDS)!; defer catch { (void)ctx.cmd_queue.free(); } ctx.active_div = 0; diff --git a/resources/shaders/source/font.frag.glsl b/resources/shaders/source/font.frag.glsl index 4c5f3c8..56207b4 100644 --- a/resources/shaders/source/font.frag.glsl +++ b/resources/shaders/source/font.frag.glsl @@ -13,7 +13,7 @@ layout(location = 0) out vec4 fragColor; void main() { ivec2 ts = textureSize(tx, 0); - vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 fts = vec2(ts); vec2 real_uv = uv / fts; vec4 opacity = texture(tx, real_uv); diff --git a/resources/shaders/source/msdf.frag.glsl b/resources/shaders/source/msdf.frag.glsl index e4ef9e5..f46be74 100644 --- a/resources/shaders/source/msdf.frag.glsl +++ b/resources/shaders/source/msdf.frag.glsl @@ -24,7 +24,7 @@ float median(float r, float g, float b) { void main() { ivec2 ts = textureSize(tx, 0); - vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 fts = vec2(ts); vec2 real_uv = uv / fts; vec3 msd = texture(tx, real_uv).rgb; diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl index f89cbae..35a3538 100644 --- a/resources/shaders/source/rect.frag.glsl +++ b/resources/shaders/source/rect.frag.glsl @@ -4,56 +4,24 @@ layout(set = 3, binding = 0) uniform Viewport { ivec2 view; }; -layout(location = 0) in vec4 color; -layout(location = 1) in vec2 local_position; -layout(location = 2) in vec2 global_position; -layout(location = 3) in float radius; +layout(location = 0) in vec4 in_color; +layout(location = 1) in vec4 in_quad_size; // x,y, w,h +layout(location = 2) in float in_radius; layout(location = 0) out vec4 fragColor; // SDF for a rounded rectangle given the centerpoint, half size and radius, all in pixels -float sdf_rr(vec2 p, vec2 center, vec2 half_size, float radius) { - // Translate fragment position to rectangle's coordinate system - p -= center; - // Adjust for rounded corners: shrink the rectangle by the radius +float sdf_rr(vec2 p, vec2 half_size, float radius) { vec2 q = abs(p) - half_size + radius; - // Combine distance components: - // - max(q, 0.0) handles regions outside the rounded corners - // - min(max(q.x, q.y), 0.0) handles regions inside the rectangle return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; } void main() { - // local_position are normalized coordinates in the rectangle, passed from the - // vertex shader - /* - * Window - * +-----------------------+ - * | (1,1) | - * | +----------x | - * | | | | - * | | | | - * | | Rect | | - * | | | | - * | | | | - * | x----------+ | - * | (-1,-1) | - * | | - * +-----------------------+ - */ + vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5; + vec2 half_size = in_quad_size.zw * 0.5; + float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius); + float alpha = 1.0 - smoothstep(0.0, 1.0, distance); - vec2 dx = dFdx(local_position); - vec2 dy = dFdy(local_position); - // Conversion from normalized coordinates to pixels - vec2 norm_to_px = 1.0 / vec2(length(dx), length(dy)); - - vec2 centerpoint = global_position - local_position * norm_to_px; - // the half size of the rectangle is also norm_to_px - vec2 half_size = 1.0 * norm_to_px; - - float distance = sdf_rr(global_position, centerpoint, half_size, radius); - float alpha = 1.0 - smoothstep(0.0, 1.0, max(distance, 0.0)); - - fragColor = vec4(color.rgb, color.a * alpha); + fragColor = vec4(in_color.rgb, in_color.a * alpha); } \ No newline at end of file diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl index 2c08c7c..f60b501 100644 --- a/resources/shaders/source/rect.vert.glsl +++ b/resources/shaders/source/rect.vert.glsl @@ -5,26 +5,25 @@ layout(set = 1, binding = 0) uniform Viewport { }; layout(location = 0) in ivec2 position; -layout(location = 1) in ivec2 uv; -layout(location = 2) in ivec4 color; +layout(location = 1) in ivec4 attr; // quad x,y,w,h +layout(location = 2) in ivec2 uv; // x,y in the texture +layout(location = 3) in ivec4 color; -layout(location = 0) out vec4 col; -layout(location = 1) out vec2 local_position; -layout(location = 2) out vec2 global_position; -layout(location = 3) out float radius; +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_quad_size; +layout(location = 2) out float out_radius; void main() { // vertex position - vec2 pos; - pos.x = float(position.x)*2.0 / view.x - 1.0; - pos.y = -(float(position.y)*2.0 / view.y - 1.0); + ivec2 px_pos = attr.xy + position.xy * attr.zw; + vec2 clip_pos; + clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0; + clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0); - gl_Position = vec4(pos, 0.0, 1.0); + gl_Position = vec4(clip_pos, 0.0, 1.0); - local_position = vec2(sign(uv)); - global_position = gl_Position.xy; - radius = abs(float(uv.x)); - - col = vec4(color) / 255.0; + out_color = vec4(color) / 255.0; + out_quad_size = vec4(attr); + out_radius = float(abs(uv.x)); } diff --git a/resources/shaders/source/sprite.frag.glsl b/resources/shaders/source/sprite.frag.glsl index 789d546..ade47cb 100644 --- a/resources/shaders/source/sprite.frag.glsl +++ b/resources/shaders/source/sprite.frag.glsl @@ -12,7 +12,7 @@ layout(set = 2, binding = 0) uniform sampler2D tx; void main() { ivec2 ts = textureSize(tx, 0); - vec2 fts = vec2(float(ts.x), float(ts.y)); + vec2 fts = vec2(ts); vec2 real_uv = uv / fts; fragColor = texture(tx, real_uv); } \ No newline at end of file diff --git a/resources/shaders/source/sprite.vert.glsl b/resources/shaders/source/sprite.vert.glsl index e0a476c..3c8a443 100644 --- a/resources/shaders/source/sprite.vert.glsl +++ b/resources/shaders/source/sprite.vert.glsl @@ -5,19 +5,24 @@ layout(set = 1, binding = 0) uniform Viewport { }; layout(location = 0) in ivec2 position; -layout(location = 1) in ivec2 in_uv; -layout(location = 2) in ivec4 color; +layout(location = 1) in ivec4 attr; // quad x,y,w,h +layout(location = 2) in ivec2 in_uv; +layout(location = 3) in ivec4 color; layout(location = 0) out vec2 out_uv; layout(location = 1) out vec4 out_color; void main() { - vec2 pos; - pos.x = float(position.x)*2.0 / view.x - 1.0; - pos.y = -(float(position.y)*2.0 / view.y - 1.0); - gl_Position = vec4(pos, 0.0, 1.0); + // vertex position + ivec2 px_pos = attr.xy + position.xy * attr.zw; + vec2 clip_pos; + clip_pos.x = float(px_pos.x)*2.0 / view.x - 1.0; + clip_pos.y = -(float(px_pos.y)*2.0 / view.y - 1.0); - out_uv = vec2(float(in_uv.x), float(in_uv.y)); + gl_Position = vec4(clip_pos, 0.0, 1.0); + + vec2 px_uv = in_uv.xy + position.xy * attr.zw; + out_uv = vec2(px_uv); out_color = vec4(color) / 255.0; } diff --git a/src/main.c3 b/src/main.c3 index 6718998..3618f18 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -61,7 +61,7 @@ fn int main(String[] args) defer ui.free(); ren::Renderer ren; - ren.init("Ugui Test", 640, 480, true); + ren.init("Ugui Test", 640, 480, false); defer ren.free(); ui.input_window_size(640, 480)!!; diff --git a/src/renderer.c3 b/src/renderer.c3 index a237fb1..1882fdf 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -53,14 +53,13 @@ struct Texture { // The GPU buffers that contain quad info, the size is determined by MAX_QUAD_BATCH const int MAX_QUAD_BATCH = 2048; struct QuadBuffer { - sdl::GPUBuffer* vert_buf; - sdl::GPUBuffer* idx_buf; + sdl::GPUBuffer* vert_buf; // on-gpu vertex buffer + sdl::GPUBuffer* idx_buf; // on-gpu index buffer + sdl::GPUBuffer* attr_buf; // on-gpu quad attribute buffer - sdl::GPUTransferBuffer* vertex_ts; - sdl::GPUTransferBuffer* index_ts; + sdl::GPUTransferBuffer* attr_ts; - Vertex[] vertex_ts_mapped; - short[] index_ts_mapped; + QuadAttributes[] attr_ts_mapped; int count; int off; // the offset to draw from @@ -90,24 +89,24 @@ struct Renderer { uint scissor_x, scissor_y, scissor_w, scissor_h; } -// how each vertex is represented in the gpu -struct Vertex @packed { +// How each vertex is represented in the gpu +struct Vertex { + short x, y; +} + +// Attributes of each quad instance +struct QuadAttributes { struct pos { - short x, y; + short x, y, w, h; } struct uv { short u, v; } - struct col { // FIXME: this is shit - union { - char r, g, b, a; - char[4] arr; - uint u; - } - } + uint color; } -struct Quad @packed { +// A single quad +struct Quad { struct vertices { Vertex v1,v2,v3,v4; } @@ -176,40 +175,102 @@ $endif // ========================== QuadBuffer* qb = &self.quad_buffer; + // since instanced rendering is used, on the gpu there is only one mesh, a single quad. + // create the vertex and index buffer on the gpu qb.vert_buf = sdl::create_gpu_buffer(self.gpu, - &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = Quad.vertices.sizeof} ); if (qb.vert_buf == null) { unreachable("failed to initialize quad buffer (vertex): %s", sdl::get_error()); } qb.idx_buf = sdl::create_gpu_buffer(self.gpu, - &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_INDEX, .size = Quad.indices.sizeof} ); if (qb.idx_buf == null) { unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } - // allocate the transfer buffers for the vertices and indices - qb.vertex_ts = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.vertices.sizeof * MAX_QUAD_BATCH} + qb.attr_buf = sdl::create_gpu_buffer(self.gpu, + &&(GPUBufferCreateInfo){.usage = GPU_BUFFERUSAGE_VERTEX, .size = QuadAttributes.sizeof * MAX_QUAD_BATCH} ); - if (qb.vertex_ts == null) { - unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + if (qb.attr_buf == null) { + unreachable("failed to initialize quad buffer (index): %s", sdl::get_error()); } - qb.index_ts = sdl::create_gpu_transfer_buffer(self.gpu, - &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.indices.sizeof * MAX_QUAD_BATCH} + + // upload the quad mesh + GPUTransferBuffer *ts = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = Quad.sizeof} ); - if (qb.index_ts == null) { + if (ts == null) { unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); } + Quad* quad = (Quad*)sdl::map_gpu_transfer_buffer(self.gpu, ts, false); - // map the transfer buffers - qb.vertex_ts_mapped = ((Vertex*)sdl::map_gpu_transfer_buffer(self.gpu, qb.vertex_ts, false))[:MAX_QUAD_BATCH]; - qb.index_ts_mapped = ((short*)sdl::map_gpu_transfer_buffer(self.gpu, qb.index_ts, false))[:MAX_QUAD_BATCH]; - if (qb.vertex_ts_mapped.ptr == null || qb.index_ts_mapped.ptr == null) { + /* v1 v4 + * +-------------+ + * | _/| + * | _/ | + * | 1 _/ | + * | _/ | + * | _/ | + * | _/ 2 | + * |/ | + * +-------------+ + * v2 v3 + */ + quad.vertices.v1 = {.x = 0, .y = 0}; + quad.vertices.v2 = {.x = 0, .y = 1}; + quad.vertices.v3 = {.x = 1, .y = 1}; + quad.vertices.v4 = {.x = 1, .y = 0}; + // triangle 1 indices + quad.indices.i1 = 0; // v1 + quad.indices.i2 = 1; // v2 + quad.indices.i3 = 3; // v4 + // triangle 2 indices + quad.indices.i4 = 1; // v2 + quad.indices.i5 = 2; // v3 + quad.indices.i6 = 3; // v4 + + sdl::unmap_gpu_transfer_buffer(self.gpu, ts); + + GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); + if (cmd == null) { + unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); + } + GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); + + // upload vertices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.vertices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = 0, .size = Quad.vertices.sizeof}, + false + ); + // upload indices + sdl::upload_to_gpu_buffer(cpy, + &&(GPUTransferBufferLocation){.transfer_buffer = ts, .offset = Quad.indices.offsetof}, + &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = 0, .size = Quad.indices.sizeof}, + false + ); + + sdl::end_gpu_copy_pass(cpy); + if (!sdl::submit_gpu_command_buffer(cmd)) { + unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); + } + sdl::release_gpu_transfer_buffer(self.gpu, ts); + + + // create and map the quad attributes transfer buffer + qb.attr_ts = sdl::create_gpu_transfer_buffer(self.gpu, + &&(GPUTransferBufferCreateInfo){.usage = GPU_TRANSFERBUFFERUSAGE_UPLOAD, .size = QuadAttributes.sizeof * MAX_QUAD_BATCH} + ); + if (qb.attr_ts == null) { + unreachable("failed to create gpu transfer buffer: %s", sdl::get_error()); + } + qb.attr_ts_mapped = ((QuadAttributes*)sdl::map_gpu_transfer_buffer(self.gpu, qb.attr_ts, false))[:MAX_QUAD_BATCH]; + if (qb.attr_ts_mapped.ptr == null) { unreachable("failed to map vertex or index buffers: %s", sdl::get_error()); } @@ -355,35 +416,47 @@ fn void Renderer.create_pipeline(&self, String shader_name, PipelineType type) // is represented by two floats, the color as 32 bit rgba and the uv also as intgers. .vertex_input_state = { // the description of each vertex buffer, for now I use only one buffer - .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){{ - .slot = 0, - .pitch = Vertex.sizeof, - .input_rate = GPU_VERTEXINPUTRATE_VERTEX, - .instance_step_rate = 0, - }}, - .num_vertex_buffers = 1, - // the description of each vertex, each vertex has three properties + .vertex_buffer_descriptions = (GPUVertexBufferDescription[]){ + { // first slot, per-vertex attributes + .slot = 0, + .pitch = Vertex.sizeof, + .input_rate = GPU_VERTEXINPUTRATE_VERTEX, + }, + { // second slot, per-instance attributes + .slot = 1, + .pitch = QuadAttributes.sizeof, + .input_rate = GPU_VERTEXINPUTRATE_INSTANCE, + } + }, + .num_vertex_buffers = 2, + // the description of each vertex .vertex_attributes = (GPUVertexAttribute[]){ { // at location zero there is the position of the vertex .location = 0, - .buffer_slot = 0, // only one buffer so always slot zero - .format = GPU_VERTEXELEMENTFORMAT_SHORT2, - .offset = Vertex.pos.offsetof, + .buffer_slot = 0, // buffer slot zero so per-vertex + .format = GPU_VERTEXELEMENTFORMAT_SHORT2, // x,y + .offset = 0, }, - { // at location one there are the uv coordinates + { // at location one there is the per-quad position .location = 1, - .buffer_slot = 0, - .format = GPU_VERTEXELEMENTFORMAT_SHORT2, - .offset = Vertex.uv.offsetof, + .buffer_slot = 1, // buffer slot one so per-instance + .format = GPU_VERTEXELEMENTFORMAT_SHORT4, // x,y,w,h + .offset = QuadAttributes.pos.offsetof, }, - { // at location two there is the color + { // at location two there are the per-quad uv coordinates .location = 2, - .buffer_slot = 0, - .format = GPU_VERTEXELEMENTFORMAT_UBYTE4, // 4x8bit unsigned rgba format - .offset = Vertex.col.offsetof, + .buffer_slot = 1, + .format = GPU_VERTEXELEMENTFORMAT_SHORT2, + .offset = QuadAttributes.uv.offsetof, + }, + { // at location three there is the quad color + .location = 3, + .buffer_slot = 1, + .format = GPU_VERTEXELEMENTFORMAT_UBYTE4, + .offset = QuadAttributes.color.offsetof, } }, - .num_vertex_attributes = 3, + .num_vertex_attributes = 4, }, // the pipeline's primitive type and rasterizer state differs based on what needs to // be drawn @@ -556,75 +629,32 @@ fn void Renderer.update_texture_by_id(&self, Id id, char[] pixels, uint width, u fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, short v, uint color = 0xffffffff) { - Quad quad; - /* v1 v4 - * +-------------+ - * | _/| - * | _/ | - * | 1 _/ | - * | _/ | - * | _/ | - * | _/ 2 | - * |/ | - * +-------------+ - * v2 v3 - */ - quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = u, .v = v}, .col.u = color}; - quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = u, .v = v+h}, .col.u = color}; - quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = u+w, .v = v+h}, .col.u = color}; - quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = u+w, .v = v}, .col.u = color}; - // triangle 1 indices - quad.indices.i1 = 0; // v1 - quad.indices.i2 = 1; // v2 - quad.indices.i3 = 3; // v4 - // triangle 2 indices - quad.indices.i4 = 1; // v2 - quad.indices.i5 = 2; // v3 - quad.indices.i6 = 3; // v4 + QuadAttributes qa = { + .pos = {.x = x, .y = y, .w = w, .h = h}, + .uv = {.u = u, .v = v}, + .color = color + }; - return self.upload_quad(&quad); + return self.upload_quad(qa); } // Push a quad into the quad buffer, return true on success and false on failure fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) { - Quad quad; - /* v1 v4 - * +-------------+ - * | _/| - * | _/ | - * | 1 _/ | - * | _/ | - * | _/ | - * | _/ 2 | - * |/ | - * +-------------+ - * v2 v3 - */ - // the wanted radius is pushed into the uv coordinates, the vertex shader then extracts the absolute value - // and passes it to the fragment shader, then it uses the sign to give the fragment shader local coordinates - // into the quad. - quad.vertices.v1 = {.pos = {.x = x, .y = y}, .uv = {.u = -radius, .v = +radius}, .col.u = color}; - quad.vertices.v2 = {.pos = {.x = x, .y = y+h}, .uv = {.u = -radius, .v = -radius}, .col.u = color}; - quad.vertices.v3 = {.pos = {.x = x+w, .y = y+h}, .uv = {.u = +radius, .v = -radius}, .col.u = color}; - quad.vertices.v4 = {.pos = {.x = x+w, .y = y}, .uv = {.u = +radius, .v = +radius}, .col.u = color}; - // triangle 1 indices - quad.indices.i1 = 0; // v1 - quad.indices.i2 = 1; // v2 - quad.indices.i3 = 3; // v4 - // triangle 2 indices - quad.indices.i4 = 1; // v2 - quad.indices.i5 = 2; // v3 - quad.indices.i6 = 3; // v4 + QuadAttributes qa = { + .pos = {.x = x, .y = y, .w = w, .h = h}, + .uv = {.u = radius, .v = radius}, + .color = color + }; - return self.upload_quad(&quad); + return self.upload_quad(qa); } // this does not upload a quad, but it simply copies the quad data to the correct transfer buffers. // Data transfer to the GPU only happens in draw_quads() to save time -fn bool Renderer.upload_quad(&self, Quad* source_quad) +fn bool Renderer.upload_quad(&self, QuadAttributes qa) { - if (self.quad_buffer.count >= MAX_QUAD_BATCH || source_quad == null) { + if (self.quad_buffer.count >= MAX_QUAD_BATCH) { return false; } QuadBuffer* qb = &self.quad_buffer; @@ -634,17 +664,7 @@ fn bool Renderer.upload_quad(&self, Quad* source_quad) unreachable("quad buffer not initialized"); } - qb.vertex_ts_mapped[qb.count*4 + 0] = source_quad.vertices.v1; - qb.vertex_ts_mapped[qb.count*4 + 1] = source_quad.vertices.v2; - qb.vertex_ts_mapped[qb.count*4 + 2] = source_quad.vertices.v3; - qb.vertex_ts_mapped[qb.count*4 + 3] = source_quad.vertices.v4; - - qb.index_ts_mapped[qb.count*6 + 0] = source_quad.indices.i1; - qb.index_ts_mapped[qb.count*6 + 1] = source_quad.indices.i2; - qb.index_ts_mapped[qb.count*6 + 2] = source_quad.indices.i3; - qb.index_ts_mapped[qb.count*6 + 3] = source_quad.indices.i4; - qb.index_ts_mapped[qb.count*6 + 4] = source_quad.indices.i5; - qb.index_ts_mapped[qb.count*6 + 5] = source_quad.indices.i6; + qb.attr_ts_mapped[qb.count] = qa; qb.count++; @@ -666,16 +686,10 @@ fn void Renderer.draw_quads(&self) } GPUCopyPass* cpy = sdl::begin_gpu_copy_pass(cmd); - // upload vertices + // upload quad attributes sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = qb.vertex_ts, .offset = qb.off * Quad.vertices.sizeof}, - &&(GPUBufferRegion){.buffer = qb.vert_buf, .offset = qb.off * Quad.vertices.sizeof, .size = Quad.vertices.sizeof * (long)(qb.count - qb.off)}, - false - ); - // upload indices - sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = qb.index_ts, .offset = qb.off * Quad.indices.sizeof}, - &&(GPUBufferRegion){.buffer = qb.idx_buf, .offset = qb.off * Quad.indices.sizeof, .size = Quad.indices.sizeof * (long)(qb.count - qb.off)}, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = QuadAttributes.sizeof * qb.off}, + &&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = QuadAttributes.sizeof * qb.off, .size = QuadAttributes.sizeof * (long)(qb.count - qb.off)}, false ); @@ -684,13 +698,14 @@ fn void Renderer.draw_quads(&self) unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } - sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){{.buffer = qb.vert_buf, .offset = qb.off*Quad.vertices.sizeof}}, 1); - sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = qb.off*Quad.indices.sizeof}, GPU_INDEXELEMENTSIZE_16BIT); + sdl::bind_gpu_vertex_buffers(self.render_pass, 0, + (GPUBufferBinding[]){ + {.buffer = qb.vert_buf, .offset = 0}, + {.buffer = qb.attr_buf, .offset = 0}, + }, 2); + sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); - // we need instancing to not do this - for (int i = 0; i < qb.count - qb.off; i++) { - sdl::draw_gpu_indexed_primitives(self.render_pass, 6, 1, i*6, i*4, 0); - } + sdl::draw_gpu_indexed_primitives(self.render_pass, 6, qb.count-qb.off, 0, 0, qb.off); qb.off = qb.count; } From 05232c1d246286e0ca6711b01ea7c7aaa4068de3 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Jun 2025 23:35:37 +0200 Subject: [PATCH 120/250] do a single upload pass to reduce badwidth --- src/renderer.c3 | 69 ++++++++++++++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/src/renderer.c3 b/src/renderer.c3 index 1882fdf..4bb140b 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -61,8 +61,8 @@ struct QuadBuffer { QuadAttributes[] attr_ts_mapped; + // how many quads are currently stored int count; - int off; // the offset to draw from bool initialized; } @@ -635,10 +635,9 @@ fn bool Renderer.push_sprite(&self, short x, short y, short w, short h, short u, .color = color }; - return self.upload_quad(qa); + return self.map_quad(qa); } -// Push a quad into the quad buffer, return true on success and false on failure fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color, ushort radius = 0) { QuadAttributes qa = { @@ -647,12 +646,12 @@ fn bool Renderer.push_quad(&self, short x, short y, short w, short h, uint color .color = color }; - return self.upload_quad(qa); + return self.map_quad(qa); } // this does not upload a quad, but it simply copies the quad data to the correct transfer buffers. // Data transfer to the GPU only happens in draw_quads() to save time -fn bool Renderer.upload_quad(&self, QuadAttributes qa) +fn bool Renderer.map_quad(&self, QuadAttributes qa) { if (self.quad_buffer.count >= MAX_QUAD_BATCH) { return false; @@ -671,15 +670,10 @@ fn bool Renderer.upload_quad(&self, QuadAttributes qa) return true; } -// draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense -// to draw them one a the time -fn void Renderer.draw_quads(&self) +fn void Renderer.upload_quads(&self) { QuadBuffer* qb = &self.quad_buffer; - if (qb.off == qb.count) return; - - // upload quads GPUCommandBuffer* cmd = sdl::acquire_gpu_command_buffer(self.gpu); if (cmd == null) { unreachable("failed to upload quad at acquiring command buffer: %s", sdl::get_error()); @@ -688,8 +682,8 @@ fn void Renderer.draw_quads(&self) // upload quad attributes sdl::upload_to_gpu_buffer(cpy, - &&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = QuadAttributes.sizeof * qb.off}, - &&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = QuadAttributes.sizeof * qb.off, .size = QuadAttributes.sizeof * (long)(qb.count - qb.off)}, + &&(GPUTransferBufferLocation){.transfer_buffer = qb.attr_ts, .offset = 0}, + &&(GPUBufferRegion){.buffer = qb.attr_buf, .offset = 0, .size = QuadAttributes.sizeof * qb.count}, false ); @@ -697,6 +691,18 @@ fn void Renderer.draw_quads(&self) if (!sdl::submit_gpu_command_buffer(cmd)) { unreachable("failed to upload quads at submit command buffer: %s", sdl::get_error()); } +} + +// draw all quads in the quad buffer, since uniforms are per-drawcall it makes no sense +// to draw them one a the time +fn void Renderer.draw_quads(&self, uint off, uint count) +{ + QuadBuffer* qb = &self.quad_buffer; + + // too many quads to draw + if (off >= qb.count || count > qb.count - off) { + unreachable("too many quads, have %d, requested %d, offset %d", qb.count, count, off); + } sdl::bind_gpu_vertex_buffers(self.render_pass, 0, (GPUBufferBinding[]){ @@ -705,15 +711,12 @@ fn void Renderer.draw_quads(&self) }, 2); sdl::bind_gpu_index_buffer(self.render_pass, &&(GPUBufferBinding){.buffer = qb.idx_buf, .offset = 0}, GPU_INDEXELEMENTSIZE_16BIT); - sdl::draw_gpu_indexed_primitives(self.render_pass, 6, qb.count-qb.off, 0, 0, qb.off); - - qb.off = qb.count; + sdl::draw_gpu_indexed_primitives(self.render_pass, 6, count, 0, 0, off); } fn void Renderer.reset_quads(&self) { self.quad_buffer.count = 0; - self.quad_buffer.off = 0; } @@ -843,21 +846,33 @@ fn void Renderer.reset_scissor(&self) fn void Renderer.render_ugui(&self, CmdQueue* queue) { + // upload pass + foreach (&c : queue) { + if (c.type == CMD_RECT) { + CmdRect r = c.rect; + self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); + } else if (c.type == CMD_SPRITE) { + CmdSprite s = c.sprite; + self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y, s.hue.to_uint()); + } + } + self.upload_quads(); + Cmd* last_command; + uint off; + uint count; for (Cmd* cmd; (cmd = queue.dequeue() ?? null) != null;) { if (last_command == null || last_command.type != cmd.type) { - self.end_command(last_command); + self.end_command(last_command, off, count); self.begin_command(cmd); + off += count; + count = 0; } switch (cmd.type) { - case CMD_RECT: - CmdRect r = cmd.rect; - self.push_quad(r.rect.x, r.rect.y, r.rect.w, r.rect.h, r.color.to_uint(), r.radius); + case CMD_RECT: nextcase; case CMD_SPRITE: - // TODO: support hue in sprite - CmdSprite s = cmd.sprite; - self.push_sprite(s.rect.x, s.rect.y, s.texture_rect.w, s.texture_rect.h, s.texture_rect.x, s.texture_rect.y); + count++; case CMD_UPDATE_ATLAS: // TODO: verify the correct type CmdUpdateAtlas u = cmd.update_atlas; @@ -877,7 +892,7 @@ fn void Renderer.render_ugui(&self, CmdQueue* queue) last_command = cmd; } - self.end_command(last_command); + self.end_command(last_command, off, count); } fn void Renderer.begin_command(&self, Cmd* cmd) @@ -915,14 +930,14 @@ fn void Renderer.begin_command(&self, Cmd* cmd) } } -fn void Renderer.end_command(&self, Cmd* cmd) +fn void Renderer.end_command(&self, Cmd* cmd, uint off, uint count) { if (cmd == null) return; switch (cmd.type) { case CMD_RECT: nextcase; case CMD_SPRITE: - self.draw_quads(); + self.draw_quads(off, count); self.end_render_pass(); case CMD_UPDATE_ATLAS: break; case CMD_SCISSOR: break; From b411718c9406ba6decdbab8c0841e7f74172504f Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Sun, 15 Jun 2025 23:47:09 +0200 Subject: [PATCH 121/250] add fps counter --- src/main.c3 | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main.c3 b/src/main.c3 index 3618f18..0f9af6c 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -113,8 +113,10 @@ fn int main(String[] args) isz frame; + double fps; bool toggle = true; time::Clock clock; + time::Clock fps_clock; Times ui_times; Times draw_times; @@ -126,6 +128,7 @@ fn int main(String[] args) bool quit = false; while (!quit) { clock.mark(); + fps_clock.mark(); // FIXME: modkeys input is broken ugui::ModKeys mod; @@ -268,7 +271,7 @@ fn int main(String[] args) ui.div_begin("fps", {0, ui.height-100, -300, 100})!!; { ui.layout_set_column()!!; - ui.text_unbounded("frame number", string::tformat("frame %d", frame))!!; + ui.text_unbounded("frame number", string::tformat("frame %d, fps = %.2f", frame, fps))!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; }; @@ -292,6 +295,8 @@ fn int main(String[] args) //draw_times.print_stats(); /* End Drawing */ + + fps = 1.0 / fps_clock.mark().to_sec(); frame++; } From 5b0169cd94079e9b58846c16f383c9d88e4a8ca5 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 17 Jun 2025 18:23:36 +0200 Subject: [PATCH 122/250] better input handling --- lib/ugui.c3l/src/ugui_input.c3 | 6 ++++ src/main.c3 | 60 +++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_input.c3 b/lib/ugui.c3l/src/ugui_input.c3 index 5dcb12b..45811c4 100644 --- a/lib/ugui.c3l/src/ugui_input.c3 +++ b/lib/ugui.c3l/src/ugui_input.c3 @@ -181,6 +181,12 @@ fn void Ctx.input_text_unicode(&ctx, char[] text) ctx.input.events.text_input = true; } +fn void Ctx.input_char(&ctx, char c) +{ + char[1] b = {c}; + ctx.input_text_utf8(b[..]); +} + // Mouse Buttons down fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys) { diff --git a/src/main.c3 b/src/main.c3 index 0f9af6c..1324859 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -5,6 +5,7 @@ import ugui; import std::time; import std::collections::ringbuffer; import std::core::string; +import std::ascii; import sdlrenderer::ren; import sdl3::sdl; @@ -61,7 +62,7 @@ fn int main(String[] args) defer ui.free(); ren::Renderer ren; - ren.init("Ugui Test", 640, 480, false); + ren.init("Ugui Test", 640, 480, true); defer ren.free(); ui.input_window_size(640, 480)!!; @@ -117,6 +118,7 @@ fn int main(String[] args) bool toggle = true; time::Clock clock; time::Clock fps_clock; + time::Clock sleep_clock; Times ui_times; Times draw_times; @@ -124,21 +126,37 @@ fn int main(String[] args) // // MAIN LOOP // + sdl::start_text_input(ren.win); + sdl::Event e; bool quit = false; + ugui::ModKeys mod; + ugui::MouseButtons btn; while (!quit) { clock.mark(); fps_clock.mark(); + sleep_clock.mark(); - // FIXME: modkeys input is broken - ugui::ModKeys mod; - while (sdl::poll_event(&e)) { + do { switch (e.type) { case EVENT_QUIT: quit = true; + case EVENT_KEY_UP: nextcase; case EVENT_KEY_DOWN: - mod.rctrl = !!(e.key.key == K_RCTRL); - mod.lctrl = !!(e.key.key == K_LCTRL); + mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl; + mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl; + + // pressing ctrl+key or alt+key does not generate a character as such no + // TEXT_INPUT event is generated. When those keys are pressed we have to + // do manual text input, bummer + if (e.type == EVENT_KEY_DOWN && (mod.lctrl || mod.rctrl)) { + if (ascii::is_alnum_m((uint)e.key.key)) { + ui.input_char((char)e.key.key); + } + } + + case EVENT_TEXT_INPUT: + ui.input_text_utf8(e.text.text.str_view()); case EVENT_WINDOW_RESIZED: ui.input_window_size((short)e.window.data1, (short)e.window.data2)!!; case EVENT_WINDOW_FOCUS_GAINED: @@ -152,27 +170,20 @@ fn int main(String[] args) case EVENT_MOUSE_BUTTON_DOWN: nextcase; case EVENT_MOUSE_BUTTON_UP: sdl::MouseButtonFlags mb = sdl::get_mouse_state(null, null); - ugui::MouseButtons b = { + btn = { .btn_left = !!(mb & BUTTON_LMASK), .btn_right = !!(mb & BUTTON_RMASK), .btn_middle = !!(mb & BUTTON_MMASK), .btn_4 = !!(mb & BUTTON_X1MASK), .btn_5 = !!(mb & BUTTON_X2MASK), }; - ui.input_mouse_button(b); + case EVENT_POLL_SENTINEL: break; default: io::eprintfn("unhandled event: %s", e.type); } - } + } while(sdl::poll_event(&e)); ui.input_mod_keys(mod); - - /* - for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) { - ZString kname = rl::getKeyName(key); - if (kname == null) continue; - ui.input_text_unicode(kname.str_view()); - } - */ + ui.input_mouse_button(btn); /* End Input Handling */ @@ -181,14 +192,6 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; -/* - ui.div_begin("main", ugui::DIV_FILL)!!; - ui.button("button0", {0,0,30,30})!!; - ui.draw_sprite("sprite1", "tux")!!; - ui.text_unbounded("text1", "Ciao")!!; - ui.div_end()!!; -*/ - ui.div_begin("main", {.w=-100})!!; { ui.layout_set_column()!!; @@ -268,12 +271,12 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", {0, ui.height-100, -300, 100})!!; + ui.div_begin("fps", {0, ui.height-150, -300, 150})!!; { ui.layout_set_column()!!; ui.text_unbounded("frame number", string::tformat("frame %d, fps = %.2f", frame, fps))!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; - ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; + ui.text_unbounded("ui text input", string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; }; ui.div_end()!!; @@ -295,7 +298,10 @@ fn int main(String[] args) //draw_times.print_stats(); /* End Drawing */ + // wait for the next event, timeout after 100ms + sdl::wait_event_timeout(&e, (int)(100.0-sleep_clock.mark().to_ms()-0.5)); + fps = 1.0 / fps_clock.mark().to_sec(); frame++; } From cd83c528ee1103cf8bed3b116af28403f9ef883a Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Thu, 19 Jun 2025 15:24:46 +0200 Subject: [PATCH 123/250] fixed vulkan validation errors --- resources/shaders/source/rect.vert.glsl | 2 +- resources/shaders/source/sprite.vert.glsl | 2 +- src/renderer.c3 | 26 ++++++++++++++++++----- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/resources/shaders/source/rect.vert.glsl b/resources/shaders/source/rect.vert.glsl index f60b501..00785db 100644 --- a/resources/shaders/source/rect.vert.glsl +++ b/resources/shaders/source/rect.vert.glsl @@ -7,7 +7,7 @@ layout(set = 1, binding = 0) uniform Viewport { layout(location = 0) in ivec2 position; layout(location = 1) in ivec4 attr; // quad x,y,w,h layout(location = 2) in ivec2 uv; // x,y in the texture -layout(location = 3) in ivec4 color; +layout(location = 3) in uvec4 color; layout(location = 0) out vec4 out_color; layout(location = 1) out vec4 out_quad_size; diff --git a/resources/shaders/source/sprite.vert.glsl b/resources/shaders/source/sprite.vert.glsl index 3c8a443..2dca841 100644 --- a/resources/shaders/source/sprite.vert.glsl +++ b/resources/shaders/source/sprite.vert.glsl @@ -7,7 +7,7 @@ layout(set = 1, binding = 0) uniform Viewport { layout(location = 0) in ivec2 position; layout(location = 1) in ivec4 attr; // quad x,y,w,h layout(location = 2) in ivec2 in_uv; -layout(location = 3) in ivec4 color; +layout(location = 3) in uvec4 color; layout(location = 0) out vec2 out_uv; layout(location = 1) out vec4 out_color; diff --git a/src/renderer.c3 b/src/renderer.c3 index 4bb140b..45e5cb6 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -86,7 +86,7 @@ struct Renderer { Id sprite_atlas_id; Id font_atlas_id; - uint scissor_x, scissor_y, scissor_w, scissor_h; + int scissor_x, scissor_y, scissor_w, scissor_h; } // How each vertex is represented in the gpu @@ -291,7 +291,18 @@ fn void Renderer.free(&self) } self.pipelines.free(); - // FIXME: release the quad buffer + foreach (&t: self.textures) { + sdl::release_gpu_texture(self.gpu, t.texture); + sdl::release_gpu_sampler(self.gpu, t.sampler); + } + self.textures.free(); + + QuadBuffer* qb = &self.quad_buffer; + sdl::unmap_gpu_transfer_buffer(self.gpu, qb.attr_ts); + sdl::release_gpu_transfer_buffer(self.gpu, qb.attr_ts); + sdl::release_gpu_buffer(self.gpu, qb.vert_buf); + sdl::release_gpu_buffer(self.gpu, qb.idx_buf); + sdl::release_gpu_buffer(self.gpu, qb.attr_buf); sdl::release_window_from_gpu_device(self.gpu, self.win); sdl::destroy_gpu_device(self.gpu); @@ -377,12 +388,14 @@ fn void Renderer.load_spirv_shader_from_file(&self, String name, String vert_pat char[] frag_code; // create vertex shader - vert_code = mem::new_array(char, file::get_size(vert_path)!!+1); + usz size = file::get_size(vert_path)!!; + vert_code = mem::new_array(char, size + size%4); file::load_buffer(vert_path, vert_code)!!; defer mem::free(vert_code); // create fragment shader - frag_code = mem::new_array(char, file::get_size(frag_path)!!+1); + size = file::get_size(frag_path)!!; + frag_code = mem::new_array(char, size + size%4); file::load_buffer(frag_path, frag_code)!!; defer mem::free(frag_code); @@ -810,8 +823,11 @@ fn void Renderer.bind_texture(&self, String texture_name) ); } -fn void Renderer.set_scissor(&self, uint x, uint y, uint w, uint h) +fn void Renderer.set_scissor(&self, int x, int y, int w, int h) { + // in vulkan scissor size must be positive, clamp to zero + w = max(w, 0); + h = max(h, 0); sdl::set_gpu_scissor(self.render_pass, &&(sdl::Rect){x,y,w,h}); } From fc3fa32ddd99add9430ea38f59202e59fb068cbd Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 25 Jun 2025 10:47:23 +0200 Subject: [PATCH 124/250] more smoothing on rounded corners --- resources/shaders/source/rect.frag.glsl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/shaders/source/rect.frag.glsl b/resources/shaders/source/rect.frag.glsl index 35a3538..4ae4ea4 100644 --- a/resources/shaders/source/rect.frag.glsl +++ b/resources/shaders/source/rect.frag.glsl @@ -21,7 +21,7 @@ void main() vec2 centerpoint = in_quad_size.xy + in_quad_size.zw * 0.5; vec2 half_size = in_quad_size.zw * 0.5; float distance = sdf_rr(vec2(gl_FragCoord) - centerpoint, half_size, in_radius); - float alpha = 1.0 - smoothstep(0.0, 1.0, distance); + float alpha = 1.0 - smoothstep(0.0, 1.5, distance); fragColor = vec4(in_color.rgb, in_color.a * alpha); } \ No newline at end of file From c98c00bfb936ee4dbbb9181864872d83a4ff2c61 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Wed, 25 Jun 2025 10:47:44 +0200 Subject: [PATCH 125/250] ecode editor config --- .ecode/project_build.json | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .ecode/project_build.json diff --git a/.ecode/project_build.json b/.ecode/project_build.json new file mode 100644 index 0000000..3e67462 --- /dev/null +++ b/.ecode/project_build.json @@ -0,0 +1,43 @@ +{ + "Debug": { + "build": [ + { + "args": "", + "command": "scripts/compile_shaders.sh", + "working_dir": "" + }, + { + "args": "clean-run -g", + "command": "c3c", + "working_dir": "" + } + ], + "build_types": [], + "clean": [ + { + "args": "", + "command": "rm build/ugui", + "working_dir": "" + } + ], + "config": { + "clear_sys_env": false + }, + "os": [ + "linux" + ], + "output_parser": { + "config": { + "relative_file_paths": true + } + }, + "run": [ + { + "args": "", + "command": "build/ugui", + "name": "Custom Executable", + "working_dir": "" + } + ] + } +} \ No newline at end of file From 88d9b6502839c6dc152a7912cf518670e26879b5 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 13:07:42 +0200 Subject: [PATCH 126/250] updated build configuration --- .ecode/project_build.json | 10 +++++----- resources/shaders/Makefile | 33 ++++++++++++++++++++++++++++++ scripts/compile_shaders.sh | 41 -------------------------------------- 3 files changed, 38 insertions(+), 46 deletions(-) create mode 100644 resources/shaders/Makefile delete mode 100755 scripts/compile_shaders.sh diff --git a/.ecode/project_build.json b/.ecode/project_build.json index 3e67462..593ac60 100644 --- a/.ecode/project_build.json +++ b/.ecode/project_build.json @@ -2,12 +2,12 @@ "Debug": { "build": [ { - "args": "", - "command": "scripts/compile_shaders.sh", + "args": "-C resources/shaders", + "command": "make", "working_dir": "" }, { - "args": "clean-run -g", + "args": "build -g", "command": "c3c", "working_dir": "" } @@ -15,8 +15,8 @@ "build_types": [], "clean": [ { - "args": "", - "command": "rm build/ugui", + "args": "clean", + "command": "c3c", "working_dir": "" } ], diff --git a/resources/shaders/Makefile b/resources/shaders/Makefile new file mode 100644 index 0000000..cdc4a01 --- /dev/null +++ b/resources/shaders/Makefile @@ -0,0 +1,33 @@ +SOURCE_DIR := ./source +COMPILED_DIR := ./compiled + +SOURCE_FILES := $(wildcard $(SOURCE_DIR)/*.glsl) + +COMPILED_FILES := $(patsubst $(SOURCE_DIR)/%.glsl,$(COMPILED_DIR)/%.spv,$(SOURCE_FILES)) + +all: $(COMPILED_FILES) + @echo "Compiling shaders from $(SOURCE_DIR) -> $(COMPILED_DIR)" + +$(COMPILED_DIR)/%.spv: $(SOURCE_DIR)/%.glsl + @mkdir -p $(COMPILED_DIR) + @stage=$$(basename $< .glsl | cut -d. -f2); \ + if [ "$$stage" = "frag" ] || [ "$$stage" = "vert" ]; then \ + echo "$$stage $(notdir $<) > $(notdir $@)"; \ + glslc -O0 -g -fshader-stage=$$stage $< -o $@; \ + else \ + echo "Skipping $<: unsupported stage $$stage"; \ + fi + +$(COMPILED_DIR): + mkdir -p $(COMPILED_DIR) + +.PHONY: clean +clean: + rm -rf $(COMPILED_DIR) + +.PHONY: tree +tree: + tree $(COMPILED_DIR) + +.PHONY: compile_all +compile_all: clean all tree diff --git a/scripts/compile_shaders.sh b/scripts/compile_shaders.sh deleted file mode 100755 index 336cdf1..0000000 --- a/scripts/compile_shaders.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/sh - -source_directory="./resources/shaders/source" -compiled_directory="./resources/shaders/compiled" -#vulkan_version="1.0" - -mkdir -p "$compiled_directory" -rm -f "$compiled_directory"/* - -echo "Compiling from $source_directory -> $compiled_directory" - -for file in "$source_directory"/*; do - [ -f "$file" ] || continue # Skip non-files - filename=$(basename "$file") - - # Extract filename parts using POSIX parameter expansion - shader_language="${filename##*.}" - stage_part="${filename%.*}" # Remove extension - base_name="${stage_part%.*}" # Remove stage - stage="${stage_part#"$base_name"}" - stage="${stage#.}" # Remove leading dot - - # Skip if not in base.stage.glsl format - [ "$shader_language" = "glsl" ] && [ -n "$base_name" ] && [ -n "$stage" ] || continue - - # Handle HLSL rejection - if [ "$shader_language" != "glsl" ]; then - echo "Error: Only GLSL shaders are supported" >&2 - exit 1 - fi - - # Compile based on shader stage - case "$stage" in - frag|vert) - echo "$stage $filename > $base_name.$stage.spv" - glslc -O0 -g -fshader-stage="$stage" "$file" -o "$compiled_directory/$base_name.$stage.spv" - ;; - esac -done - -tree "$compiled_directory" \ No newline at end of file From 9827a899f15b3204577e22c9fb50c869b8958acd Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 13:08:27 +0200 Subject: [PATCH 127/250] get_line_height and get_cursor_position --- lib/ugui.c3l/src/ugui_font.c3 | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/lib/ugui.c3l/src/ugui_font.c3 b/lib/ugui.c3l/src/ugui_font.c3 index 23700c6..020ed75 100644 --- a/lib/ugui.c3l/src/ugui_font.c3 +++ b/lib/ugui.c3l/src/ugui_font.c3 @@ -211,6 +211,34 @@ fn Rect? Ctx.get_text_bounds(&ctx, String text) return text_bounds; } +fn Point? Ctx.get_cursor_position(&ctx, String text) +{ + short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; + short line_gap = (short)ctx.font.linegap; + Glyph* gp; + + // TODO: account for unicode codepoints + Point line; + Codepoint cp; + usz off, x; + while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + off += x; + bool n; + if (!ascii::is_cntrl((char)cp)) { + gp = ctx.font.get_glyph(cp)!; + line.x += gp.adv; + } else if (cp == '\n'){ + line.y += line_height + line_gap; + line.x = 0; + } else { + continue; + } + } + + return line; +} + + fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds) { short dw = bounds.w - text_bounds.w; @@ -234,3 +262,5 @@ fn Atlas*? Ctx.get_font_atlas(&ctx, String name) return &ctx.font.atlas; } + +fn int Font.line_height(&font) => (int)(font.ascender - font.descender + (float)0.5); From 6d2594db2da8214c9968bce036a85d5020270c17 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 13:08:52 +0200 Subject: [PATCH 128/250] test id generation with macros --- test/test_idgen.c3 | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 test/test_idgen.c3 diff --git a/test/test_idgen.c3 b/test/test_idgen.c3 new file mode 100644 index 0000000..d4709d5 --- /dev/null +++ b/test/test_idgen.c3 @@ -0,0 +1,30 @@ +import std::io; + +alias Id = uint; + +fn void foo_ex(Id id) +{ + io::printfn("id = %d", id); +} + + +macro Id @compute_id(...) +{ + Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE); + $for var $i = 0; $i < $vacount; $i++: + id ^= (Id)$vaconst[$i].hash(); + $endfor + return id; +} + +macro foo(...) => foo_ex(@compute_id($vasplat)); + +fn int main() +{ + foo_ex(1234); + foo(); + foo(); + foo(); + + return 0; +} \ No newline at end of file From 972c9b581dfa0cf293461223b3ff0e76e56dddb7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 13:10:00 +0200 Subject: [PATCH 129/250] text input box --- lib/ugui.c3l/src/ugui_cmd.c3 | 4 +-- lib/ugui.c3l/src/ugui_core.c3 | 4 ++- lib/ugui.c3l/src/ugui_input.c3 | 19 +++++++--- lib/ugui.c3l/src/ugui_text.c3 | 66 +++++++++++++++++++++++++++++++++- src/main.c3 | 24 ++++++------- 5 files changed, 94 insertions(+), 23 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index 77468cb..5b2ac83 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -138,7 +138,7 @@ fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_i ctx.push_cmd(&cmd, z_index)!; } -fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue = 0xffffffffu.to_rgba()) +fn void? Ctx.push_string(&ctx, Rect bounds, char[] text, int z_index, Color hue = 0xffffffffu.to_rgba()) { if (text.len == 0) { return; @@ -158,7 +158,7 @@ fn void? Ctx.push_string(&ctx, Rect bounds, String text, int z_index, Color hue short line_len; Codepoint cp; usz off, x; - while ((cp = str_to_codepoint(text[off..], &x)) != 0) { + while (off < text.len && (cp = str_to_codepoint(text[off..], &x)) != 0) { off += x; Glyph* gp; if (!ascii::is_cntrl((char)cp)) { diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 7db60a6..40696d5 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -34,6 +34,7 @@ bitstruct ElemEvents : uint { bool mouse_release : 5; bool mouse_hold : 6; bool update : 7; + bool text_input : 8; } // element structure @@ -111,7 +112,7 @@ struct Ctx { struct keyboard { char[TEXT_MAX] text; usz text_len; - ModKeys down; + ModKeys modkeys; } } @@ -343,6 +344,7 @@ fn ElemEvents Ctx.get_elem_events(&ctx, Elem *elem) .mouse_press = hover && focus && ctx.is_mouse_pressed(BTN_ANY), .mouse_release = hover && focus && ctx.is_mouse_released(BTN_ANY), .mouse_hold = hover && focus && ctx.is_mouse_down(BTN_ANY), + .text_input = focus && (ctx.input.keyboard.text_len || ctx.input.keyboard.modkeys & KMOD_TXT), }; return ev; } diff --git a/lib/ugui.c3l/src/ugui_input.c3 b/lib/ugui.c3l/src/ugui_input.c3 index 45811c4..ac70110 100644 --- a/lib/ugui.c3l/src/ugui_input.c3 +++ b/lib/ugui.c3l/src/ugui_input.c3 @@ -25,7 +25,7 @@ bitstruct MouseButtons : uint { // FIXME: all of these names were prefixed with key_ idk if this is better, // if it is remove the prefix on MouseButtons as well -// Modifier Keys, same as SDL +// Modifier Keys, intended as any key that is not text bitstruct ModKeys : uint { bool lshift : 0; bool rshift : 1; @@ -39,12 +39,20 @@ bitstruct ModKeys : uint { bool caps : 9; bool mode : 10; bool scroll : 11; + bool bkspc : 12; + bool del : 13; + // arrow keys + bool up : 14; + bool down : 15; + bool left : 16; + bool right : 17; } const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true}; const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true}; const ModKeys KMOD_ALT = {.lalt = true, .ralt = true}; const ModKeys KMOD_GUI = {.lgui = true, .rgui = true}; +const ModKeys KMOD_TXT = {.bkspc = true, .del = true}; // modkeys that act like text input const ModKeys KMOD_NONE = {}; const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max); @@ -60,7 +68,7 @@ const ModKeys KEY_ANY = (ModKeys)(ModKeys.inner.max); fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String keys) { - bool is_mod = (bool)(ctx.input.keyboard.down & mod); + bool is_mod = (bool)(ctx.input.keyboard.modkeys & mod); bool is_keys = true; String haystack = (String)ctx.input.keyboard.text[0..ctx.input.keyboard.text_len]; char[2] needle; @@ -187,9 +195,10 @@ fn void Ctx.input_char(&ctx, char c) ctx.input_text_utf8(b[..]); } -// Mouse Buttons down +// Modifier keys, like control or backspace +// TODO: make this call repetible to input modkeys one by one fn void Ctx.input_mod_keys(&ctx, ModKeys modkeys) { - ctx.input.keyboard.down = modkeys; - ctx.input.events.mod_key = (uint)ctx.input.keyboard.down != 0; + ctx.input.keyboard.modkeys = modkeys; + ctx.input.events.mod_key = (uint)ctx.input.keyboard.modkeys != 0; } diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index bf04077..2a6d436 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -3,7 +3,8 @@ module ugui; import std::io; struct ElemText { - char* str; + char[] str; + usz cursor; // cursor offset } fn void? Ctx.text_unbounded(&ctx, String label, String text) @@ -28,3 +29,66 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text) ctx.push_string(elem.bounds, text, parent.div.z_index)!; } + +fn ElemEvents? Ctx.text_box(&ctx, String label, Rect size, char[] text, usz* text_len) +{ + Id id = ctx.gen_id(label)!; + + Elem *parent = ctx.get_parent()!; + Elem *elem = ctx.get_elem(id)!; + // add it to the tree + ctx.tree.add(id, ctx.active_div)!; + + // 1. Fill the element fields + // this resets the flags + elem.type = ETYPE_TEXT; + elem.text.str = text; + + // layout the text box + elem.bounds = ctx.position_element(parent, size, true); + + // check input and update the text + elem.events = ctx.get_elem_events(elem); + + if (elem.events.text_input) { + usz l = ctx.input.keyboard.text_len; + char[] t = ctx.input.keyboard.text[..l]; + + if (l != 0 && l < text.len - *text_len) { + text[*text_len..*text_len+l] = t[..]; + *text_len += l; + } + + if (ctx.input.keyboard.modkeys.bkspc) { + *text_len = *text_len > 0 ? *text_len-1 : 0; + } + + } + elem.text.cursor = *text_len; + + // draw the box + short line_height = (short)ctx.font.line_height(); + Rect text_box = elem.bounds.sub({0,0,0,line_height}); + Rect input_box = { + .x = elem.bounds.x, + .y = elem.bounds.y + elem.bounds.h - line_height, + .w = elem.bounds.w, + .h = line_height, + }; + Rect cursor; + Point b = ctx.get_cursor_position((String)text[:elem.text.cursor])!; + cursor = { + .x = b.x, + .y = b.y, + .w = 3, + .h = line_height, + }; + cursor = cursor.off(elem.bounds.position()); + + ctx.push_rect(text_box, 0xabababffu.to_rgba(), parent.div.z_index, do_border: true)!; + ctx.push_string(text_box, text[:*text_len], parent.div.z_index)!; + ctx.push_rect(input_box, 0x424242ffu.to_rgba(), parent.div.z_index)!; + ctx.push_rect(cursor, 0x00000042u.to_rgba(), parent.div.z_index)!; + + return elem.events; +} diff --git a/src/main.c3 b/src/main.c3 index 1324859..71ead09 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -62,9 +62,9 @@ fn int main(String[] args) defer ui.free(); ren::Renderer ren; - ren.init("Ugui Test", 640, 480, true); + ren.init("Ugui Test", 800, 600, true); defer ren.free(); - ui.input_window_size(640, 480)!!; + ui.input_window_size(800, 600)!!; // // FONT LOADING @@ -145,6 +145,7 @@ fn int main(String[] args) case EVENT_KEY_DOWN: mod.rctrl = e.key.key == K_RCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.rctrl; mod.lctrl = e.key.key == K_LCTRL ? !!(e.type == EVENT_KEY_DOWN) : mod.lctrl; + mod.bkspc = e.key.key == K_BACKSPACE ? !!(e.type == EVENT_KEY_DOWN) : mod.bkspc; // pressing ctrl+key or alt+key does not generate a character as such no // TEXT_INPUT event is generated. When those keys are pressed we have to @@ -154,6 +155,8 @@ fn int main(String[] args) ui.input_char((char)e.key.key); } } + + if (e.type == EVENT_KEY_DOWN && e.key.key == K_RETURN) ui.input_char('\n'); case EVENT_TEXT_INPUT: ui.input_text_utf8(e.text.text.str_view()); @@ -226,19 +229,13 @@ fn int main(String[] args) static bool check; ui.checkbox("check1", "", {}, &check, "tick")!!; ui.toggle("toggle1", "", {}, &toggle)!!; - -/* - ui.layout_set_column()!!; - ui.button_label(" A ")!!; - ui.button_label(" B ")!!; - ui.layout_next_column()!!; - ui.button_label(" C ")!!; - ui.button_label(" D ")!!; - ui.layout_next_row()!!; - ui.button_label(" E ")!!; -*/ }; ui.draw_sprite("sprite1", "tux")!!; + + static char[128] text_box = "ciao mamma"; + static usz text_len = "ciao mamma".len; + ui.text_box("text input", {0,0,200,200}, text_box[..], &text_len)!!; + ui.div_end()!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; @@ -280,7 +277,6 @@ fn int main(String[] args) }; ui.div_end()!!; - ui.frame_end()!!; /* End UI Handling */ ui_times.push(clock.mark()); From 586e81935c0b7684d48de720f7523420f54ed9f4 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Mon, 30 Jun 2025 18:24:50 +0200 Subject: [PATCH 130/250] improved the api to not require explicit labels everywhere --- lib/ugui.c3l/src/ugui_button.c3 | 51 +++++++++++++++++------------ lib/ugui.c3l/src/ugui_core.c3 | 13 ++++++-- lib/ugui.c3l/src/ugui_div.c3 | 14 ++++---- lib/ugui.c3l/src/ugui_slider.c3 | 48 ++++++++++++++++++--------- lib/ugui.c3l/src/ugui_sprite.c3 | 7 ++-- lib/ugui.c3l/src/ugui_text.c3 | 12 ++++--- src/main.c3 | 58 ++++++++++++++++----------------- 7 files changed, 123 insertions(+), 80 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index 81b817f..5661fbb 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -7,12 +7,13 @@ struct ElemButton { int filler; } + // draw a button, return the events on that button -// FIXME: "state" should be renamed "active" to toggle between an usable button and -// an inactive (greyed-out) button -fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) +macro Ctx.button(&ctx, Rect size, bool state = false, ...) + => ctx.button_id(@compute_id($vasplat), size, state); +fn ElemEvents? Ctx.button_id(&ctx, Id id, Rect size, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -33,7 +34,7 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); - if (state) { + if (active) { col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { col = 0xff00ffffu.to_rgba(); @@ -45,9 +46,11 @@ fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false) return elem.events; } -fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool state = false) +macro Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool active = false, ...) + => ctx.button_label_id(@compute_id($vasplat), label, size, active); +fn ElemEvents? Ctx.button_label_id(&ctx, Id id, String label, Rect size, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -68,7 +71,7 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s Color col = 0x0000ffffu.to_rgba(); elem.events = ctx.get_elem_events(elem); - if (state) { + if (active) { col = 0xff0000ffu.to_rgba(); } else if (ctx.elem_focus(elem) || elem.events.mouse_hover) { col = 0xff00ffffu.to_rgba(); @@ -86,9 +89,11 @@ fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,s return elem.events; } -fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = "", bool state = false) +macro Ctx.button_icon(&ctx, String icon, String on_icon = "", bool active = false, ...) + => ctx.button_icon_id(@compute_id($vasplat), icon, on_icon, active); +fn ElemEvents? Ctx.button_icon_id(&ctx, Id id, String icon, String on_icon, bool active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -115,7 +120,7 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = elem.events = ctx.get_elem_events(elem); Id tex_id = ctx.sprite_atlas.id; - if (state && on_icon != "") { + if (active && on_icon != "") { ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, parent.div.z_index, type: on_sprite.type)!; } else { ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, parent.div.z_index, type: def_sprite.type)!; @@ -129,9 +134,11 @@ fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = // FIXME: this should be inside the style const ushort DEFAULT_CHECKBOX_SIZE = 16; -fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) +macro Ctx.checkbox(&ctx, String desc, Point off, bool* active, String tick_sprite = {}, ...) + => ctx.checkbox_id(@compute_id($vasplat), desc, off, active, tick_sprite); +fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* active, String tick_sprite) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -154,17 +161,17 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s if (elem.bounds.is_null()) return; elem.events = ctx.get_elem_events(elem); - if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); Color col; if (tick_sprite != {}) { col = ctx.style.bgcolor; ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; - if (*state) { + if (*active) { ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; } } else { - if (*state) { + if (*active) { col = 0xff0000ffu.to_rgba(); } else { col = 0xff00ffffu.to_rgba(); @@ -176,9 +183,11 @@ fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* s // FIXME: this should be inside the style const short DEFAULT_SWITCH_SIZE = 16; -fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* state) +macro Ctx.toggle(&ctx, String desc, Point off, bool* active) + => ctx.toggle_id(@compute_id($vasplat), desc, off, active); +fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -201,10 +210,10 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta if (elem.bounds.is_null()) return; elem.events = ctx.get_elem_events(elem); - if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); + if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); Color col; - if (*state) { + if (*active) { col = 0xff0000ffu.to_rgba(); } else { col = 0xff00ffffu.to_rgba(); @@ -212,6 +221,6 @@ fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* sta // Draw the button // FIXME: THIS IS SHIT ctx.push_rect(elem.bounds, ctx.style.bgcolor, parent.div.z_index, do_border: true, do_radius: true)!; - Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); + Rect t = elem.bounds.add({*active ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); ctx.push_rect(t, col, parent.div.z_index, do_border: false, do_radius: true)!; } diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 40696d5..24c90d2 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -136,10 +136,9 @@ const uint GOLDEN_RATIO = 0x9E3779B9; // generate an id combining the hashes of the parent id and the label // with the Cantor pairing function -macro Id? Ctx.gen_id(&ctx, String label) +fn Id? Ctx.gen_id(&ctx, Id id2) { Id id1 = ctx.tree.get(ctx.active_div)!; - Id id2 = label.hash(); // Mix the two IDs non-linearly Id mixed = id1 ^ id2.rotate_left(13); mixed ^= id1.rotate_left(7); @@ -147,6 +146,16 @@ macro Id? Ctx.gen_id(&ctx, String label) return mixed; } +// compute the id from arguments and the line of the call +macro Id @compute_id(...) +{ + Id id = (Id)$$LINE.hash() ^ (Id)@str_hash($$FILE); + $for var $i = 0; $i < $vacount; $i++: + id ^= (Id)$vaconst[$i].hash(); + $endfor + return id; +} + // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly fn Elem*? Ctx.get_elem(&ctx, Id id) diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index 4bfd45e..caa9e25 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -29,9 +29,11 @@ struct ElemDiv { // if the width or height are negative the width or height will be calculated based on the children size // sort similar to a flexbox, and the minimum size is set by the negative of the width or height // FIXME: there is a bug if the size.w or size.h == -0 -fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) +macro Ctx.div_begin(&ctx, Rect size, bool scroll_x = false, bool scroll_y = false, ...) + => ctx.div_begin_id(@compute_id($vasplat), size, scroll_x, scroll_y); +fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem* parent = ctx.get_parent()!; Elem* elem = ctx.get_elem(id)!; @@ -110,8 +112,8 @@ fn void? Ctx.div_end(&ctx) // vertical overflow elem.div.scroll_y.on = cbc.y > bc.y && elem.div.scroll_y.enabled; - Id hsid = ctx.gen_id("div_scrollbar_horizontal")!; - Id vsid = ctx.gen_id("div_scrollbar_vertical")!; + Id hsid = ctx.gen_id("div_scrollbar_horizontal".hash())!; + Id vsid = ctx.gen_id("div_scrollbar_vertical".hash())!; short wdim = elem.div.scroll_y.on ? (ctx.focus_id == vsid || ctx.is_hovered(ctx.find_elem(vsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; short hdim = elem.div.scroll_x.on ? (ctx.focus_id == hsid || ctx.is_hovered(ctx.find_elem(hsid)) ? SCROLLBAR_DIM*3 : SCROLLBAR_DIM) : 0; @@ -128,7 +130,7 @@ fn void? Ctx.div_end(&ctx) }; Layout prev_l = elem.div.layout; elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_ver("div_scrollbar_vertical", vslider, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; + ctx.slider_ver_id(vsid, vslider, &elem.div.scroll_y.value, max((float)bc.y / cbc.y, (float)0.15))!; elem.div.layout = prev_l; } @@ -145,7 +147,7 @@ fn void? Ctx.div_end(&ctx) }; Layout prev_l = elem.div.layout; elem.div.layout = LAYOUT_ABSOLUTE; - ctx.slider_hor("div_scrollbar_horizontal", hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; + ctx.slider_hor_id(hsid, hslider, &elem.div.scroll_x.value, max((float)bc.x / cbc.x, (float)0.15))!; elem.div.layout = prev_l; } diff --git a/lib/ugui.c3l/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 index a161389..7fb6d9a 100644 --- a/lib/ugui.c3l/src/ugui_slider.c3 +++ b/lib/ugui.c3l/src/ugui_slider.c3 @@ -8,23 +8,31 @@ struct ElemSlider { Rect handle; } + /* handle * +----+-----+---------------------+ * | |#####| | * +----+-----+---------------------+ */ +macro Ctx.slider_hor(&ctx, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba(), ...) + => ctx.slider_hor_id(@compute_id($vasplat), size, value, hpercent, bgcolor, handlecolor); <* @require value != null *> -fn ElemEvents? Ctx.slider_hor(&ctx, - String label, +fn ElemEvents? Ctx.slider_hor_id(&ctx, + Id id, Rect size, float* value, float hpercent = 0.25, Color bgcolor = 0x0000ffffu.to_rgba(), Color handlecolor = 0x0ff000ffu.to_rgba()) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -67,27 +75,35 @@ fn ElemEvents? Ctx.slider_hor(&ctx, return elem.events; } + /* - * +-+ - * | | - * | | - * +-+ - * |#| handle - * |#| - * +-+ - * | | - * | | - * +-+ + * +--+ + * | | + * | | + * +--+ + * |##| handle + * |##| + * +--+ + * | | + * | | + * +--+ */ -fn ElemEvents? Ctx.slider_ver(&ctx, - String label, +macro Ctx.slider_ver(&ctx, + Rect size, + float* value, + float hpercent = 0.25, + Color bgcolor = 0x0000ffffu.to_rgba(), + Color handlecolor = 0x0ff000ffu.to_rgba(), ...) + => ctx.slider_ver_id(@compute_id($vasplat), size, value, hpercent, bgcolor, handlecolor); +fn ElemEvents? Ctx.slider_ver_id(&ctx, + Id id, Rect size, float* value, float hpercent = 0.25, Color bgcolor = 0x0000ffffu.to_rgba(), Color handlecolor = 0x0ff000ffu.to_rgba()) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 7dca870..888a1b4 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -110,9 +110,12 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t ctx.sprite_atlas.insert(name, type, pixels, (ushort)desc.width, (ushort)desc.height, (ushort)desc.width)!; } -fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) + +macro Ctx.draw_sprite(&ctx, String name, Point off = {0,0}, ...) + => ctx.draw_sprite_id(@compute_id($vasplat), name, off); +fn void? Ctx.draw_sprite_id(&ctx, Id id, String name, Point off) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index 2a6d436..dda6ce4 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -7,9 +7,11 @@ struct ElemText { usz cursor; // cursor offset } -fn void? Ctx.text_unbounded(&ctx, String label, String text) +macro Ctx.text_unbounded(&ctx, String text, ...) + => ctx.text_unbounded_id(@compute_id($vasplat), text); +fn void? Ctx.text_unbounded_id(&ctx, Id id, String text) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; @@ -30,9 +32,11 @@ fn void? Ctx.text_unbounded(&ctx, String label, String text) ctx.push_string(elem.bounds, text, parent.div.z_index)!; } -fn ElemEvents? Ctx.text_box(&ctx, String label, Rect size, char[] text, usz* text_len) +macro Ctx.text_box(&ctx, Rect size, char[] text, usz* text_len, ...) + => ctx.text_box_id(@compute_id($vasplat), size, text, text_len); +fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_len) { - Id id = ctx.gen_id(label)!; + id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; Elem *elem = ctx.get_elem(id)!; diff --git a/src/main.c3 b/src/main.c3 index 71ead09..af2b584 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -195,71 +195,71 @@ fn int main(String[] args) if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) quit = true; - ui.div_begin("main", {.w=-100})!!; + ui.div_begin({.w=-100})!!; { ui.layout_set_column()!!; - if (ui.button("button0", {0,0,30,30}, toggle)!!.mouse_press) { + if (ui.button({0,0,30,30}, toggle)!!.mouse_press) { io::printn("press button0"); toggle = !toggle; } //ui.layout_next_column()!!; - if (ui.button("button1", {0,0,30,30})!!.mouse_press) { + if (ui.button({0,0,30,30})!!.mouse_press) { io::printn("press button1"); } //ui.layout_next_column()!!; - if (ui.button("button2", {0,0,30,30})!!.mouse_release) { + if (ui.button({0,0,30,30})!!.mouse_release) { io::printn("release button2"); } ui.layout_set_row()!!; ui.layout_next_row()!!; static float rf, gf, bf, af; - ui.slider_ver("slider_r", {0,0,30,100}, &rf)!!; - ui.slider_ver("slider_g", {0,0,30,100}, &gf)!!; - ui.slider_ver("slider_b", {0,0,30,100}, &bf)!!; - ui.slider_ver("slider_a", {0,0,30,100}, &af)!!; + ui.slider_ver({0,0,30,100}, &rf)!!; + ui.slider_ver({0,0,30,100}, &gf)!!; + ui.slider_ver({0,0,30,100}, &bf)!!; + ui.slider_ver({0,0,30,100}, &af)!!; ui.layout_next_column()!!; - ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; + ui.text_unbounded("Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; ui.layout_next_column()!!; ui.button_label("Continua!")!!; ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", {}, &check, "tick")!!; - ui.toggle("toggle1", "", {}, &toggle)!!; + ui.checkbox("", {}, &check, "tick")!!; + ui.toggle("", {}, &toggle)!!; }; - ui.draw_sprite("sprite1", "tux")!!; + ui.draw_sprite("tux")!!; static char[128] text_box = "ciao mamma"; static usz text_len = "ciao mamma".len; - ui.text_box("text input", {0,0,200,200}, text_box[..], &text_len)!!; + ui.text_box({0,0,200,200}, text_box[..], &text_len)!!; ui.div_end()!!; - ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; + ui.div_begin(ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; { ui.layout_set_column()!!; static float slider2 = 0.5; - if (ui.slider_ver("slider", {0,0,30,100}, &slider2)!!.update) { + if (ui.slider_ver({0,0,30,100}, &slider2)!!.update) { io::printfn("other slider: %f", slider2); } - ui.button("button0", {0,0,50,50})!!; - ui.button("button1", {0,0,50,50})!!; - ui.button("button2", {0,0,50,50})!!; - ui.button("button3", {0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; if (toggle) { - ui.button("button4", {0,0,50,50})!!; - ui.button("button5", {0,0,50,50})!!; - ui.button("button6", {0,0,50,50})!!; - ui.button("button7", {0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; + ui.button({0,0,50,50})!!; } ui.layout_next_column()!!; ui.layout_set_row()!!; static float f1, f2; - ui.slider_hor("hs1", {0,0,100,30}, &f1)!!; - ui.slider_hor("hs2", {0,0,100,30}, &f2)!!; + ui.slider_hor({0,0,100,30}, &f1)!!; + ui.slider_hor({0,0,100,30}, &f2)!!; }; ui.div_end()!!; @@ -268,12 +268,12 @@ fn int main(String[] args) TimeStats uts = ui_times.get_stats(); ui.layout_set_floating()!!; - ui.div_begin("fps", {0, ui.height-150, -300, 150})!!; + ui.div_begin({0, ui.height-150, -300, 150})!!; { ui.layout_set_column()!!; - ui.text_unbounded("frame number", string::tformat("frame %d, fps = %.2f", frame, fps))!!; - ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; - ui.text_unbounded("ui text input", string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; + ui.text_unbounded(string::tformat("frame %d, fps = %.2f", frame, fps))!!; + ui.text_unbounded(string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; + ui.text_unbounded(string::tformat("%s %s", mod.lctrl, (String)ui.input.keyboard.text[..]))!!; }; ui.div_end()!!; From 1de4fd78b870c78043e6e8c6f8ca8bb94a170908 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 1 Jul 2025 15:28:08 +0200 Subject: [PATCH 131/250] better checkboxes --- lib/ugui.c3l/src/ugui_button.c3 | 18 +++++++----------- lib/ugui.c3l/src/ugui_cmd.c3 | 1 - lib/ugui.c3l/src/ugui_sprite.c3 | 8 +++++++- src/main.c3 | 1 + 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index 5661fbb..93e519f 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -145,8 +145,6 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ // add it to the tree ctx.tree.add(id, ctx.active_div)!; - // FIXME: for now checkboxes and buttons have no members so the element types - // can be the same if (elem.flags.is_new) { elem.type = ETYPE_BUTTON; } else if (elem.type != ETYPE_BUTTON) { @@ -163,21 +161,19 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ elem.events = ctx.get_elem_events(elem); if (elem.events.mouse_hover && elem.events.mouse_release) *active = !(*active); - Color col; + Color col = ctx.style.bgcolor; if (tick_sprite != {}) { - col = ctx.style.bgcolor; ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; if (*active) { - ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; + ctx.draw_sprite_raw(tick_sprite, elem.bounds, center: true)!; } } else { - if (*active) { - col = 0xff0000ffu.to_rgba(); - } else { - col = 0xff00ffffu.to_rgba(); - } - // Draw the button ctx.push_rect(elem.bounds, col, parent.div.z_index, do_border: true, do_radius: true)!; + if (*active) { + ushort x = DEFAULT_CHECKBOX_SIZE / 4; + Rect check = elem.bounds.add({x, x, -x*2, -x*2}); + ctx.push_rect(check, 0xff0000ffu.to_rgba(), parent.div.z_index, do_radius: true)!; + } } } diff --git a/lib/ugui.c3l/src/ugui_cmd.c3 b/lib/ugui.c3l/src/ugui_cmd.c3 index 5b2ac83..5c76aeb 100644 --- a/lib/ugui.c3l/src/ugui_cmd.c3 +++ b/lib/ugui.c3l/src/ugui_cmd.c3 @@ -124,7 +124,6 @@ fn void? Ctx.push_rect(&ctx, Rect rect, Color color, int z_index, bool do_border ctx.push_cmd(&cmd, z_index)!; } -// TODO: add texture id fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, int z_index, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) { Cmd cmd = { diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 888a1b4..216b1e2 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -145,10 +145,16 @@ fn void? Ctx.draw_sprite_id(&ctx, Id id, String name, Point off) return ctx.push_sprite(elem.bounds, uv, tex_id, parent.div.z_index)!; } -fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) +fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds, bool center = false) { Elem *parent = ctx.get_parent()!; Sprite* sprite = ctx.sprite_atlas.get(name)!; Id tex_id = ctx.sprite_atlas.id; + + if (center) { + Point off = {.x = (bounds.w - sprite.w) / 2, .y = (bounds.h - sprite.h) / 2}; + bounds = bounds.off(off); + } + return ctx.push_sprite(bounds, sprite.uv(), tex_id, parent.div.z_index, type: sprite.type)!; } diff --git a/src/main.c3 b/src/main.c3 index af2b584..337cf81 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -228,6 +228,7 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; ui.checkbox("", {}, &check, "tick")!!; + ui.checkbox("", {}, &check)!!; ui.toggle("", {}, &toggle)!!; }; ui.draw_sprite("tux")!!; From 849b267f9142884857e0614d7101b334c11e0ab3 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 1 Jul 2025 15:33:22 +0200 Subject: [PATCH 132/250] renamed get_element_by_tree_idx to get_active_div --- lib/ugui.c3l/src/ugui_core.c3 | 15 +++------------ lib/ugui.c3l/src/ugui_div.c3 | 2 +- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index 24c90d2..cb64e8b 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -183,17 +183,7 @@ macro Elem* Ctx.find_elem(&ctx, Id id) return elem; } -// FIXME: Since ids are now keyed with the element's parent id, this function does not work -// outside of the element's div block. -// this searches an element in the cache by label, it does not create a new element -// if it does't find one -//macro Ctx.get_elem_by_label(&ctx, String label) -//{ -// Id id = ctx.get_id(label); -// return ctx.cache.search(id); -//} - -macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private +fn Elem*? Ctx.get_active_div(&ctx) { Id id = ctx.tree.get(ctx.active_div)!; return ctx.cache.search(id); @@ -278,7 +268,8 @@ const int DEBUG = 1; fn void? Ctx.frame_end(&ctx) { - Elem* root = ctx.get_elem_by_tree_idx(0)!; + // FIXME: this is not guaranteed to be root. the user might forget to close a div or some other element + Elem* root = ctx.get_active_div()!; root.div.layout = LAYOUT_ROW; // 1. clear the tree diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index caa9e25..f259096 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -87,7 +87,7 @@ fn void? Ctx.div_end(&ctx) { // swap the children bounds Elem* parent = ctx.get_parent()!; - Elem* elem = ctx.get_elem_by_tree_idx(ctx.active_div)!; + Elem* elem = ctx.get_active_div()!; elem.div.pcb = elem.div.children_bounds; // FIXME: this causes all elements inside the div to loose focus since the mouse press happens From c49a689304575374d6d485e5e6660c07c03387cb Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 1 Jul 2025 16:03:11 +0200 Subject: [PATCH 133/250] get_elem now also pushes into the tree and check the correct type --- TODO | 2 ++ lib/ugui.c3l/src/ugui_button.c3 | 50 ++++----------------------------- lib/ugui.c3l/src/ugui_core.c3 | 49 +++++++++++++++----------------- lib/ugui.c3l/src/ugui_div.c3 | 12 ++------ lib/ugui.c3l/src/ugui_slider.c3 | 15 ++-------- lib/ugui.c3l/src/ugui_sprite.c3 | 16 +++-------- lib/ugui.c3l/src/ugui_text.c3 | 16 ++--------- src/main.c3 | 2 +- 8 files changed, 41 insertions(+), 121 deletions(-) diff --git a/TODO b/TODO index a3055f3..0c832c3 100644 --- a/TODO +++ b/TODO @@ -64,6 +64,8 @@ to maintain focus until mouse release (fix scroll bars) - border radius [x] add a command to update an atlas [ ] New window command, useful for popups +[ ] Text command returns the text bounds, this way we can avoid the pattern + draw_text(a, pos) -> off = compute_bounds(a) -> draw_text(b, pos+off) -> ... ## Atlas diff --git a/lib/ugui.c3l/src/ugui_button.c3 b/lib/ugui.c3l/src/ugui_button.c3 index 93e519f..2e98af1 100644 --- a/lib/ugui.c3l/src/ugui_button.c3 +++ b/lib/ugui.c3l/src/ugui_button.c3 @@ -16,15 +16,7 @@ fn ElemEvents? Ctx.button_id(&ctx, Id id, Rect size, bool active) id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - if (elem.flags.is_new) { - elem.type = ETYPE_BUTTON; - } else if (elem.type != ETYPE_BUTTON) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; elem.bounds = ctx.position_element(parent, size, true); @@ -53,13 +45,7 @@ fn ElemEvents? Ctx.button_label_id(&ctx, Id id, String label, Rect size, bool ac id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - // 1. Fill the element fields - // this resets the flags - elem.type = ETYPE_BUTTON; + Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; Rect text_size = ctx.get_text_bounds(label)!; @@ -96,15 +82,7 @@ fn ElemEvents? Ctx.button_icon_id(&ctx, Id id, String icon, String on_icon, bool id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - if (elem.flags.is_new) { - elem.type = ETYPE_BUTTON; - } else if (elem.type != ETYPE_BUTTON) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Sprite* def_sprite = ctx.sprite_atlas.get(icon)!; Sprite* on_sprite = ctx.sprite_atlas.get(on_icon) ?? &&(Sprite){}; @@ -141,15 +119,7 @@ fn void? Ctx.checkbox_id(&ctx, Id id, String description, Point off, bool* activ id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - if (elem.flags.is_new) { - elem.type = ETYPE_BUTTON; - } else if (elem.type != ETYPE_BUTTON) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Rect size = {off.x, off.y, DEFAULT_CHECKBOX_SIZE, DEFAULT_CHECKBOX_SIZE}; elem.bounds = ctx.position_element(parent, size, true); @@ -186,17 +156,7 @@ fn void? Ctx.toggle_id(&ctx, Id id, String description, Point off, bool* active) id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - // FIXME: for now switches and buttons have no members so the element types - // can be the same - if (elem.flags.is_new) { - elem.type = ETYPE_BUTTON; - } else if (elem.type != ETYPE_BUTTON) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_BUTTON)!; Rect size = {off.x, off.y, DEFAULT_SWITCH_SIZE*2, DEFAULT_SWITCH_SIZE}; elem.bounds = ctx.position_element(parent, size, true); diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index cb64e8b..d0354f8 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -40,6 +40,7 @@ bitstruct ElemEvents : uint { // element structure struct Elem { Id id; + isz tree_idx; ElemFlags flags; ElemEvents events; Rect bounds; @@ -158,7 +159,7 @@ macro Id @compute_id(...) // get or push an element from the cache, return a pointer to it // resets all flags except is_new which is set accordingly -fn Elem*? Ctx.get_elem(&ctx, Id id) +fn Elem*? Ctx.get_elem(&ctx, Id id, ElemType type) { Elem empty_elem; bool is_new; @@ -166,8 +167,13 @@ fn Elem*? Ctx.get_elem(&ctx, Id id) elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!; elem.flags = (ElemFlags)0; elem.flags.is_new = is_new; - // FIXME: should this be here? or is it better to have the elements set the id? elem.id = id; + if (is_new == false && elem.type != type) { + return WRONG_ELEMENT_TYPE?; + } else { + elem.type = type; + } + elem.tree_idx = ctx.tree.add(id, ctx.active_div)!; return elem; } @@ -223,11 +229,13 @@ fn void Ctx.free(&ctx) fn void? Ctx.frame_begin(&ctx) { - + // 1. Reset the active div // 2. Get the root element from the cache and update it - Elem* elem = ctx.get_elem(ROOT_ID)!; + ctx.active_div = 0; + Elem* elem = ctx.get_elem(ROOT_ID, ETYPE_DIV)!; + ctx.active_div = elem.tree_idx; // The root should have the updated flag only if the size of the window - // was changed between frames, this propagates an element size recalculation + // was changed between frasmes, this propagates an element size recalculation // down the element tree elem.flags.updated = ctx.input.events.resize; // if the window has focus then the root element also has focus, no other @@ -235,28 +243,15 @@ fn void? Ctx.frame_begin(&ctx) // other stuff //elem.flags.has_focus = ctx.has_focus; - Elem def_root = { - .id = ROOT_ID, - .type = ETYPE_DIV, - .bounds = { - .w = ctx.width, - .h = ctx.height, - }, - .div = { - .layout = LAYOUT_ROW, - .z_index = 0, - .children_bounds = { - .w = ctx.width, - .h = ctx.height, - } - }, - .flags = elem.flags, - }; - - *elem = def_root; - - // 3. Push the root element into the element tree - ctx.active_div = ctx.tree.add(ROOT_ID, 0)!; + elem.bounds = {0, 0, ctx.width, ctx.height}; + elem.div.layout = LAYOUT_ROW; + elem.div.z_index = 0; + elem.div.children_bounds = elem.bounds; + elem.div.scroll_x.enabled = false; + elem.div.scroll_y.enabled = false; + elem.div.pcb = {}; + elem.div.origin_c = {}; + elem.div.origin_r = {}; ctx.div_scissor = {0, 0, ctx.width, ctx.height}; diff --git a/lib/ugui.c3l/src/ugui_div.c3 b/lib/ugui.c3l/src/ugui_div.c3 index f259096..ed02156 100644 --- a/lib/ugui.c3l/src/ugui_div.c3 +++ b/lib/ugui.c3l/src/ugui_div.c3 @@ -36,17 +36,9 @@ fn void? Ctx.div_begin_id(&ctx, Id id, Rect size, bool scroll_x, bool scroll_y) id = ctx.gen_id(id)!; Elem* parent = ctx.get_parent()!; - Elem* elem = ctx.get_elem(id)!; - isz div_node = ctx.tree.add(id, ctx.active_div)!; - ctx.active_div = div_node; + Elem* elem = ctx.get_elem(id, ETYPE_DIV)!; + ctx.active_div = elem.tree_idx; - bool is_new = elem.flags.is_new; - - if (elem.flags.is_new) { - elem.type = ETYPE_DIV; - } else if (elem.type != ETYPE_DIV) { - return WRONG_ELEMENT_TYPE?; - } elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_y.enabled = scroll_y; elem.div.z_index = parent.div.z_index + 1; diff --git a/lib/ugui.c3l/src/ugui_slider.c3 b/lib/ugui.c3l/src/ugui_slider.c3 index 7fb6d9a..55cd7fc 100644 --- a/lib/ugui.c3l/src/ugui_slider.c3 +++ b/lib/ugui.c3l/src/ugui_slider.c3 @@ -35,16 +35,7 @@ fn ElemEvents? Ctx.slider_hor_id(&ctx, id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - // 1. Fill the element fields - if (elem.flags.is_new) { - elem.type = ETYPE_SLIDER; - } else if (elem.type != ETYPE_SLIDER) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_SLIDER)!; // 2. Layout elem.bounds = ctx.position_element(parent, size, true); @@ -106,9 +97,7 @@ fn ElemEvents? Ctx.slider_ver_id(&ctx, id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; + Elem *elem = ctx.get_elem(id, ETYPE_SLIDER)!; // 1. Fill the element fields if (elem.flags.is_new) { diff --git a/lib/ugui.c3l/src/ugui_sprite.c3 b/lib/ugui.c3l/src/ugui_sprite.c3 index 216b1e2..0976281 100644 --- a/lib/ugui.c3l/src/ugui_sprite.c3 +++ b/lib/ugui.c3l/src/ugui_sprite.c3 @@ -111,22 +111,14 @@ fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t } -macro Ctx.draw_sprite(&ctx, String name, Point off = {0,0}, ...) - => ctx.draw_sprite_id(@compute_id($vasplat), name, off); -fn void? Ctx.draw_sprite_id(&ctx, Id id, String name, Point off) +macro Ctx.sprite(&ctx, String name, Point off = {0,0}, ...) + => ctx.sprite_id(@compute_id($vasplat), name, off); +fn void? Ctx.sprite_id(&ctx, Id id, String name, Point off) { id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - if (elem.flags.is_new) { - elem.type = ETYPE_SPRITE; - } else if (elem.type != ETYPE_SPRITE) { - return WRONG_ELEMENT_TYPE?; - } + Elem *elem = ctx.get_elem(id, ETYPE_SPRITE)!; Sprite* sprite = ctx.sprite_atlas.get(name)!; diff --git a/lib/ugui.c3l/src/ugui_text.c3 b/lib/ugui.c3l/src/ugui_text.c3 index dda6ce4..de15b33 100644 --- a/lib/ugui.c3l/src/ugui_text.c3 +++ b/lib/ugui.c3l/src/ugui_text.c3 @@ -14,13 +14,8 @@ fn void? Ctx.text_unbounded_id(&ctx, Id id, String text) id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; + Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!; - // 1. Fill the element fields - // this resets the flags - elem.type = ETYPE_TEXT; elem.text.str = text; // if the element is new or the parent was updated then redo layout @@ -39,13 +34,8 @@ fn ElemEvents? Ctx.text_box_id(&ctx, Id id, Rect size, char[] text, usz* text_le id = ctx.gen_id(id)!; Elem *parent = ctx.get_parent()!; - Elem *elem = ctx.get_elem(id)!; - // add it to the tree - ctx.tree.add(id, ctx.active_div)!; - - // 1. Fill the element fields - // this resets the flags - elem.type = ETYPE_TEXT; + Elem *elem = ctx.get_elem(id, ETYPE_TEXT)!; + elem.text.str = text; // layout the text box diff --git a/src/main.c3 b/src/main.c3 index 337cf81..0d55283 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -231,7 +231,7 @@ fn int main(String[] args) ui.checkbox("", {}, &check)!!; ui.toggle("", {}, &toggle)!!; }; - ui.draw_sprite("tux")!!; + ui.sprite("tux")!!; static char[128] text_box = "ciao mamma"; static usz text_len = "ciao mamma".len; From a390a8908c450e36ff6130580eeca806be5b7ee2 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 1 Jul 2025 16:48:30 +0200 Subject: [PATCH 134/250] changed Id to uint since builtin hash functions hash to uints --- lib/ugui.c3l/src/ugui_core.c3 | 2 +- src/renderer.c3 | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index d0354f8..ae5cd5d 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -9,7 +9,7 @@ import std::core::string; // element ids are just long ints -alias Id = usz; +alias Id = uint; enum ElemType { ETYPE_NONE, diff --git a/src/renderer.c3 b/src/renderer.c3 index 45e5cb6..eae3b8b 100644 --- a/src/renderer.c3 +++ b/src/renderer.c3 @@ -522,18 +522,18 @@ enum TextureType : (GPUTextureFormat format) { macro void Renderer.new_texture(&self, name_or_id, TextureType type, char[] pixels, uint width, uint height) { $switch $typeof(name_or_id): - $case usz: return self.new_texture_by_id(id, type, pixels, width, height); + $case uint: return self.new_texture_by_id(id, type, pixels, width, height); $case String: return self.new_texture_by_id(name_or_id.hash(), type, pixels, width, height); - $default: unreachable("texture must have a name (String) or an id (usz)"); + $default: unreachable("texture must have a name (String) or an id (uint)"); $endswitch } macro void Renderer.update_texture(&self, name_or_id, char[] pixels, uint width, uint height, uint x = 0, uint y = 0) { $switch $typeof(name_or_id): - $case usz: return self.update_texture_by_id(name_or_id, pixels, width, height, x, y); + $case uint: return self.update_texture_by_id(name_or_id, pixels, width, height, x, y); $case String: return self.update_texture_by_id(name_or_id.hash(), pixels, width, height, x, y); - $default: unreachable("texture must have a name (String) or an id (usz)"); + $default: unreachable("texture must have a name (String) or an id (uint)"); $endswitch } From 9fc1d90455c2117346a688cb20a6888891cc26a7 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Fri, 4 Jul 2025 11:17:42 +0200 Subject: [PATCH 135/250] simple style import with a subset of css --- lib/ugui.c3l/src/ugui_core.c3 | 29 +-- lib/ugui.c3l/src/ugui_shapes.c3 | 11 + lib/ugui.c3l/src/ugui_style.c3 | 402 ++++++++++++++++++++++++++++++++ src/main.c3 | 9 + 4 files changed, 430 insertions(+), 21 deletions(-) create mode 100644 lib/ugui.c3l/src/ugui_style.c3 diff --git a/lib/ugui.c3l/src/ugui_core.c3 b/lib/ugui.c3l/src/ugui_core.c3 index ae5cd5d..81c5ca6 100644 --- a/lib/ugui.c3l/src/ugui_core.c3 +++ b/lib/ugui.c3l/src/ugui_core.c3 @@ -6,6 +6,7 @@ import fifo; import std::io; import std::core::string; +import std::core::mem::allocator; // element ids are just long ints @@ -74,25 +75,14 @@ const uint MAX_CMDS = 2048; const uint ROOT_ID = 1; const uint TEXT_MAX = 64; -// global style, similar to the css box model -struct Style { // css box model - Rect padding; - Rect border; - Rect margin; - Color bgcolor; // background color - Color fgcolor; // foreground color - Color brcolor; // border color - ushort radius; -} - - struct Ctx { IdTree tree; ElemCache cache; CmdQueue cmd_queue; + StyleMap styles; // total size in pixels of the context ushort width, height; - Style style; + Style* style; // TODO: rename to "active_style" or something Font font; SpriteAtlas sprite_atlas; @@ -206,16 +196,12 @@ fn void? Ctx.init(&ctx) ctx.cmd_queue.init(MAX_CMDS)!; defer catch { (void)ctx.cmd_queue.free(); } + ctx.styles.init(allocator::heap()); + defer catch { ctx.styles.free(); } + ctx.active_div = 0; - // TODO: add style config - ctx.style.margin = {2, 2, 2, 2}; - ctx.style.border = {2, 2, 2, 2}; - ctx.style.padding = {1, 1, 1, 1}; - ctx.style.radius = 12; - ctx.style.bgcolor = 0x282828ffu.to_rgba(); - ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba(); - ctx.style.brcolor = 0xd79921ffu.to_rgba(); + ctx.style = &DEFAULT_STYLE; } fn void Ctx.free(&ctx) @@ -225,6 +211,7 @@ fn void Ctx.free(&ctx) (void)ctx.cmd_queue.free(); (void)ctx.font.free(); (void)ctx.sprite_atlas.free(); + (void)ctx.styles.free(); } fn void? Ctx.frame_begin(&ctx) diff --git a/lib/ugui.c3l/src/ugui_shapes.c3 b/lib/ugui.c3l/src/ugui_shapes.c3 index 1bfb00a..9e778b7 100644 --- a/lib/ugui.c3l/src/ugui_shapes.c3 +++ b/lib/ugui.c3l/src/ugui_shapes.c3 @@ -203,6 +203,17 @@ macro Color uint.to_rgba(u) }; } + +macro Color uint.@to_rgba($u) +{ + return { + .r = (char)(($u >> 24) & 0xff), + .g = (char)(($u >> 16) & 0xff), + .b = (char)(($u >> 8) & 0xff), + .a = (char)(($u >> 0) & 0xff) + }; +} + macro uint Color.to_uint(c) { uint u = c.r | (c.g << 8) | (c.b << 16) | (c.a << 24); diff --git a/lib/ugui.c3l/src/ugui_style.c3 b/lib/ugui.c3l/src/ugui_style.c3 new file mode 100644 index 0000000..3899edb --- /dev/null +++ b/lib/ugui.c3l/src/ugui_style.c3 @@ -0,0 +1,402 @@ +module ugui; + +import std::collections::map; +import std::core::mem::allocator; +import std::io; + +// global style, similar to the css box model +struct Style { // css box model + Rect padding; + Rect border; + Rect margin; + Color bgcolor; // background color + Color fgcolor; // foreground color + Color brcolor; // border color + ushort radius; +} + +const Style DEFAULT_STYLE = { + .margin = {2, 2, 2, 2}, + .border = {2, 2, 2, 2}, + .padding = {1, 1, 1, 1}, + .bgcolor = 0x282828ffu.@to_rgba(), + .fgcolor = 0xfbf1c7ffu.@to_rgba(), + .brcolor = 0xd79921ffu.@to_rgba(), + .radius = 12, +}; + +// style is stored in a hashmap, each style has an Id that can be generated by a string or whatever +alias StyleMap = map::HashMap{Id, Style}; + +// push or update a new style into the map +fn void StyleMap.register_style(&map, Style* style, Id id) +{ + if (style == null) return; + map.set(id, *style); +} + +// get a style from the map, if the style is not found then use a default style. +fn Style* StyleMap.get_style(&map, Id id) +{ + Style*? s = map.get_ref(id); + if (catch e = s) { + return &DEFAULT_STYLE; + } + return s; +} + +fn int StyleMap.import_style_string(&map, String text) +{ + Parser p; + p.lex.text = text; + int added; + + while (p.parse_style() == true) { + added++; + map.register_style(&p.style, p.style_id); + if (p.lex.peep_token().type == EOF) break; + } + + return added; +} + +fn int Ctx.import_style_from_string(&ctx, String text) => ctx.styles.import_style_string(text); +fn int Ctx.import_style_from_file(&ctx, String path) +{ + char[] text; + usz size = file::get_size(path)!!; + text = mem::new_array(char, size); + file::load_buffer(path, text)!!; + defer mem::free(text); + + int added = ctx.import_style_from_string((String)text); + return added; +} + + +/* + * Style can be serialized and deserialized with a subset of CSS + *