Compare commits

..

No commits in common. 'c3' and 'master' have entirely different histories.
c3 ... master

  1. 8
      .gitignore
  2. 3
      .gitmodules
  3. 184
      ARCHITECTURE.md
  4. 0
      LICENSE
  5. 0
      README.md
  6. 4
      RENDERER
  7. 76
      TODO
  8. 40
      def_style.h
  9. 0
      docs/.gitkeep
  10. 3
      font-to-atlas/.gitignore
  11. 2
      font-to-atlas/Makefile
  12. 118
      font-to-atlas/ff.c
  13. 25
      font-to-atlas/ff.h
  14. 107
      font-to-atlas/main.c
  15. BIN
      font-to-atlas/monospace.ttf
  16. 5077
      font-to-atlas/stb_truetype.h
  17. 40
      input.c
  18. 0
      lib/.gitkeep
  19. 3
      lib/libgrapheme.c3l/Makefile
  20. 46
      lib/libgrapheme.c3l/libgrapheme.c3i
  21. 9
      lib/libgrapheme.c3l/manifest.json
  22. 44
      lib/libgrapheme.c3l/project.json
  23. 1
      lib/libgrapheme.c3l/thirdparty/libgrapheme
  24. 6
      lib/libmqoi.c3l/Makefile
  25. 122
      lib/libmqoi.c3l/libmqoi.c3i
  26. 9
      lib/libmqoi.c3l/manifest.json
  27. 31
      lib/libmqoi.c3l/project.json
  28. 1
      lib/libmqoi.c3l/thirdparty/mini-qoi
  29. 3
      lib/libschrift.c3l/Makefile
  30. 58
      lib/libschrift.c3l/libschrift.c3
  31. BIN
      lib/libschrift.c3l/linux-x64/libschrift.a
  32. 9
      lib/libschrift.c3l/manifest.json
  33. 45
      lib/libschrift.c3l/project.json
  34. 1
      lib/libschrift.c3l/thirdparty/libschrift
  35. 2
      opengl-ren/.gitignore
  36. 5
      opengl-ren/Makefile
  37. 2
      opengl-ren/README
  38. BIN
      opengl-ren/charmap.ff
  39. BIN
      opengl-ren/charmap.png
  40. 12
      opengl-ren/fragment.glsl
  41. 521
      opengl-ren/main.c
  42. 17
      opengl-ren/vertex.glsl
  43. 2
      opengl/.gitignore
  44. 5
      opengl/Makefile
  45. 2
      opengl/README
  46. 10
      opengl/fragment.glsl
  47. 278
      opengl/main.c
  48. 14
      opengl/vertex.glsl
  49. 55
      project.json
  50. 0
      resources/.gitkeep
  51. BIN
      resources/hack-nerd.ttf
  52. BIN
      resources/tux.qoi
  53. 126
      rewrite/generic_stack.h
  54. 0
      scripts/.gitkeep
  55. 0
      src/.gitkeep
  56. 146
      src/cache.c3
  57. 49
      src/fifo.c3
  58. 353
      src/main.c3
  59. 102
      src/ugui_atlas.c3
  60. 85
      src/ugui_button.c3
  61. 192
      src/ugui_cmd.c3
  62. 345
      src/ugui_core.c3
  63. 152
      src/ugui_div.c3
  64. 229
      src/ugui_font.c3
  65. 189
      src/ugui_input.c3
  66. 198
      src/ugui_layout.c3
  67. 204
      src/ugui_shapes.c3
  68. 135
      src/ugui_slider.c3
  69. 154
      src/ugui_sprite.c3
  70. 30
      src/ugui_text.c3
  71. 342
      src/vtree.c3
  72. 0
      test/.gitkeep
  73. 11
      test/Makefile
  74. 268
      test/main.c
  75. BIN
      test/monospace.ttf
  76. 623
      test/stb_rect_pack.h
  77. 5077
      test/stb_truetype.h
  78. 212
      test/stbttf.h
  79. 14
      test/test_bitsruct.c3
  80. 10
      test/test_bittype.c3
  81. 19
      test/test_color.c3
  82. 14
      test/test_custom_hash.c3
  83. 72
      test/test_error.c3
  84. 6
      test/test_font.c3
  85. 26
      test/test_keyboard.c3
  86. 26
      test/test_union.c3
  87. 7
      test/test_vtree.c3
  88. 4
      text_rendering/.gitignore
  89. 18
      text_rendering/Makefile
  90. 8
      text_rendering/box_fragshader.glsl
  91. 19
      text_rendering/box_vertshader.glsl
  92. 81
      text_rendering/font.c
  93. 50
      text_rendering/font.h
  94. 18
      text_rendering/font_fragshader.glsl
  95. 26
      text_rendering/font_vertshader.glsl
  96. 126
      text_rendering/generic_cache.h
  97. 134
      text_rendering/generic_hash.h
  98. 118
      text_rendering/generic_stack.h
  99. 46
      text_rendering/genmake.sh
  100. 96
      text_rendering/main.c
  101. Some files were not shown because too many files have changed in this diff Show More

8
.gitignore vendored

@ -1,5 +1,5 @@
microgui
*.o *.o
*.a test/test
build/* **/compile_commands.json
**/.ccls-cache **/.cache
perf.data*

3
.gitmodules vendored

@ -1,3 +0,0 @@
[submodule "lib/raylib.c3l"]
path = lib/raylib.c3l
url = https://github.com/NexushasTaken/raylib.c3l

@ -1,184 +0,0 @@
## 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() |
+--------------+
```
### Layouting
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.
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
- 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
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
```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
}
```
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

@ -0,0 +1,4 @@
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

76
TODO

@ -1,76 +0,0 @@
# TODOs, semi-random sorting
[x] Implement glyph draw command
[x] Implement div.view and scrollbars
[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)
[ ] 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
[ ] Resizeable divs
[ ] 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
[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
[ ] 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
[ ] Text reflow
[x] 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
[x] rect commads should have:
_ border width
_ border radius
[x] add a command to update an atlas
[ ] New window command, useful for popups
## Atlases
[ ] Add an interface to create, destroy, update and get atlases based on their ids
[ ] Implement multiple font atlases
## Fonts
[x] Fix the missing alpha channel
[x] Fix the alignment
## Raylib
[x] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
[x] Implement pixel radius rounding for border radius
## Widgets
[x] Dynamic text box to implement an fps counter
[x] Button with label
[ ] Text Input box
[ ] Icon Buttons
[ ] Switch
[ ] Checkbox

@ -0,0 +1,40 @@
#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

@ -0,0 +1,3 @@
*.ff
*.png
font-to-atlas

@ -0,0 +1,2 @@
font-to-atlas: main.c ff.c ff.h
cc -lm -g main.c ff.c -o font-to-atlas

@ -0,0 +1,118 @@
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#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;
}

@ -0,0 +1,25 @@
#ifndef _FARBFELD_EZ_H
#define _FARBFELD_EZ_H
#include <stdint.h>
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

@ -0,0 +1,107 @@
#define _POSIX_C_SOURCE 200809l
#define STB_TRUETYPE_IMPLEMENTATION
#define STBTT_STATIC
#include <sys/mman.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#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;
}

Binary file not shown.

File diff suppressed because it is too large Load Diff

@ -0,0 +1,40 @@
#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;
}

@ -1,3 +0,0 @@
all:
make -C thirdparty/libgrapheme
cp thirdparty/libgrapheme/libgrapheme.a linux-x64/libgrapheme.a

@ -1,46 +0,0 @@
module grapheme;
const uint GRAPHEME_INVALID_CODEPOINT = 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");

@ -1,9 +0,0 @@
{
"provides": "grapheme",
"targets": {
"linux-x64": {
"dependencies": [],
"linked-libraries": ["grapheme", "c"]
}
}
}

@ -1,44 +0,0 @@
{
// 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 <alemauri001@gmail.com"],
// Version using semantic versioning.
"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.
"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.
}

@ -1 +0,0 @@
Subproject commit 65b354f0fcb1d925f4340dbb4415ea06e8af2bec

@ -1,6 +0,0 @@
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

@ -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");

@ -1,9 +0,0 @@
{
"provides" : "mqoi",
"targets" : {
"linux-x64" : {
"dependencies" : [],
"linked-libraries" : ["mqoi", "c"]
}
}
}

@ -1,31 +0,0 @@
{
// 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 <alemauri001@gmail.com" ],
// Version using semantic versioning.
"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": [],
// 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.
}

@ -1 +0,0 @@
Subproject commit f8b5a8a4d2eeee68b52e143060b7412ba78b5077

@ -1,3 +0,0 @@
all:
make -C thirdparty/libschrift
cp thirdparty/libschrift/libschrift.a linux-x64/libschrift.a

@ -1,58 +0,0 @@
module schrift;
def SftFont = void*;
def SftUChar = uint;
def SftGlyph = uint;
const int SFT_DOWNWARD_Y = 0x01;
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 ZString sft_version() @extern("sft_version");
extern fn SftFont loadmem(void* mem, usz size) @extern("sft_loadmem");
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");
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");

@ -1,9 +0,0 @@
{
"provides" : "schrift",
"targets" : {
"linux-x64" : {
"dependencies" : [],
"linked-libraries" : ["schrift", "c"]
}
}
}

@ -1,45 +0,0 @@
{
// 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 <alemauri001@gmail.com" ],
// Version using semantic versioning.
"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.
"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.
}

@ -1 +0,0 @@
Subproject commit 24737d2922b23df4a5692014f5ba03da0c296112

@ -0,0 +1,2 @@
a.out
gl

@ -0,0 +1,5 @@
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
LDFLAGS = -lSDL2 -lGLEW -lGL
gl: main.c
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl

@ -0,0 +1,2 @@
since I've never worked with opengl I made this folder as a little test environment
for an SDL-OpenGL renderer

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 862 B

@ -0,0 +1,12 @@
#version 330
flat in vec4 out_color;
in vec2 texture_coord;
uniform sampler2D texture_sampler;
out vec4 color;
void main()
{
color = texture(texture_sampler, texture_coord) + out_color;
}

@ -0,0 +1,521 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL2/SDL_opengl.h>
#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;
}

@ -0,0 +1,17 @@
#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;
}

2
opengl/.gitignore vendored

@ -0,0 +1,2 @@
a.out
gl

@ -0,0 +1,5 @@
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
LDFLAGS = -lSDL2 -lGLEW -lGL
gl: main.c
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl

@ -0,0 +1,2 @@
since I've never worked with opengl I made this folder as a little test environment
for an SDL-OpenGL renderer

@ -0,0 +1,10 @@
#version 330
smooth in vec4 out_color;
out vec4 color;
void main()
{
// set the color for each vertex to white
color = out_color;
}

@ -0,0 +1,278 @@
#define _POSIX_C_SOURCE 200809l
#include <sys/mman.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <err.h>
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL2/SDL_opengl.h>
#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;
}

@ -0,0 +1,14 @@
#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;
}

@ -1,55 +0,0 @@
{
// 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.
"authors": ["Alessandro Mauri <ale@shitposting.expert>"],
// 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.
}

Binary file not shown.

Binary file not shown.

@ -0,0 +1,126 @@
#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

@ -1,146 +0,0 @@
module cache(<Key, Value, SIZE>);
/* 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(<SIZE>) @private;
def IdTable = map::HashMap(<Key, usz>) @private;
def IdTableEntry = map::Entry(<Key, usz>) @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.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);
}
fn Value*! Cache.search(&cache, Key id)
{
// get_entry() faults on miss
IdTableEntry* entry = cache.table.get_entry(id)!;
/* MISS, wrong key */
if (entry.key != id) {
cache.table.remove(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]);
}
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
{
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;
}
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;
}
}

@ -1,49 +0,0 @@
module fifo(<Type>);
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;
}

@ -1,353 +0,0 @@
import std::io;
import vtree;
import cache;
import ugui;
import raylib5::rl;
import std::time;
import std::collections::ringbuffer;
import std::core::string;
def Times = ringbuffer::RingBuffer(<time::NanoDuration, 128>);
fn void Times.print_stats(&times)
{
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);
}
struct TimeStats {
time::NanoDuration min, max, avg;
}
fn TimeStats Times.get_stats(&times)
{
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
// 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;
}
`;
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;
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")!!;
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;
time::Clock clock;
Times ui_times;
Times draw_times;
// 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");
ugui::Id sprite_id = ui.get_sprite_atlas_id("icons");
// Main loop
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);
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 */
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;
// main div, fill the whole window
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) {
io::printn("press button0");
toggle = !toggle;
}
//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");
}
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à ⚡\n'\udb80\udd2c'")!!;
ui.layout_next_column()!!;
ui.button_label("Continua!")!!;
|};
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", ugui::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})!!;
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.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()!!;
// 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-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()!!;
/* 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 ugui::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:
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 ugui::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) {
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());
} 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::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);
}
}
draw_times.push(clock.mark());
//draw_times.print_stats();
rl::endDrawing();
/* End Drawing */
}
rl::closeWindow();
return 0;
}
/*
fn void! test_vtree() @test
{
vtree::VTree(<String>) 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(<int, String, 256>);
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");
}
*/

@ -1,102 +0,0 @@
module ugui;
import std::io;
fault UgAtlasError {
CANNOT_PLACE,
INVALID_TYPE,
}
enum AtlasType {
ATLAS_GRAYSCALE,
ATLAS_RGBA32,
}
// 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;
case ATLAS_RGBA32: return 4;
}
}
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);
}
/*
* 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, ushort stride)
{
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?;
}
}
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*bpp + (p.x+x)*bpp ..];
char[] pix = pixels[(usz)y*stride*bpp + x*bpp ..];
buf[0..bpp-1] = pix[0..bpp-1];
}
}
atlas.row.x += w;
if (h > atlas.row_h) {
atlas.row_h = h;
}
return p;
}

@ -1,85 +0,0 @@
module ugui;
import std::io;
// button element
struct ElemButton {
int filler;
}
// draw a button, return the events on that button
fn ElemEvents! Ctx.button(&ctx, String label, Rect size, 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 UgError.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{}; }
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)!;
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 = text_size.add(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 = 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
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;
}

@ -1,192 +0,0 @@
module ugui;
import std::ascii;
// command type
enum CmdType {
CMD_RECT,
CMD_UPDATE_ATLAS,
CMD_SPRITE,
CMD_SCISSOR,
}
// command to draw a rect
struct CmdRect {
Rect rect;
ushort radius;
Color color;
}
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;
Color hue;
Id texture_id;
}
// if rect is zero Rect{0} then reset the scissor
struct CmdScissor {
Rect rect;
}
// command structure
struct Cmd {
CmdType type;
union {
CmdRect rect;
CmdUpdateAtlas update_atlas;
CmdSprite sprite;
CmdScissor scissor;
}
}
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)
{
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.push_cmd(&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,
};
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, Color hue = 0xffffffffu.to_rgba())
{
Cmd cmd = {
.type = CMD_SPRITE,
.sprite.rect = bounds,
.sprite.texture_rect = texture,
.sprite.texture_id = texture_id,
.sprite.hue = hue,
};
ctx.push_cmd(&cmd)!;
}
fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba())
{
if (text.len == 0) {
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;
Id texture_id = ctx.font.id; // or ctx.font.atlas.id
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,
};
// push the sprite only if it collides with the bounds
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;
line_len = 0;
} else {
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)
{
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.push_cmd(&up)!;
}
fn void! Ctx.push_scissor(&ctx, Rect rect)
{
Cmd sc = {
.type = CMD_SCISSOR,
.scissor.rect = rect.intersection(ctx.div_scissor),
};
ctx.push_cmd(&sc)!;
}

@ -1,345 +0,0 @@
module ugui;
import vtree;
import cache;
import fifo;
import std::io;
import std::core::string;
// element ids are just long ints
def Id = usz;
enum ElemType {
ETYPE_NONE,
ETYPE_DIV,
ETYPE_BUTTON,
ETYPE_SLIDER,
ETYPE_TEXT,
ETYPE_SPRITE,
}
bitstruct ElemFlags : uint {
bool updated : 0;
bool is_new : 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;
bool update : 7;
}
// element structure
struct Elem {
Id id;
ElemFlags flags;
ElemEvents events;
Rect bounds;
ElemType type;
union {
ElemDiv div;
ElemButton button;
ElemSlider slider;
ElemText text;
ElemSprite sprite;
}
}
// relationships between elements are stored in a tree, it stores just the ids
def IdTree = vtree::VTree(<Id>) @private;
// elements themselves are kept in a cache
const uint MAX_ELEMENTS = 256;
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
def CmdQueue = fifo::Fifo(<Cmd>);
fault UgError {
INVALID_SIZE,
EVENT_UNSUPPORTED,
UNEXPECTED_ELEMENT,
WRONG_ELEMENT_TYPE,
}
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;
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;
// total size in pixels of the context
ushort width, height;
Style style;
Font font;
SpriteAtlas sprite_atlas;
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;
// scroll wheel
Point scroll;
}
struct keyboard {
char[TEXT_MAX] text;
usz text_len;
ModKeys down;
}
}
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
}
// return a pointer to the parent of the current active div
fn Elem*! Ctx.get_parent(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
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 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
fn Elem*! Ctx.get_elem(&ctx, Id id)
{
Elem empty_elem;
bool is_new;
Elem* elem;
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;
}
// 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
// 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
{
Id id = ctx.tree.get(ctx.active_div)!;
return ctx.cache.search(id);
}
fn void! Ctx.init(&ctx)
{
ctx.tree.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.tree.free(); }
ctx.cache.init()!;
defer catch { (void)ctx.cache.free(); }
ctx.cmd_queue.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.cmd_queue.free(); }
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.radius = 5;
ctx.style.bgcolor = 0x282828ffu.to_rgba();
ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba();
ctx.style.brcolor = 0xd79921ffu.to_rgba();
}
fn void Ctx.free(&ctx)
{
(void)ctx.tree.free();
(void)ctx.cache.free();
(void)ctx.cmd_queue.free();
(void)ctx.font.free();
(void)ctx.sprite_atlas.free();
}
fn void! Ctx.frame_begin(&ctx)
{
// 2. Get the root element from the cache and update it
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
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
//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,
.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)!;
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
}
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.nuke();
// 2. clear input fields
ctx.input.events = (InputEvents)0;
ctx.input.keyboard.text_len = 0;
// send atlas updates
if (ctx.font.should_update) {
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
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 = 0xff00ffffu.to_rgba()
};
ctx.cmd_queue.enqueue(&cmd)!;
$endif
}
<*
* @ensure elem != null
*>
macro bool Ctx.is_hovered(&ctx, Elem *elem)
{
return ctx.input.mouse.pos.in_rect(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
// FIXME: hacked together, please do better
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;
}
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;
}

@ -1,152 +0,0 @@
module ugui;
import std::io;
import std::math;
const short SCROLLBAR_DIM = 5;
// div element
struct ElemDiv {
Layout layout;
struct scroll_x {
bool enabled;
bool on;
float value;
}
struct scroll_y {
bool enabled;
bool on;
float value;
}
Rect children_bounds; // current frame children bounds
Rect pcb; // previous frame children bounds
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)!;
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;
bool is_new = elem.flags.is_new;
if (elem.flags.is_new) {
elem.type = ETYPE_DIV;
} else if (elem.type != ETYPE_DIV) {
return UgError.WRONG_ELEMENT_TYPE?;
}
elem.div.scroll_x.enabled = scroll_x;
elem.div.scroll_y.enabled = scroll_y;
// 2. layout the element
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
ctx.div_scissor = elem.bounds;
ctx.push_scissor(elem.bounds)!;
// 4. Fill the div fields
elem.div.origin_c = Point{
.x = elem.bounds.x,
.y = elem.bounds.y,
};
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(elem.bounds, ctx.style.bgcolor, do_border: do_border)!;
elem.events = ctx.get_elem_events(elem);
// TODO: check active
// TODO: check resizeable
}
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.div.pcb = elem.div.children_bounds;
// 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 = {
.x = cb.x + cb.w,
.y = cb.y + cb.h,
};
// div bounds bottom-right corner
Point bc = {
.x = elem.bounds.x + elem.bounds.w,
.y = elem.bounds.y + elem.bounds.h,
};
// set the scrollbar flag, is used in layout
// horizontal overflow
elem.div.scroll_x.on = cbc.x > bc.x && elem.div.scroll_x.enabled;
// 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")!;
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,
.w = wdim,
.h = elem.bounds.h - hdim,
};
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 (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,
.w = elem.bounds.w - wdim,
.h = hdim,
};
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
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
}

@ -1,229 +0,0 @@
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;
/* width and height of a glyph contain the kering advance
* (u,v)
* +-------------*---+ -
* | ^ | | ^
* | |oy | | |
* | v | | |
* | .ii. | | |
* | @@@@@@. | | |
* | V@Mio@@o | | |
* | :i. V@V | | h
* | :oM@@M | | |
* | :@@@MM@M | | |
* | @@o o@M | | |
* |<->:@@. M@M | | |
* |ox @@@o@@@@ | | |
* | :M@@V:@@.| | v
* +-------------*---+ -
* |<---- w ---->|
* |<------ adv ---->|
*/
struct Glyph {
Codepoint code;
ushort u, v;
ushort w, h;
short adv, ox, oy;
}
const uint FONT_CACHED = 128;
def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private;
fault UgFontError {
TTF_LOAD_FAILED,
MISSING_GLYPH,
BAD_GLYPH_METRICS,
RENDER_ERROR,
}
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
Atlas atlas;
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)
{
font.table.new_init(capacity: FONT_CACHED);
font.id = name.hash();
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) {
font.table.free();
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;
//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'
ushort size = (ushort)font.size*(ushort)($$sqrt((float)FONT_CACHED));
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)
{
Glyph*! gp;
gp = font.table.get_ref(code);
if (catch excuse = gp) {
if (excuse != SearchResult.MISSING) {
return excuse?;
}
} else {
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;
//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, (ushort)img.width)!;
glyph.u = uv.x;
glyph.v = uv.y;
mem::free(pixels);
font.table.set(code, glyph);
font.should_update = true;
return font.table.get_ref(code);
}
fn void Font.free(&font)
{
font.atlas.free();
font.table.free();
schrift::freefont(font.sft.font);
}
fn void! Ctx.load_font(&ctx, String name, ZString 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;
}
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();
}

@ -1,189 +0,0 @@
module ugui;
import grapheme;
import std::io;
import std::math;
import std::core::string;
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
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);
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: keys) {
needle[0] = c;
is_keys = is_keys && haystack.contains((String)needle[..]);
}
return is_mod && is_keys;
}
// 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;
}
// 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
ctx.input.events.change_focus = ctx.has_focus != has_focus;
ctx.has_focus = has_focus;
}
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;
// 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 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 = (uint)ctx.input.mouse.down != 0 || (uint)ctx.input.mouse.updated != 0;
}
// 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);
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 = dx != 0 || dy != 0;
}
// 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 = math::clamp(mx, 0u16, ctx.width);
ctx.input.mouse.pos.y = math::clamp(my, 0u16, ctx.height);
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, char[] text)
{
if (text.ptr == null || 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;
}

@ -1,198 +0,0 @@
module ugui;
enum Layout {
LAYOUT_ROW,
LAYOUT_COLUMN,
LAYOUT_FLOATING,
LAYOUT_ABSOLUTE,
}
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.bounds.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.bounds.y,
};
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
// style: apply style
<*
@require ctx != null
@require parent.type == ETYPE_DIV
*>
fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
{
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;
case LAYOUT_COLUMN:
origin = div.origin_c;
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{};
}
// 2. Compute the parent's view
parent_bounds = parent.bounds;
parent_view = parent.get_view();
// 3. Compute the placement and occupied area
// 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;
// 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 padding = ctx.style.padding;
// 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());
// 4. Update the parent's origin
div.origin_r = Point{
.x = child_occupied.bottom_right().x,
.y = origin.y,
};
div.origin_c = Point{
.x = origin.x,
.y = child_occupied.bottom_right().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;
}
// 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;
}
}
// 99. return the placement
if (child_placement.collides(parent_view)) {
return child_placement.off(parent.get_view_off().neg());
} else {
return Rect{};
}
}

@ -1,204 +0,0 @@
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;
}
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)
};
}

@ -1,135 +0,0 @@
module ugui;
import std::io;
import std::math;
// slider element
struct ElemSlider {
Rect handle;
}
/* handle
* +----+-----+---------------------+
* | |#####| |
* +----+-----+---------------------+
*/
<*
@require value != null
*>
fn ElemEvents! Ctx.slider_hor(&ctx,
String label,
Rect size,
float* value,
float hpercent = 0.25,
Color bgcolor = 0x0000ffffu.to_rgba(),
Color handlecolor = 0x0ff000ffu.to_rgba())
{
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
if (elem.flags.is_new) {
elem.type = ETYPE_SLIDER;
} else if (elem.type != ETYPE_SLIDER) {
return UgError.WRONG_ELEMENT_TYPE?;
}
// 2. Layout
elem.bounds = ctx.position_element(parent, size, true);
// handle width
short hw = (short)(elem.bounds.w * hpercent);
Rect handle = {
.x = calc_slider(elem.bounds.x, elem.bounds.w-hw, *value),
.y = elem.bounds.y,
.w = hw,
.h = elem.bounds.h,
};
elem.slider.handle = handle;
Point m = ctx.input.mouse.pos;
elem.events = ctx.get_elem_events(elem);
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;
}
// Draw the slider background and handle
ctx.push_rect(elem.bounds, bgcolor)!;
ctx.push_rect(elem.slider.handle, handlecolor)!;
return elem.events;
}
/*
* +-+
* | |
* | |
* +-+
* |#| handle
* |#|
* +-+
* | |
* | |
* +-+
*/
fn ElemEvents! Ctx.slider_ver(&ctx,
String label,
Rect size,
float* value,
float hpercent = 0.25,
Color bgcolor = 0x0000ffffu.to_rgba(),
Color handlecolor = 0x0ff000ffu.to_rgba())
{
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
if (elem.flags.is_new) {
elem.type = ETYPE_SLIDER;
} else if (elem.type != ETYPE_SLIDER) {
return UgError.WRONG_ELEMENT_TYPE?;
}
// 2. Layout
elem.bounds = ctx.position_element(parent, size, true);
// handle height
short hh = (short)(elem.bounds.h * hpercent);
Rect handle = {
.x = elem.bounds.x,
.y = calc_slider(elem.bounds.y, elem.bounds.h-hh, *value),
.w = elem.bounds.w,
.h = hh,
};
elem.slider.handle = handle;
Point m = ctx.input.mouse.pos;
elem.events = ctx.get_elem_events(elem);
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;
}
// Draw the slider background and handle
ctx.push_rect(elem.bounds, bgcolor)!;
ctx.push_rect(elem.slider.handle, handlecolor)!;
return elem.events;
}
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);

@ -1,154 +0,0 @@
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(<Id, Sprite>);
struct SpriteAtlas {
Id id;
Atlas atlas;
SpriteMap sprites;
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)
{
// 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)!;
}
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 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(elem.bounds, uv, tex_id)!;
}

@ -1,30 +0,0 @@
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)!;
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;
// 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);
if (elem.bounds.is_null()) { return; }
ctx.push_string(elem.bounds, text)!;
}

@ -1,342 +0,0 @@
module vtree(<ElemType>);
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{};
$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;
}
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)
{
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("}");
}
}

@ -0,0 +1,11 @@
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

@ -0,0 +1,268 @@
#define _POSIX_C_SOURCE 200809l
#include <stdio.h>
#include <SDL2/SDL.h>
#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();
}

Binary file not shown.

@ -0,0 +1,623 @@
// 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 <stdlib.h>
#define STBRP_SORT qsort
#endif
#ifndef STBRP_ASSERT
#include <assert.h>
#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.
------------------------------------------------------------------------------
*/

File diff suppressed because it is too large Load Diff

@ -0,0 +1,212 @@
#ifndef __STBTTF_H__
#define __STBTTF_H__
#include <SDL2/SDL.h>
#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 <stdio.h>
#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 <font>\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

@ -1,14 +0,0 @@
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;
}

@ -1,10 +0,0 @@
import std::io;
import std::collections::bitset;
def Bits = bitset::BitSet(<128>);
fn void main()
{
Bits b;
io::printn($typeof(b.data[0]).sizeof);
}

@ -1,19 +0,0 @@
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());
}

@ -1,14 +0,0 @@
import std::collections::map;
def Codepoint = uint;
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();
def CodeMap = map::HashMap(<Codepoint, Codepoint>);
fn int main()
{
CodeMap m;
m.new_init();
m.free();
return 0;
}

@ -1,72 +0,0 @@
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);
}

@ -1,6 +0,0 @@
import rl;
fn int main(void)
{
return 0;
}

@ -1,26 +0,0 @@
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;
}

@ -1,26 +0,0 @@
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;
}

@ -1,7 +0,0 @@
import std::io;
import vtree;
fn int main()
{
return 0;
}

@ -0,0 +1,4 @@
*.png
test
obj/**
objlist

@ -0,0 +1,18 @@
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

@ -0,0 +1,8 @@
#version 330 core
in vec4 col;
void main()
{
gl_FragColor = col;
}

@ -0,0 +1,19 @@
#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;
}

@ -1,72 +1,41 @@
module ugui; #define _POSIX_C_SOURCE 200809l
#define STB_TRUETYPE_IMPLEMENTATION
import cache; #define STBTT_STATIC
//#include <grapheme.h> #define STB_IMAGE_WRITE_IMPLEMENTATION
//#include <assert.h>
//#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(<Codepoint, Glyph, 1024>); #include <grapheme.h>
#include <assert.h>
// identity map the ASCII range #include "font.h"
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash(); #include "stb_truetype.h"
#include "stb_image_write.h"
#include "util.h"
struct FontAtlas { #include "generic_cache.h"
uint width, height; static inline unsigned int hash(unsigned int code)
char* atlas; {
uint glyph_max_w, glyph_max_h; // identity map the ascii range
int size; return code < 128 ? code : hash_u32(code);
int file_size;
char *file;
void *priv;
} }
CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32)
macro is_utf8(char c) => c & 0x80; #define UTF8(c) (c&0x80)
const uint BDEPTH = 1; #define BDEPTH 1
const uint BORDER = 4; #define BORDER 4
// FIXME: as of now only monospaced fonts look decent since no // FIXME: as of now only monospaced fonts work correctly since no kerning information
// kerning information is stored // is stored
struct Priv @private {
struct priv {
stbtt_fontinfo stb; stbtt_fontinfo stb;
float scale; float scale;
int baseline; int baseline;
unsigned char *bitmap; unsigned char *bitmap;
struct cache c; struct cache c;
} };
//#define PRIV(x) ((struct priv *)x->priv) #define PRIV(x) ((struct priv *)x->priv)
struct font_atlas * font_init(void) struct font_atlas * font_init(void)

@ -0,0 +1,50 @@
#ifndef _FONT_H
#define _FONT_H
#include <stdint.h>
/* 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

@ -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));
}

@ -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);
}

@ -0,0 +1,126 @@
#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

@ -0,0 +1,134 @@
#ifndef _HASH_GENERIC
#define _HASH_GENERIC
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#define HASH_MAXSIZE 4096
// for fibonacci hashing, 2^{32,64}/<golden ratio>
#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

@ -0,0 +1,118 @@
#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

@ -0,0 +1,46 @@
#!/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

@ -0,0 +1,96 @@
#include <SDL2/SDL.h>
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_video.h>
#include <stdlib.h>
#include <locale.h>
#include <stdio.h>
#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;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save