Compare commits
50 Commits
master
...
font_atlas
Author | SHA1 | Date | |
---|---|---|---|
fa4d3ed0ec | |||
5a89e9ec7d | |||
0db858e814 | |||
8cf3881b6b | |||
373243d138 | |||
3070fac9f5 | |||
5c687bd24e | |||
c880c2b26e | |||
089140e1ed | |||
fb177c03f7 | |||
328cac871a | |||
f0aa59ef0b | |||
61556d0a2c | |||
2356d165fe | |||
dbe70eb4f4 | |||
f86a360f39 | |||
7e18c7a316 | |||
537acd4765 | |||
d5bea68058 | |||
574a1f23dc | |||
f8e2c0b70c | |||
9a785e0f06 | |||
bb1745a05d | |||
04dff26067 | |||
fa3362cc66 | |||
73bc933eb5 | |||
763e9ba8d6 | |||
250a0fb3b5 | |||
8bc38452b3 | |||
1cad13e597 | |||
28598f0575 | |||
f48151b38e | |||
39e78ea078 | |||
2dcc1b582c | |||
a374a37971 | |||
5427b191c2 | |||
3a8a55d177 | |||
a4974c8df8 | |||
97295df516 | |||
305df93182 | |||
d6358944ac | |||
59acce1150 | |||
d4c97e1f4f | |||
28b5ee16fb | |||
7909306d7a | |||
99df8ad38d | |||
156c3b3959 | |||
94837ed410 | |||
4aefe8b42d | |||
71080476b1 |
.gitignore.gitmodulesARCHITECTURE.mdLICENSEREADME.mdRENDERERTODOdef_style.h
docs
font-to-atlas
input.clib
opengl-ren
opengl
project.jsonresources
rewrite
scripts
src
.gitkeepcache.c3fifo.c3main.c3ugui_atlas.c3ugui_button.c3ugui_cmd.c3ugui_data.c3ugui_div.c3ugui_font.c3ugui_impl.c3ugui_input.c3ugui_layout.c3ugui_slider.c3ugui_text.c3vtree.c3
test
.gitkeepMakefilemain.cmonospace.ttfstb_rect_pack.hstb_truetype.hstbttf.htest_bitsruct.c3test_bittype.c3test_custom_hash.c3test_font.c3test_union.c3test_vtree.c3ugui_font.c3
text_rendering
.gitignoreMakefilebox_fragshader.glslbox_vertshader.glslfont.hfont_fragshader.glslfont_vertshader.glslgeneric_cache.hgeneric_hash.hgeneric_stack.hgenmake.shmain.cren.cren.hstb_image_write.hstb_truetype.hutil.cutil.h
ugui.cugui.h
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,5 +1,4 @@
|
||||
microgui
|
||||
*.o
|
||||
test/test
|
||||
**/compile_commands.json
|
||||
**/.cache
|
||||
*.a
|
||||
build/*
|
||||
**/.ccls-cache
|
||||
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
[submodule "lib/raylib.c3l"]
|
||||
path = lib/raylib.c3l
|
||||
url = https://github.com/NexushasTaken/raylib.c3l
|
184
ARCHITECTURE.md
Normal file
184
ARCHITECTURE.md
Normal file
@ -0,0 +1,184 @@
|
||||
## 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
|
4
RENDERER
4
RENDERER
@ -1,4 +0,0 @@
|
||||
Divide up the OpenGL renderer into three parts, one part that draws simple shapes,
|
||||
another that only draws text and one that is responsible for drawing icons and
|
||||
mapping sprites in general, this way all the texture atlases are separate and
|
||||
everything is done within 3 draw calls
|
41
TODO
Normal file
41
TODO
Normal file
@ -0,0 +1,41 @@
|
||||
# TODOs, semi-random sorting
|
||||
|
||||
[ ] Check every instance of foreach to see if I am using by-copy or by-reference correctly
|
||||
[x] Implement glyph draw command
|
||||
[x] Implement div.view and scrollbars
|
||||
[ ] Port font system from C to C3 (rewrite1)
|
||||
[ ] Update ARCHITECTURE.md
|
||||
[ ] Write a README.md
|
||||
[ ] Use an arena allocator for cache
|
||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||
[ ] Better handling of the active and focused widgets, try
|
||||
to maintain focus until mouse release (fix scroll bars)
|
||||
[ ] Write a description for each file and the structs, interfaces provided
|
||||
|
||||
## Commands
|
||||
|
||||
[x] rect commads should have:
|
||||
_ border width
|
||||
_ border radius
|
||||
[x] add a command to update an atlas
|
||||
|
||||
## Atlases
|
||||
|
||||
[ ] Add an interface to create, destroy, update and get atlases based on their ids
|
||||
[ ] Implement multiple font atlases
|
||||
[ ] Create and use ShortIds for atlases
|
||||
|
||||
## Fonts
|
||||
|
||||
[ ] Fix the missing alpha channel
|
||||
[x] Fix the alignment
|
||||
|
||||
## Raylib
|
||||
|
||||
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
||||
[x] Implement pixel radius rounding for border radius
|
||||
|
||||
## Widgets
|
||||
|
||||
[ ] Dynamic text box to implement an fps counter
|
||||
[ ] Button with label
|
40
def_style.h
40
def_style.h
@ -1,40 +0,0 @@
|
||||
#ifndef _UG_DEF_STYLE_H
|
||||
#define _UG_DEF_STYLE_H
|
||||
|
||||
#include "ugui.h"
|
||||
|
||||
#define SZ_INT(x) x.size.i
|
||||
|
||||
static const ug_style_t default_style = {
|
||||
.color = {
|
||||
.bg = RGB_FORMAT(0x131313),
|
||||
.fg = RGB_FORMAT(0xffffff),
|
||||
},
|
||||
.margin = SIZE_PX(3),
|
||||
.border = {
|
||||
.color = RGB_FORMAT(0xf50a00),
|
||||
.size = SIZE_PX(2),
|
||||
},
|
||||
.title = {
|
||||
.color = {
|
||||
.bg = RGB_FORMAT(0xbbbbbb),
|
||||
.fg = RGB_FORMAT(0xffff00),
|
||||
},
|
||||
.height = SIZE_PX(20),
|
||||
.font_size = SIZE_PX(14),
|
||||
},
|
||||
.btn = {
|
||||
.color = {
|
||||
.act = RGB_FORMAT(0x440044),
|
||||
.bg = RGB_FORMAT(0x006600),
|
||||
.fg = RGB_FORMAT(0xffff00),
|
||||
.sel = RGB_FORMAT(0x0a0aff),
|
||||
.br = RGB_FORMAT(0xff00ff),
|
||||
},
|
||||
.font_size = SIZE_PX(10),
|
||||
.border = SIZE_PX(5),
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
#endif
|
0
docs/.gitkeep
Normal file
0
docs/.gitkeep
Normal file
3
font-to-atlas/.gitignore
vendored
3
font-to-atlas/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
*.ff
|
||||
*.png
|
||||
font-to-atlas
|
@ -1,2 +0,0 @@
|
||||
font-to-atlas: main.c ff.c ff.h
|
||||
cc -lm -g main.c ff.c -o font-to-atlas
|
@ -1,118 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
#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
|
@ -1,107 +0,0 @@
|
||||
#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
40
input.c
40
input.c
@ -1,40 +0,0 @@
|
||||
#include "ugui.h"
|
||||
|
||||
/*=============================================================================*
|
||||
* Input Handling *
|
||||
*=============================================================================*/
|
||||
|
||||
|
||||
#define TEST_CTX(ctx) { if (!ctx) return -1; }
|
||||
|
||||
|
||||
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y)
|
||||
{
|
||||
TEST_CTX(ctx)
|
||||
if (x < 0 || y < 0)
|
||||
return 0;
|
||||
|
||||
ctx->mouse.pos = (ug_vec2_t){.x = x, .y = y};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask)
|
||||
{
|
||||
TEST_CTX(ctx);
|
||||
|
||||
ctx->mouse.update |= mask;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask)
|
||||
{
|
||||
TEST_CTX(ctx);
|
||||
|
||||
ctx->mouse.update |= mask;
|
||||
|
||||
return 0;
|
||||
}
|
0
lib/.gitkeep
Normal file
0
lib/.gitkeep
Normal file
3
lib/libgrapheme.c3l/Makefile
Normal file
3
lib/libgrapheme.c3l/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
all:
|
||||
make -C thirdparty/libgrapheme
|
||||
cp thirdparty/libgrapheme/libgrapheme.a linux-x64/libgrapheme.a
|
46
lib/libgrapheme.c3l/libgrapheme.c3i
Normal file
46
lib/libgrapheme.c3l/libgrapheme.c3i
Normal file
@ -0,0 +1,46 @@
|
||||
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");
|
9
lib/libgrapheme.c3l/manifest.json
Normal file
9
lib/libgrapheme.c3l/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"provides": "grapheme",
|
||||
"targets": {
|
||||
"linux-x64": {
|
||||
"dependencies": [],
|
||||
"linked-libraries": ["grapheme", "c"]
|
||||
}
|
||||
}
|
||||
}
|
44
lib/libgrapheme.c3l/project.json
Normal file
44
lib/libgrapheme.c3l/project.json
Normal file
@ -0,0 +1,44 @@
|
||||
{
|
||||
// Language version of C3.
|
||||
"langrev": "1",
|
||||
// Warnings used for all targets.
|
||||
"warnings": ["no-unused"],
|
||||
// Directories where C3 library files may be found.
|
||||
"dependency-search-paths": [".."],
|
||||
// Libraries to use for all targets.
|
||||
"dependencies": ["grapheme"],
|
||||
// Authors, optionally with email.
|
||||
"authors": ["Alessandro Mauri <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
lib/libgrapheme.c3l/thirdparty/libgrapheme
vendored
Submodule
1
lib/libgrapheme.c3l/thirdparty/libgrapheme
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 65b354f0fcb1d925f4340dbb4415ea06e8af2bec
|
3
lib/libschrift.c3l/Makefile
Normal file
3
lib/libschrift.c3l/Makefile
Normal file
@ -0,0 +1,3 @@
|
||||
all:
|
||||
make -C thirdparty/libschrift
|
||||
cp thirdparty/libschrift/libschrift.a linux-x64/libschrift.a
|
58
lib/libschrift.c3l/libschrift.c3
Normal file
58
lib/libschrift.c3l/libschrift.c3
Normal file
@ -0,0 +1,58 @@
|
||||
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 char* sft_version() @extern("sft_version");
|
||||
|
||||
extern fn SftFont loadmem(void* mem, usz size) @extern("sft_loadmem");
|
||||
extern fn SftFont loadfile(char* filename) @extern("sft_loadfile");
|
||||
extern fn 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");
|
BIN
lib/libschrift.c3l/linux-x64/libschrift.a
Normal file
BIN
lib/libschrift.c3l/linux-x64/libschrift.a
Normal file
Binary file not shown.
9
lib/libschrift.c3l/manifest.json
Normal file
9
lib/libschrift.c3l/manifest.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"provides" : "schrift",
|
||||
"targets" : {
|
||||
"linux-x64" : {
|
||||
"dependencies" : [],
|
||||
"linked-libraries" : ["schrift", "c"]
|
||||
}
|
||||
}
|
||||
}
|
45
lib/libschrift.c3l/project.json
Normal file
45
lib/libschrift.c3l/project.json
Normal file
@ -0,0 +1,45 @@
|
||||
{
|
||||
// Language version of C3.
|
||||
"langrev": "1",
|
||||
// Warnings used for all targets.
|
||||
"warnings": [ "no-unused" ],
|
||||
// Directories where C3 library files may be found.
|
||||
"dependency-search-paths": [ ".." ],
|
||||
// Libraries to use for all targets.
|
||||
"dependencies": [ "schrift" ],
|
||||
// Authors, optionally with email.
|
||||
"authors": [ "Alessandro Mauri <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
lib/libschrift.c3l/thirdparty/libschrift
vendored
Submodule
1
lib/libschrift.c3l/thirdparty/libschrift
vendored
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit 24737d2922b23df4a5692014f5ba03da0c296112
|
1
lib/raylib.c3l
Submodule
1
lib/raylib.c3l
Submodule
@ -0,0 +1 @@
|
||||
Subproject commit c7ebe054ce16136c1128fab54fcce4921044293e
|
2
opengl-ren/.gitignore
vendored
2
opengl-ren/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
a.out
|
||||
gl
|
@ -1,5 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
|
||||
gl: main.c
|
||||
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl
|
@ -1,2 +0,0 @@
|
||||
since I've never worked with opengl I made this folder as a little test environment
|
||||
for an SDL-OpenGL renderer
|
Binary file not shown.
Binary file not shown.
Before ![]() (image error) Size: 862 B |
@ -1,12 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,521 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
#version 330
|
||||
|
||||
// use the input ("in") vertices from index zero
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 1) in vec4 color;
|
||||
layout(location = 2) in vec2 uv;
|
||||
|
||||
flat out vec4 out_color;
|
||||
out vec2 texture_coord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// simply copy teh output position
|
||||
gl_Position = vec4(position.x, position.y, 0.0f, 1.0f);
|
||||
out_color = color;
|
||||
texture_coord = uv;
|
||||
}
|
2
opengl/.gitignore
vendored
2
opengl/.gitignore
vendored
@ -1,2 +0,0 @@
|
||||
a.out
|
||||
gl
|
@ -1,5 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lGLEW -lGL
|
||||
|
||||
gl: main.c
|
||||
gcc ${CFLAGS} ${LDFLAGS} main.c -o gl
|
@ -1,2 +0,0 @@
|
||||
since I've never worked with opengl I made this folder as a little test environment
|
||||
for an SDL-OpenGL renderer
|
@ -1,10 +0,0 @@
|
||||
#version 330
|
||||
|
||||
smooth in vec4 out_color;
|
||||
out vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
// set the color for each vertex to white
|
||||
color = out_color;
|
||||
}
|
278
opengl/main.c
278
opengl/main.c
@ -1,278 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#version 330
|
||||
|
||||
// use the input ("in") vertices from index zero
|
||||
layout(location = 0) in vec4 position;
|
||||
layout(location = 1) in vec4 color;
|
||||
|
||||
smooth out vec4 out_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
// simply copy teh output position
|
||||
gl_Position = position;
|
||||
out_color = color;
|
||||
}
|
55
project.json
Normal file
55
project.json
Normal file
@ -0,0 +1,55 @@
|
||||
{
|
||||
// Language version of C3.
|
||||
"langrev": "1",
|
||||
// Warnings used for all targets.
|
||||
"warnings": [ "no-unused" ],
|
||||
// Directories where C3 library files may be found.
|
||||
"dependency-search-paths": [ "lib" ],
|
||||
// Libraries to use for all targets.
|
||||
"dependencies": [ "raylib", "schrift", "grapheme" ],
|
||||
"features": [
|
||||
// See rcore.c3
|
||||
//"SUPPORT_INTERNAL_MEMORY_MANAGEMENT",
|
||||
//"SUPPORT_STANDARD_FILEIO",
|
||||
//"SUPPORT_FILE_SYSTEM_FUNCTIONS",
|
||||
//"SUPPORT_DATA_ENCODER",
|
||||
// See text.c3
|
||||
//"SUPPORT_TEXT_CODEPOINTS_MANAGEMENT",
|
||||
//"SUPPORT_TEXT_C_STRING_MANAGEMENT",
|
||||
//"SUPPORT_RANDOM_GENERATION",
|
||||
//"SUPPORT_RAYGUI",
|
||||
//"RAYGUI_NO_ICONS",
|
||||
//"RAYGUI_CUSTOM_ICONS",
|
||||
],
|
||||
// Authors, optionally with email.
|
||||
"authors": [ "John Doe <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.
|
||||
}
|
0
resources/.gitkeep
Normal file
0
resources/.gitkeep
Normal file
@ -1,126 +0,0 @@
|
||||
#ifndef _STACK_GENERIC_H
|
||||
#define _STACK_GENERIC_H
|
||||
|
||||
|
||||
#define STACK_STEP 8
|
||||
#define STACK_SALT 0xbabb0cac
|
||||
|
||||
// FIXME: find a way to not re-hash the whole stack when removing one item
|
||||
|
||||
// incremental hash for every grow
|
||||
#define STACK_HASH(p, s, h) \
|
||||
{ \
|
||||
unsigned char *v = (unsigned char *)(p); \
|
||||
for (int x = (s); x; x--) { \
|
||||
(h) += v[x-1]; \
|
||||
(h) += (h) << 10; \
|
||||
(h) ^= (h) >> 6; \
|
||||
} \
|
||||
(h) += (h) << 3; \
|
||||
(h) ^= (h) >> 11; \
|
||||
(h) += (h) << 15; \
|
||||
}
|
||||
|
||||
|
||||
// declare just the prototypes
|
||||
#define STACK_PROTO(stackname, type) \
|
||||
struct stackname { \
|
||||
type *items; \
|
||||
int size, idx, old_idx; \
|
||||
unsigned int hash, old_hash; \
|
||||
}; \
|
||||
struct stackname stackname##_init(void); \
|
||||
int stackname##_grow(struct stackname *stack, int step); \
|
||||
int stackname##_push(struct stackname *stack, type *e); \
|
||||
type stackname##_pop(struct stackname *stack); \
|
||||
int stackname##_clear(struct stackname *stack); \
|
||||
int stackname##_changed(struct stackname *stack); \
|
||||
int stackname##_size_changed(struct stackname *stack); \
|
||||
int stackname##_free(struct stackname *stack);
|
||||
|
||||
|
||||
#define STACK_DEFINE(stackname, type) \
|
||||
struct stackname; \
|
||||
\
|
||||
\
|
||||
struct stackname stackname##_init(void) \
|
||||
{ \
|
||||
return (struct stackname){0, .hash = STACK_SALT}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_grow(struct stackname *stack, int step) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
|
||||
if(!stack->items) \
|
||||
return -1; \
|
||||
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
|
||||
stack->size += step; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_push(struct stackname *stack, type *e) \
|
||||
{ \
|
||||
if (!stack || !e) \
|
||||
return -1; \
|
||||
if (stack->idx >= stack->size) \
|
||||
if (stackname##_grow(stack, STACK_STEP)) \
|
||||
return -1; \
|
||||
stack->items[stack->idx++] = *e; \
|
||||
STACK_HASH(e, sizeof(type), stack->hash); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
type stackname##_pop(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack || stack->idx == 0 || stack->size == 0) \
|
||||
return (type){0}; \
|
||||
stack->hash = STACK_SALT; \
|
||||
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
|
||||
return stack->items[stack->idx--]; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_clear(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->old_idx = stack->idx; \
|
||||
stack->old_hash = stack->hash; \
|
||||
stack->hash = STACK_SALT; \
|
||||
stack->idx = 0; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->hash != stack->old_hash; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_size_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->size != stack->old_idx; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_free(struct stackname *stack) \
|
||||
{ \
|
||||
if (stack) { \
|
||||
stackname##_clear(stack); \
|
||||
if (stack->items) \
|
||||
free(stack->items); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#endif
|
0
scripts/.gitkeep
Normal file
0
scripts/.gitkeep
Normal file
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
133
src/cache.c3
Normal file
133
src/cache.c3
Normal file
@ -0,0 +1,133 @@
|
||||
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 */
|
||||
if (entry.key != id) {
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/* MISS, the data is not valid (not present) */
|
||||
if (!cache.present[entry.value]) {
|
||||
// if the data is not present but it is still in the table, remove it
|
||||
cache.table.remove(id)!;
|
||||
return SearchResult.MISSING?;
|
||||
}
|
||||
|
||||
/* HIT, set as recently used */
|
||||
cache.used[entry.value] = true;
|
||||
return &(cache.pool[entry.value]);
|
||||
}
|
||||
|
||||
/* Look for a free spot in the present bitmap and return its index */
|
||||
/* If there is no free space left then just return the first position */
|
||||
fn usz Cache.get_free_spot(&cache) @private
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
49
src/fifo.c3
Normal file
49
src/fifo.c3
Normal file
@ -0,0 +1,49 @@
|
||||
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;
|
||||
}
|
238
src/main.c3
Normal file
238
src/main.c3
Normal file
@ -0,0 +1,238 @@
|
||||
import std::io;
|
||||
import vtree;
|
||||
import cache;
|
||||
import ugui;
|
||||
import rl;
|
||||
import std::time;
|
||||
import std::collections::ringbuffer;
|
||||
|
||||
def Times = ringbuffer::RingBuffer(<time::NanoDuration, 128>);
|
||||
|
||||
fn void Times.print_stats(×)
|
||||
{
|
||||
time::NanoDuration min, max, avg, x;
|
||||
min = times.get(0);
|
||||
for (usz i = 0; i < times.written; i++) {
|
||||
x = times.get(i);
|
||||
if (x < min) { min = x; }
|
||||
if (x > max) { max = x; }
|
||||
avg += x;
|
||||
}
|
||||
avg = (NanoDuration)((ulong)avg/128.0);
|
||||
|
||||
io::printfn("min=%s, max=%s, avg=%s", min, max, avg);
|
||||
}
|
||||
|
||||
fn int main(String[] args)
|
||||
{
|
||||
ugui::Ctx ui;
|
||||
ui.init()!!;
|
||||
ui.font.load("/usr/share/fonts/TTF/HackNerdFontMono-Regular.ttf", 16)!!;
|
||||
|
||||
short width = 800;
|
||||
short height = 450;
|
||||
rl::set_config_flags(rl::FLAG_WINDOW_RESIZABLE);
|
||||
rl::init_window(width, height, "Ugui Test");
|
||||
ui.input_window_size(width, height)!!;
|
||||
rl::set_target_fps(60);
|
||||
rl::enable_event_waiting();
|
||||
|
||||
isz frame;
|
||||
bool toggle = true;
|
||||
time::Clock clock;
|
||||
Times ui_times;
|
||||
Times draw_times;
|
||||
|
||||
// Main loop
|
||||
while (!rl::window_should_close()) {
|
||||
clock.mark();
|
||||
|
||||
/* Start Input Handling */
|
||||
if (rl::is_window_resized()) {
|
||||
width = (short)rl::get_screen_width();
|
||||
height = (short)rl::get_screen_height();
|
||||
ui.input_window_size(width, height)!!;
|
||||
}
|
||||
|
||||
ui.input_changefocus(rl::is_window_focused());
|
||||
|
||||
rl::Vector2 mpos = rl::get_mouse_position();
|
||||
ui.input_mouse_abs((short)mpos.x, (short)mpos.y);
|
||||
|
||||
ugui::MouseButtons buttons;
|
||||
buttons.btn_left = rl::is_mouse_button_down(rl::MOUSE_BUTTON_LEFT);
|
||||
buttons.btn_right = rl::is_mouse_button_down(rl::MOUSE_BUTTON_RIGHT);
|
||||
buttons.btn_middle = rl::is_mouse_button_down(rl::MOUSE_BUTTON_MIDDLE);
|
||||
ui.input_mouse_button(buttons);
|
||||
/* End Input Handling */
|
||||
|
||||
/* Start UI Handling */
|
||||
ui.frame_begin()!!;
|
||||
|
||||
// main div, fill the whole window
|
||||
ui.div_begin("main", ugui::Rect{.w=ui.width/2})!!;
|
||||
{|
|
||||
|
||||
ui.layout_set_row()!!;
|
||||
if (ui.button("button0", ugui::Rect{0,0,30,30})!!.mouse_press) {
|
||||
io::printn("press button0");
|
||||
toggle = !toggle;
|
||||
ui.force_update()!!;
|
||||
}
|
||||
//ui.layout_next_column()!!;
|
||||
if (ui.button("button1", ugui::Rect{0,0,30,30})!!.mouse_press) {
|
||||
io::printn("press button1");
|
||||
}
|
||||
//ui.layout_next_column()!!;
|
||||
if (toggle) {
|
||||
if (ui.button("button2", ugui::Rect{0,0,30,30})!!.mouse_release) {
|
||||
io::printn("release button2");
|
||||
}
|
||||
}
|
||||
if (ui.slider_ver("slider", ugui::Rect{0,0,30,100})!!.update) {
|
||||
ugui::Elem* e = ui.get_elem_by_label("slider")!!;
|
||||
io::printfn("slider: %f", e.slider.value);
|
||||
}
|
||||
|
||||
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡")!!;
|
||||
|};
|
||||
ui.div_end()!!;
|
||||
|
||||
ui.div_begin("second", ugui::DIV_FILL)!!;
|
||||
ugui::Elem* de = ui.get_elem_by_label("second")!!;
|
||||
de.div.scroll.can_y = true;
|
||||
{|
|
||||
ui.layout_set_column()!!;
|
||||
if (ui.slider_ver("slider_other", ugui::Rect{0,0,30,100})!!.update) {
|
||||
ugui::Elem* e = ui.get_elem_by_label("slider_other")!!;
|
||||
io::printfn("other slider: %f", e.slider.value);
|
||||
}
|
||||
ui.button("button10", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button11", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button12", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button13", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button14", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button15", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button16", ugui::Rect{0,0,50,50})!!;
|
||||
ui.button("button17", ugui::Rect{0,0,50,50})!!;
|
||||
|};
|
||||
ui.div_end()!!;
|
||||
|
||||
ui.frame_end()!!;
|
||||
/* End UI Handling */
|
||||
ui_times.push(clock.mark());
|
||||
ui_times.print_stats();
|
||||
|
||||
/* Start UI Drawing */
|
||||
rl::begin_drawing();
|
||||
// ClearBackground(BLACK);
|
||||
|
||||
rl::Color c;
|
||||
rl::Rectangle r;
|
||||
static rl::Image font_atlas;
|
||||
static rl::Texture2D font_texture;
|
||||
for (Cmd* cmd; (cmd = ui.cmd_queue.dequeue() ?? null) != null;) {
|
||||
switch (cmd.type) {
|
||||
case ugui::CmdType.CMD_RECT:
|
||||
c = rl::Color{
|
||||
.r = cmd.rect.color.r,
|
||||
.g = cmd.rect.color.g,
|
||||
.b = cmd.rect.color.b,
|
||||
.a = cmd.rect.color.a,
|
||||
};
|
||||
r = rl::Rectangle{
|
||||
.x = cmd.rect.rect.x,
|
||||
.y = cmd.rect.rect.y,
|
||||
.height = cmd.rect.rect.h,
|
||||
.width = cmd.rect.rect.w,
|
||||
};
|
||||
float rad = cmd.rect.radius;
|
||||
// for some weird-ass reason the straight forward inverse formula does not work
|
||||
float roundness = r.width > r.height ? (2.1*rad)/r.height : (2.1*rad)/r.width;
|
||||
rl::draw_rectangle_rounded(r, roundness, 0, c);
|
||||
case ugui::CmdType.CMD_UPDATE_ATLAS:
|
||||
rl::unload_image(font_atlas);
|
||||
font_atlas.data = cmd.update_atlas.raw_buffer;
|
||||
font_atlas.width = cmd.update_atlas.width;
|
||||
font_atlas.height = cmd.update_atlas.height;
|
||||
font_atlas.mipmaps = 1;
|
||||
//font_atlas.format = rl::PixelFormat.PIXELFORMAT_UNCOMPRESSED_GRAYSCALE;
|
||||
font_atlas.format = 1;
|
||||
if (rl::is_texture_ready(font_texture)) {
|
||||
rl::unload_texture(font_texture);
|
||||
}
|
||||
font_texture = rl::load_texture_from_image(font_atlas);
|
||||
//rl::draw_texture(font_texture, 0, 0, rl::WHITE);
|
||||
case ugui::CmdType.CMD_SPRITE:
|
||||
rl::Rectangle source = {
|
||||
.x = cmd.sprite.texture_rect.x,
|
||||
.y = cmd.sprite.texture_rect.y,
|
||||
.width = cmd.sprite.texture_rect.w,
|
||||
.height = cmd.sprite.texture_rect.h,
|
||||
};
|
||||
rl::Vector2 position = {
|
||||
.x = cmd.sprite.rect.x,
|
||||
.y = cmd.sprite.rect.y,
|
||||
};
|
||||
|
||||
rl::draw_texture_rec(font_texture, source, position, rl::WHITE);
|
||||
//rl::draw_rectangle(cmd.sprite.rect.x,
|
||||
// cmd.sprite.rect.y,
|
||||
// cmd.sprite.rect.w,
|
||||
// cmd.sprite.rect.h,
|
||||
// rl::WHITE
|
||||
//);
|
||||
default:
|
||||
io::printfn("Unknown cmd type: %s", cmd.type);
|
||||
}
|
||||
}
|
||||
draw_times.push(clock.mark());
|
||||
draw_times.print_stats();
|
||||
rl::end_drawing();
|
||||
/* End Drawing */
|
||||
|
||||
}
|
||||
|
||||
rl::close_window();
|
||||
|
||||
ui.font.free();
|
||||
ui.free();
|
||||
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");
|
||||
}
|
||||
*/
|
183
src/ugui_atlas.c3
Normal file
183
src/ugui_atlas.c3
Normal file
@ -0,0 +1,183 @@
|
||||
module ugui;
|
||||
|
||||
import std::core::mem;
|
||||
import std::math::random;
|
||||
|
||||
random::SimpleRandom atlas_seed;
|
||||
|
||||
fault UgAtlasError {
|
||||
CANNOT_PLACE,
|
||||
INVALID_FORMAT,
|
||||
MISSING_ATLAS,
|
||||
}
|
||||
|
||||
enum AtlasFormat {
|
||||
ATLAS_GRAYSCALE,
|
||||
ATLAS_ALPHA,
|
||||
}
|
||||
|
||||
// black and white atlas
|
||||
struct Atlas {
|
||||
AtlasFormat format;
|
||||
Id id;
|
||||
ushort width, height;
|
||||
char[] buffer;
|
||||
|
||||
Point row;
|
||||
ushort row_h;
|
||||
}
|
||||
|
||||
// FIXME: allocate atlases dynamically
|
||||
fn void! Ctx.init_atlases(&ctx)
|
||||
{
|
||||
atlas_seed.set_seed("ciao mamma");
|
||||
ctx.atlas = mem::new_array(Atlas, MAX_ATLAS);
|
||||
}
|
||||
|
||||
fn void Ctx.free_atlases(&ctx)
|
||||
{
|
||||
foreach (a: ctx.atlas) {
|
||||
a.free();
|
||||
}
|
||||
mem::free(ctx.atlas);
|
||||
}
|
||||
|
||||
// FIXME: this could be slow
|
||||
fn Atlas*! Ctx.get_atlas(&ctx, Id id)
|
||||
{
|
||||
foreach (i, a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
return &ctx.atlas[i];
|
||||
}
|
||||
}
|
||||
return UgAtlasError.MISSING_ATLAS?;
|
||||
}
|
||||
|
||||
// TODO: use the same allocator as all the other parts of the code
|
||||
fn Atlas*! Ctx.request_new_atlas(&ctx, AtlasFormat format, Id *rid, ushort width, ushort height)
|
||||
{
|
||||
usz idx;
|
||||
bool free = false;
|
||||
for (; idx < ctx.atlas.len; idx++) {
|
||||
if (ctx.atlas[idx].id == 0) {
|
||||
free = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (free) {
|
||||
*rid = ctx.unique_atlas_id();
|
||||
ctx.atlas[idx].new(format, *rid, width, height)!;
|
||||
return &ctx.atlas[idx];
|
||||
} else {
|
||||
// TODO: reallocate the atlas vector
|
||||
return UgAtlasError.MISSING_ATLAS?;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: WTF
|
||||
fn Id Ctx.unique_atlas_id(&ctx) {
|
||||
Id id = atlas_seed.next_long();
|
||||
bool ok = true;
|
||||
foreach (a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok ? id : ctx.unique_atlas_id();
|
||||
}
|
||||
|
||||
fn void Ctx.discard_atlas(&ctx, Id id)
|
||||
{
|
||||
foreach (&a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
a.free();
|
||||
*a = Atlas{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to place a sprite into an atlas of the correct type and return it's id
|
||||
// new_width and new_height are used in case a new atlas has to be requested, in that
|
||||
// case they are used as width and height for the new atlas
|
||||
// FIXME: maybe there has to be a default value
|
||||
fn Id! Ctx.place_sprite(&ctx, Point* uv, AtlasFormat format, char[] pixels, ushort w, ushort h, ushort new_width, ushort new_height)
|
||||
{
|
||||
// try to place the sprite into an existing atlas
|
||||
foreach (&a: ctx.atlas) {
|
||||
if (a.id != 0 && a.format == format) {
|
||||
Point! ouv = a.place(pixels, w, h);
|
||||
if (catch ouv) {
|
||||
continue;
|
||||
}
|
||||
*uv = ouv;
|
||||
return a.id;
|
||||
}
|
||||
}
|
||||
|
||||
// fail... try to request a new atlas and place it there
|
||||
Id id;
|
||||
Atlas* atlas = ctx.request_new_atlas(format, &id, new_width, new_height)!;
|
||||
*uv = atlas.place(pixels, w, h)!;
|
||||
return id;
|
||||
}
|
||||
|
||||
fn void! Atlas.new(&atlas, AtlasFormat format, Id id, ushort width, ushort height)
|
||||
{
|
||||
atlas.format = format;
|
||||
atlas.id = id;
|
||||
atlas.width = width;
|
||||
atlas.height = height;
|
||||
|
||||
usz bpp = 1;
|
||||
switch (format) {
|
||||
case ATLAS_GRAYSCALE: nextcase;
|
||||
case ATLAS_ALPHA:
|
||||
bpp = 1;
|
||||
default:
|
||||
return UgAtlasError.INVALID_FORMAT?;
|
||||
}
|
||||
usz size = ((usz)atlas.width*atlas.height) * bpp;
|
||||
atlas.buffer = mem::new_array(char, size);
|
||||
}
|
||||
|
||||
fn void Atlas.free(&atlas)
|
||||
{
|
||||
free(atlas.buffer);
|
||||
}
|
||||
|
||||
// place a rect inside the atlas
|
||||
// uses a row first algorithm
|
||||
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
|
||||
// FIXME: untested with non-1bpp buffer depths
|
||||
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
|
||||
{
|
||||
Point p;
|
||||
|
||||
// TODO: simplify this and use rect_collision() and such
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
atlas.row.x = 0;
|
||||
atlas.row.y = atlas.row.y + atlas.row_h;
|
||||
atlas.row_h = 0;
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
return UgAtlasError.CANNOT_PLACE?;
|
||||
}
|
||||
}
|
||||
|
||||
for (usz y = 0; y < h; y++) {
|
||||
for (usz x = 0; x < w; x++) {
|
||||
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x];
|
||||
}
|
||||
}
|
||||
|
||||
atlas.row.x += w;
|
||||
if (h > atlas.row_h) {
|
||||
atlas.row_h = h;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
40
src/ugui_button.c3
Normal file
40
src/ugui_button.c3
Normal file
@ -0,0 +1,40 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
// draw a button, return the events on that button
|
||||
fn ElemEvents! Ctx.button(&ctx, String label, Rect size)
|
||||
{
|
||||
Id id = label.hash();
|
||||
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Elem *c_elem = ctx.get_elem(id)!;
|
||||
// add it to the tree
|
||||
ctx.tree.add(id, ctx.active_div)!;
|
||||
|
||||
// 1. Fill the element fields
|
||||
// this resets the flags
|
||||
c_elem.type = ETYPE_BUTTON;
|
||||
Color bg_color = uint_to_rgba(0x0000ffff);
|
||||
|
||||
// if the element is new or the parent was updated then redo layout
|
||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
||||
// 2. Layout
|
||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||
|
||||
// TODO: 3. Fill the button specific fields
|
||||
}
|
||||
|
||||
c_elem.events = ctx.get_elem_events(c_elem);
|
||||
if (parent.flags.has_focus) {
|
||||
if (c_elem.events.mouse_hover) {
|
||||
c_elem.flags.has_focus = true;
|
||||
bg_color = uint_to_rgba(0x00ff00ff);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the button
|
||||
ctx.push_rect(c_elem.bounds, bg_color, do_border: true, do_radius: true)!;
|
||||
|
||||
return c_elem.events;
|
||||
}
|
99
src/ugui_cmd.c3
Normal file
99
src/ugui_cmd.c3
Normal file
@ -0,0 +1,99 @@
|
||||
module ugui;
|
||||
|
||||
import std::ascii;
|
||||
|
||||
// FIXME: is this really the best solution?
|
||||
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
|
||||
fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
|
||||
{
|
||||
// FIXME: this should be culled higher up, maybe
|
||||
if (rect.w <= 0 || rect.h <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect border = ctx.style.border;
|
||||
Rect padding = ctx.style.padding;
|
||||
ushort radius = ctx.style.radius;
|
||||
Color border_color = ctx.style.brcolor;
|
||||
|
||||
if (do_border) {
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = rect,
|
||||
.rect.color = border_color,
|
||||
.rect.radius = do_radius ? radius : 0,
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&cmd)!;
|
||||
}
|
||||
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = {
|
||||
.x = rect.x + (do_border ? border.x : 0) + (do_padding ? padding.x : 0),
|
||||
.y = rect.y + (do_border ? border.y : 0) + (do_padding ? padding.y : 0),
|
||||
.w = rect.w - (do_border ? border.x+border.w : 0) - (do_padding ? padding.x+padding.w : 0),
|
||||
.h = rect.h - (do_border ? border.y+border.h : 0) - (do_padding ? padding.y+padding.h : 0),
|
||||
},
|
||||
.rect.color = color,
|
||||
.rect.radius = do_radius ? radius : 0,
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&cmd)!;
|
||||
}
|
||||
|
||||
// TODO: add texture id
|
||||
fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture)
|
||||
{
|
||||
Cmd cmd = {
|
||||
.type = CMD_SPRITE,
|
||||
.sprite.rect = bounds,
|
||||
.sprite.texture_rect = texture,
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&cmd)!;
|
||||
}
|
||||
|
||||
fn void! Ctx.push_string(&ctx, Rect bounds, String text)
|
||||
{
|
||||
if (text.len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
short baseline = (short)ctx.font.ascender;
|
||||
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
||||
short line_gap = (short)ctx.font.linegap;
|
||||
Point orig = {
|
||||
.x = bounds.x,
|
||||
.y = bounds.y,
|
||||
};
|
||||
|
||||
short line_len;
|
||||
Codepoint cp;
|
||||
usz off, x;
|
||||
while ((cp = str_to_codepoint(text[off..], &x)) != 0) {
|
||||
off += x;
|
||||
Glyph* gp;
|
||||
if (!ascii::is_cntrl((char)cp)) {
|
||||
gp = ctx.font.get_glyph(cp)!;
|
||||
Rect gb = {
|
||||
.x = orig.x + line_len + gp.ox,
|
||||
.y = orig.y + gp.oy + baseline,
|
||||
.w = gp.w,
|
||||
.h = gp.h,
|
||||
};
|
||||
Rect gt = {
|
||||
.x = gp.u,
|
||||
.y = gp.v,
|
||||
.w = gp.w,
|
||||
.h = gp.h,
|
||||
};
|
||||
if (rect_collision(gb, bounds)) {
|
||||
ctx.push_sprite(gb, gt)!;
|
||||
}
|
||||
line_len += gp.adv;
|
||||
} else if (cp == '\n'){
|
||||
orig.y += line_height + line_gap;
|
||||
line_len = 0;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
243
src/ugui_data.c3
Normal file
243
src/ugui_data.c3
Normal file
@ -0,0 +1,243 @@
|
||||
module ugui;
|
||||
|
||||
import std::core::string;
|
||||
|
||||
import vtree;
|
||||
import cache;
|
||||
import fifo;
|
||||
|
||||
|
||||
struct Rect {
|
||||
short x, y, w, h;
|
||||
}
|
||||
|
||||
struct Point {
|
||||
short x, y;
|
||||
}
|
||||
|
||||
struct Color{
|
||||
char r, g, b, a;
|
||||
}
|
||||
|
||||
// element ids are just long ints
|
||||
def Id = usz;
|
||||
|
||||
enum ElemType {
|
||||
ETYPE_NONE,
|
||||
ETYPE_DIV,
|
||||
ETYPE_BUTTON,
|
||||
ETYPE_SLIDER,
|
||||
ETYPE_TEXT,
|
||||
}
|
||||
|
||||
bitstruct ElemFlags : uint {
|
||||
bool updated : 0;
|
||||
bool has_focus : 1;
|
||||
bool is_new : 2;
|
||||
}
|
||||
|
||||
bitstruct ElemEvents : uint {
|
||||
bool key_press : 0;
|
||||
bool key_release : 1;
|
||||
bool key_hold : 2;
|
||||
bool mouse_hover : 3;
|
||||
bool mouse_press : 4;
|
||||
bool mouse_release : 5;
|
||||
bool mouse_hold : 6;
|
||||
bool update : 7;
|
||||
}
|
||||
|
||||
enum DivLayout {
|
||||
LAYOUT_ROW,
|
||||
LAYOUT_COLUMN,
|
||||
LAYOUT_FLOATING,
|
||||
}
|
||||
|
||||
// div element
|
||||
struct Div {
|
||||
DivLayout layout;
|
||||
struct scroll {
|
||||
bool can_x;
|
||||
bool can_y;
|
||||
bool on_x;
|
||||
bool on_y;
|
||||
float value_x;
|
||||
float value_y;
|
||||
}
|
||||
Rect children_bounds;
|
||||
Point origin_r, origin_c;
|
||||
Color color_bg;
|
||||
}
|
||||
|
||||
// slider element
|
||||
struct Slider {
|
||||
float value;
|
||||
Rect handle;
|
||||
}
|
||||
|
||||
struct Text {
|
||||
char* str;
|
||||
}
|
||||
|
||||
// element structure
|
||||
struct Elem {
|
||||
Id id;
|
||||
ElemFlags flags;
|
||||
ElemEvents events;
|
||||
Rect bounds;
|
||||
ElemType type;
|
||||
union {
|
||||
Div div;
|
||||
Slider slider;
|
||||
Text text;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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 = 1024;
|
||||
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private;
|
||||
|
||||
def CmdQueue = fifo::Fifo(<Cmd>);
|
||||
|
||||
fault UgError {
|
||||
INVALID_SIZE,
|
||||
EVENT_UNSUPPORTED,
|
||||
UNEXPECTED_ELEMENT,
|
||||
}
|
||||
|
||||
macro uint_to_rgba(uint u) {
|
||||
return Color{
|
||||
.r = (char)((u >> 24) & 0xff),
|
||||
.g = (char)((u >> 16) & 0xff),
|
||||
.b = (char)((u >> 8) & 0xff),
|
||||
.a = (char)((u >> 0) & 0xff)
|
||||
};
|
||||
}
|
||||
|
||||
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
|
||||
|
||||
macro abs(a) { return a < 0 ? -a : a; }
|
||||
macro clamp(x, min, max) { return x < min ? min : (x > max ? max : x); }
|
||||
|
||||
const uint STACK_STEP = 10;
|
||||
const uint MAX_ELEMS = 128;
|
||||
const uint MAX_CMDS = 256;
|
||||
const uint ROOT_ID = 1;
|
||||
const uint MAX_ATLAS = 2;
|
||||
|
||||
// command type
|
||||
enum CmdType {
|
||||
CMD_RECT,
|
||||
CMD_UPDATE_ATLAS,
|
||||
CMD_SPRITE,
|
||||
}
|
||||
|
||||
// command to draw a rect
|
||||
// TODO: implement radius
|
||||
struct CmdRect {
|
||||
Rect rect;
|
||||
ushort radius;
|
||||
Color color;
|
||||
}
|
||||
|
||||
// FIXME: For now only support black and white atlas, so PIXELFORMAT_UNCOMPRESSED_GRAYSCALE
|
||||
struct CmdUpdateAtlas {
|
||||
char* raw_buffer;
|
||||
short width, height;
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// 1. Add atlases as a data type
|
||||
// 2. Each atlas has an id
|
||||
struct CmdSprite {
|
||||
Rect rect;
|
||||
Rect texture_rect;
|
||||
}
|
||||
|
||||
// command structure
|
||||
struct Cmd {
|
||||
CmdType type;
|
||||
union {
|
||||
CmdRect rect;
|
||||
CmdUpdateAtlas update_atlas;
|
||||
CmdSprite sprite;
|
||||
}
|
||||
}
|
||||
|
||||
enum Layout {
|
||||
ROW,
|
||||
COLUMN,
|
||||
FLOATING
|
||||
}
|
||||
|
||||
// global style, similar to the css box model
|
||||
struct Style { // css box model
|
||||
Rect padding;
|
||||
Rect border;
|
||||
Rect margin;
|
||||
Color bgcolor; // background color
|
||||
Color fgcolor; // foreground color
|
||||
Color brcolor; // border color
|
||||
ushort radius;
|
||||
}
|
||||
|
||||
|
||||
struct Ctx {
|
||||
Layout layout;
|
||||
IdTree tree;
|
||||
ElemCache cache;
|
||||
CmdQueue cmd_queue;
|
||||
// total size in pixels of the context
|
||||
ushort width, height;
|
||||
Style style;
|
||||
|
||||
Atlas[] atlas;
|
||||
Font font;
|
||||
|
||||
bool has_focus;
|
||||
struct input {
|
||||
InputEvents events;
|
||||
struct mouse {
|
||||
Point pos, delta;
|
||||
// mouse_down: bitmap of mouse buttons that are held
|
||||
// mouse_updated: bitmap of mouse buttons that have been updated
|
||||
// mouse_released = mouse_updated & ~mouse_down
|
||||
// mouse_pressed = mouse_updated & mouse_down
|
||||
MouseButtons down;
|
||||
MouseButtons updated;
|
||||
}
|
||||
}
|
||||
|
||||
isz active_div; // tree node indicating the current active div
|
||||
}
|
||||
|
||||
macro point_in_rect(Point p, Rect r)
|
||||
{
|
||||
return (p.x >= r.x && p.x <= r.x + r.w) && (p.y >= r.y && p.y <= r.y + r.h);
|
||||
}
|
||||
|
||||
// return true if rect a contains b
|
||||
macro rect_contains(Rect a, Rect b)
|
||||
{
|
||||
return (a.x <= b.x && a.y <= b.y && a.x+a.w >= b.x+b.w && a.y+a.h >= b.y+b.h);
|
||||
}
|
||||
|
||||
macro rect_intersection(Rect a, Rect b)
|
||||
{
|
||||
return Rect{
|
||||
.x = (short)max(a.x, b.x),
|
||||
.y = (short)max(a.y, b.y),
|
||||
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
|
||||
.h = (short)min(a.y+a.h, b.y+b.h) - (short)max(a.y, b.y),
|
||||
};
|
||||
}
|
||||
|
||||
// rect intersection not null
|
||||
macro rect_collision(Rect a, Rect b)
|
||||
{
|
||||
return !(a.x > b.x+b.w || a.x+a.w < b.x || a.y > b.y+b.h || a.y+a.h < b.y);
|
||||
}
|
115
src/ugui_div.c3
Normal file
115
src/ugui_div.c3
Normal file
@ -0,0 +1,115 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
fn void! Ctx.div_begin(&ctx, String label, Rect size)
|
||||
{
|
||||
Id id = label.hash();
|
||||
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Elem* c_elem = ctx.get_elem(id)!;
|
||||
isz div_node = ctx.tree.add(id, ctx.active_div)!;
|
||||
ctx.active_div = div_node;
|
||||
|
||||
// 1. Fill the element fields
|
||||
c_elem.type = ETYPE_DIV;
|
||||
|
||||
// do layout and update flags only if the element was updated
|
||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
||||
// 2. layout the element
|
||||
c_elem.bounds = ctx.position_element(parent, size);
|
||||
if (c_elem.flags.is_new) {
|
||||
c_elem.div.children_bounds = c_elem.bounds;
|
||||
c_elem.div.color_bg = ctx.style.bgcolor;
|
||||
c_elem.div.scroll.can_x = false;
|
||||
c_elem.div.scroll.can_y = false;
|
||||
c_elem.div.scroll.value_x = 0;
|
||||
c_elem.div.scroll.value_y = 0;
|
||||
}
|
||||
|
||||
// 3. Mark the element as updated
|
||||
c_elem.flags.updated = true;
|
||||
// 4. Fill the div fields
|
||||
c_elem.div.origin_c = Point{
|
||||
.x = c_elem.bounds.x,
|
||||
.y = c_elem.bounds.y,
|
||||
};
|
||||
c_elem.div.origin_r = c_elem.div.origin_c;
|
||||
c_elem.div.layout = parent.div.layout;
|
||||
}
|
||||
|
||||
// Add the background to the draw stack
|
||||
ctx.push_rect(c_elem.bounds, c_elem.div.color_bg)!;
|
||||
|
||||
// TODO: check active
|
||||
// TODO: check resizeable
|
||||
|
||||
// check and draw scroll bars
|
||||
if (!rect_contains(c_elem.bounds, c_elem.div.children_bounds)) {
|
||||
Point cbc = {
|
||||
.x = c_elem.div.children_bounds.x + c_elem.div.children_bounds.w,
|
||||
.y = c_elem.div.children_bounds.y + c_elem.div.children_bounds.h,
|
||||
};
|
||||
Point bc = {
|
||||
.x = c_elem.bounds.x + c_elem.bounds.w,
|
||||
.y = c_elem.bounds.y + c_elem.bounds.h,
|
||||
};
|
||||
// vertical overflow, check and draw scroll bar
|
||||
if (cbc.y > bc.y && c_elem.div.scroll.can_y) {
|
||||
// set the scrollbar flag, is used in layout
|
||||
c_elem.div.scroll.on_y = true;
|
||||
|
||||
Rect vslider = {
|
||||
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
||||
.y = c_elem.bounds.y,
|
||||
.w = 10,
|
||||
.h = c_elem.bounds.h,
|
||||
};
|
||||
|
||||
float vh = max((float)bc.y / cbc.y, (float)0.15);
|
||||
Rect vhandle = {
|
||||
.x = c_elem.bounds.x + c_elem.bounds.w - 10,
|
||||
.y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1-vh) * c_elem.div.scroll.value_y)),
|
||||
.w = 10,
|
||||
.h = (short)(c_elem.bounds.h * vh),
|
||||
};
|
||||
|
||||
c_elem.events = ctx.get_elem_events(c_elem);
|
||||
if (parent.flags.has_focus && c_elem.events.mouse_hover && c_elem.events.mouse_hold && point_in_rect(ctx.input.mouse.pos, vhandle)) {
|
||||
short y = (short)clamp(ctx.input.mouse.pos.y - vhandle.h/2, vslider.y, vslider.y + vslider.h - vhandle.h);
|
||||
vhandle.y = y;
|
||||
float v = (float)(vhandle.y-vslider.y) / (float)(vslider.h-vhandle.h);
|
||||
c_elem.div.scroll.value_y = v;
|
||||
c_elem.flags.updated = true;
|
||||
c_elem.div.origin_c = Point{
|
||||
.x = c_elem.bounds.x,
|
||||
.y = c_elem.bounds.y,
|
||||
};
|
||||
c_elem.div.origin_r = c_elem.div.origin_c;
|
||||
}
|
||||
|
||||
ctx.push_rect(vslider, uint_to_rgba(0x999999ff))!;
|
||||
ctx.push_rect(vhandle, uint_to_rgba(0x9999ffff))!;
|
||||
} else {
|
||||
c_elem.div.scroll.on_y = false;
|
||||
}
|
||||
}
|
||||
|
||||
// if the bounds are outside of the view then allocate space for scrollbars
|
||||
DivLayout old_layout = c_elem.div.layout;
|
||||
c_elem.div.layout = LAYOUT_FLOATING;
|
||||
c_elem.div.layout = old_layout;
|
||||
|
||||
if (parent.flags.has_focus) {
|
||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.bounds)) {
|
||||
c_elem.flags.has_focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fn void! Ctx.div_end(&ctx)
|
||||
{
|
||||
// the active_div returns to the parent of the current one
|
||||
ctx.active_div = ctx.tree.parentof(ctx.active_div)!;
|
||||
}
|
155
src/ugui_font.c3
Normal file
155
src/ugui_font.c3
Normal file
@ -0,0 +1,155 @@
|
||||
module ugui;
|
||||
|
||||
import schrift;
|
||||
import std::collections::map;
|
||||
import std::core::mem;
|
||||
import std::io;
|
||||
|
||||
// unicode code point, different type for a different hash
|
||||
def Codepoint = uint;
|
||||
|
||||
|
||||
/* 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;
|
||||
Id atlas_id;
|
||||
}
|
||||
|
||||
const uint FONT_CACHED = 512;
|
||||
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;
|
||||
GlyphTable table;
|
||||
|
||||
float size;
|
||||
float ascender, descender, linegap; // Line Metrics
|
||||
}
|
||||
|
||||
|
||||
fn void! Ctx.font_load(&cxt, String path, uint height, float scale = 1)
|
||||
{
|
||||
Font* font = ctx.font;
|
||||
font.table.new_init(capacity: FONT_CACHED);
|
||||
|
||||
font.size = height*scale;
|
||||
|
||||
font.sft = schrift::Sft{
|
||||
.xScale = (double)font.size,
|
||||
.yScale = (double)font.size,
|
||||
.flags = schrift::SFT_DOWNWARD_Y,
|
||||
};
|
||||
|
||||
font.sft.font = schrift::loadfile(path);
|
||||
if (font.sft.font == null) {
|
||||
return UgFontError.TTF_LOAD_FAILED?;
|
||||
}
|
||||
|
||||
schrift::SftLMetrics lmetrics;
|
||||
schrift::lmetrics(&font.sft, &lmetrics);
|
||||
font.ascender = (float)lmetrics.ascender;
|
||||
font.descender = (float)lmetrics.descender;
|
||||
font.linegap = (float)lmetrics.lineGap;
|
||||
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap);
|
||||
}
|
||||
|
||||
fn Glyph*! Ctx.get_glyph(&ctx, Codepoint code, bool* is_new = null)
|
||||
{
|
||||
Font* font = &ctx.font;
|
||||
Glyph*! gp;
|
||||
gp = font.table.get_ref(code);
|
||||
|
||||
if (catch excuse = gp) {
|
||||
if (excuse != SearchResult.MISSING) {
|
||||
return excuse?;
|
||||
}
|
||||
} else {
|
||||
if (is_new) { *is_new = false; }
|
||||
return gp;
|
||||
}
|
||||
|
||||
// missing glyph, render and place into an atlas
|
||||
Glyph glyph;
|
||||
|
||||
schrift::SftGlyph gid;
|
||||
schrift::SftGMetrics gmtx;
|
||||
|
||||
if (schrift::lookup(&font.sft, code, &gid) < 0) {
|
||||
return UgFontError.MISSING_GLYPH?;
|
||||
}
|
||||
|
||||
if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) {
|
||||
return UgFontError.BAD_GLYPH_METRICS?;
|
||||
}
|
||||
|
||||
schrift::SftImage img = {
|
||||
.width = gmtx.minWidth,
|
||||
.height = gmtx.minHeight,
|
||||
};
|
||||
char[] pixels = mem::new_array(char, (usz)img.width * img.height);
|
||||
img.pixels = pixels;
|
||||
if (schrift::render(&font.sft, gid, img) < 0) {
|
||||
return UgFontError.RENDER_ERROR?;
|
||||
}
|
||||
|
||||
glyph.code = code;
|
||||
glyph.w = (ushort)img.width;
|
||||
glyph.h = (ushort)img.height;
|
||||
glyph.ox = (short)gmtx.leftSideBearing;
|
||||
glyph.oy = (short)gmtx.yOffset;
|
||||
glyph.adv = (short)gmtx.advanceWidth;
|
||||
//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);
|
||||
|
||||
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
|
||||
// like the letter 'A'
|
||||
ushort size = (ushort)font.size*256;
|
||||
Point uv;
|
||||
Id id = ctx.place_sprite(&uv, ATLAS_ALPHA, pixels, glyph.w, glyph.h, size, size)!;
|
||||
glyph.atlas_id = id;
|
||||
glyph.u = uv.x;
|
||||
glyph.v = uv.y;
|
||||
|
||||
mem::free(pixels);
|
||||
|
||||
font.table.set(code, glyph);
|
||||
|
||||
if (is_new) { *is_new = true; }
|
||||
return font.table.get_ref(code);
|
||||
}
|
||||
|
||||
// FIXME: maybe some atlases should be exclusive to fonts
|
||||
fn void Font.free(&font)
|
||||
{
|
||||
schrift::freefont(font.sft.font);
|
||||
}
|
152
src/ugui_impl.c3
Normal file
152
src/ugui_impl.c3
Normal file
@ -0,0 +1,152 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
// return a pointer to the parent of the current active div
|
||||
fn Elem*! Ctx.get_parent(&ctx)
|
||||
{
|
||||
// FIXME: if the tree held pointers to the elements then no more
|
||||
// redundant cache search
|
||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||
return ctx.cache.search(parent_id);
|
||||
}
|
||||
|
||||
// get or push an element from the cache, return a pointer to it
|
||||
// resets all flags except is_new which is set accordingly
|
||||
macro Ctx.get_elem(&ctx, Id id)
|
||||
{
|
||||
Elem empty_elem;
|
||||
bool is_new;
|
||||
Elem* c_elem;
|
||||
c_elem = ctx.cache.get_or_insert(&empty_elem, id, &is_new)!;
|
||||
c_elem.flags = (ElemFlags)0;
|
||||
c_elem.flags.is_new = is_new;
|
||||
return c_elem;
|
||||
}
|
||||
|
||||
// this searches an element in the cache by label, it does not create a new element
|
||||
// if it does't find one
|
||||
macro Ctx.get_elem_by_label(&ctx, String label)
|
||||
{
|
||||
Id id = label.hash();
|
||||
return ctx.cache.search(id);
|
||||
}
|
||||
|
||||
macro Ctx.get_elem_by_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.layout = Layout.ROW;
|
||||
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 = uint_to_rgba(0x282828ff);
|
||||
ctx.style.fgcolor = uint_to_rgba(0xfbf1c7ff);
|
||||
ctx.style.brcolor = uint_to_rgba(0xd79921ff);
|
||||
}
|
||||
|
||||
fn void Ctx.free(&ctx)
|
||||
{
|
||||
(void)ctx.tree.free();
|
||||
(void)ctx.cache.free();
|
||||
(void)ctx.cmd_queue.free();
|
||||
}
|
||||
|
||||
fn void! Ctx.frame_begin(&ctx)
|
||||
{
|
||||
|
||||
// 2. Get the root element from the cache and update it
|
||||
Elem* c_elem = ctx.get_elem(ROOT_ID)!;
|
||||
// The root should have the updated flag only if the size of the window
|
||||
// was changed between frames, this propagates an element size recalculation
|
||||
// down the element tree
|
||||
c_elem.flags.updated = ctx.input.events.resize | ctx.input.events.force_update;
|
||||
ctx.input.events.force_update = false;
|
||||
// if the window has focus then the root element also has focus, no other
|
||||
// computation needed, child elements need to check the mouse positon and
|
||||
// other stuff
|
||||
c_elem.flags.has_focus = ctx.has_focus;
|
||||
|
||||
if (c_elem.flags.is_new || c_elem.flags.updated) {
|
||||
Elem def_root = {
|
||||
.id = ROOT_ID,
|
||||
.type = ETYPE_DIV,
|
||||
.bounds = {
|
||||
.w = ctx.width,
|
||||
.h = ctx.height,
|
||||
},
|
||||
.div = {
|
||||
.layout = LAYOUT_ROW,
|
||||
.children_bounds = {
|
||||
.w = ctx.width,
|
||||
.h = ctx.height,
|
||||
}
|
||||
},
|
||||
.flags = c_elem.flags,
|
||||
};
|
||||
|
||||
*c_elem = def_root;
|
||||
}
|
||||
|
||||
// 3. Push the root element into the element tree
|
||||
ctx.active_div = ctx.tree.add(ROOT_ID, 0)!;
|
||||
|
||||
// The root element does not push anything to the stack
|
||||
// TODO: add a background color taken from a theme or config
|
||||
}
|
||||
|
||||
fn void! Ctx.force_update(&ctx)
|
||||
{
|
||||
ctx.input.events.force_update = true;
|
||||
}
|
||||
|
||||
fn void! Ctx.frame_end(&ctx)
|
||||
{
|
||||
// 1. clear the tree
|
||||
ctx.tree.prune(0)!;
|
||||
|
||||
// 2. clear input fields
|
||||
bool f = ctx.input.events.force_update;
|
||||
ctx.input.events = (InputEvents)0;
|
||||
ctx.input.events.force_update = f;
|
||||
|
||||
// draw mouse position
|
||||
$if 1:
|
||||
Cmd cmd = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = {
|
||||
.x = ctx.input.mouse.pos.x - 2,
|
||||
.y = ctx.input.mouse.pos.y - 2,
|
||||
.w = 4,
|
||||
.h = 4,
|
||||
},
|
||||
.rect.color = uint_to_rgba(0xff00ffff)
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&cmd)!;
|
||||
$endif
|
||||
}
|
||||
|
||||
<*
|
||||
* @ensure elem != null
|
||||
*>
|
||||
fn bool Ctx.is_hovered(&ctx, Elem *elem)
|
||||
{
|
||||
return point_in_rect(ctx.input.mouse.pos, elem.bounds);
|
||||
}
|
114
src/ugui_input.c3
Normal file
114
src/ugui_input.c3
Normal file
@ -0,0 +1,114 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
// TODO: this could be a bitstruct
|
||||
bitstruct InputEvents : uint {
|
||||
bool resize : 0; // window size was changed
|
||||
bool change_focus : 1; // window focus changed
|
||||
bool mouse_move : 2; // mouse was moved
|
||||
bool mouse_btn : 3; // mouse button pressed or released
|
||||
bool force_update : 4;
|
||||
}
|
||||
|
||||
// Window size was changed
|
||||
fn void! Ctx.input_window_size(&ctx, short width, short height)
|
||||
{
|
||||
if (width <= 0 || height <= 0) {
|
||||
return UgError.INVALID_SIZE?;
|
||||
}
|
||||
ctx.width = width;
|
||||
ctx.height = height;
|
||||
ctx.input.events.resize = true;
|
||||
}
|
||||
|
||||
// Window gained/lost focus
|
||||
fn void Ctx.input_changefocus(&ctx, bool has_focus)
|
||||
{
|
||||
// FIXME: raylib only has an API to query the focus status so we have to
|
||||
// update the input flag only if the focus changed
|
||||
if (ctx.has_focus != has_focus) {
|
||||
ctx.input.events.change_focus = true;
|
||||
}
|
||||
ctx.has_focus = has_focus;
|
||||
}
|
||||
|
||||
bitstruct MouseButtons : uint {
|
||||
bool btn_left : 0;
|
||||
bool btn_middle : 1;
|
||||
bool btn_right : 2;
|
||||
bool btn_4 : 3;
|
||||
bool btn_5 : 4;
|
||||
}
|
||||
|
||||
macro Ctx.mouse_pressed(&ctx) => ctx.input.mouse.updated & ctx.input.mouse.down;
|
||||
macro Ctx.mouse_released(&ctx) => ctx.input.mouse.updated & ~ctx.input.mouse.down;
|
||||
macro Ctx.mouse_down(&ctx) => ctx.input.mouse.down;
|
||||
|
||||
const MouseButtons BTN_NONE = (MouseButtons)0u;
|
||||
const MouseButtons BTN_ANY = (MouseButtons)(uint.max);
|
||||
const MouseButtons BTN_LEFT = {.btn_left = true};
|
||||
const MouseButtons BTN_MIDDLE = {.btn_middle = true};
|
||||
const MouseButtons BTN_RIGHT = {.btn_right = true};
|
||||
const MouseButtons BTN_4 = {.btn_4 = true};
|
||||
const MouseButtons BTN_5 = {.btn_5 = true};
|
||||
|
||||
// FIXME: hthis compairson could be done with a cast using MouseButtons.inner
|
||||
// property but I could not figure out how
|
||||
macro Ctx.is_mouse_pressed(&ctx, MouseButtons btn) => (ctx.mouse_pressed() & btn) != BTN_NONE;
|
||||
macro Ctx.is_mouse_released(&ctx, MouseButtons btn) => (ctx.mouse_released() & btn) != BTN_NONE;
|
||||
macro Ctx.is_mouse_down(&ctx, MouseButtons btn) => (ctx.mouse_down() & btn) != BTN_NONE;
|
||||
|
||||
macro ElemEvents Ctx.get_elem_events(&ctx, Elem *elem)
|
||||
{
|
||||
// TODO: add the other events
|
||||
// FIXME: active should be elsewhere
|
||||
bool active = elem.events.mouse_hold || ctx.is_hovered(elem);
|
||||
ElemEvents ev = {
|
||||
.mouse_hover = ctx.is_hovered(elem),
|
||||
.mouse_press = active & ctx.is_mouse_pressed(BTN_ANY),
|
||||
.mouse_release = active & ctx.is_mouse_released(BTN_ANY),
|
||||
.mouse_hold = active & ctx.is_mouse_down(BTN_ANY),
|
||||
};
|
||||
return ev;
|
||||
}
|
||||
|
||||
// Mouse Button moved
|
||||
fn void Ctx.input_mouse_button(&ctx, MouseButtons buttons)
|
||||
{
|
||||
ctx.input.mouse.updated = ctx.input.mouse.down ^ buttons;
|
||||
ctx.input.mouse.down = buttons;
|
||||
ctx.input.events.mouse_btn = true;
|
||||
}
|
||||
|
||||
// Mouse was moved, report absolute position
|
||||
fn void Ctx.input_mouse_abs(&ctx, short x, short y)
|
||||
{
|
||||
ctx.input.mouse.pos.x = clamp(x, 0u16, ctx.width);
|
||||
ctx.input.mouse.pos.y = clamp(y, 0u16, ctx.height);
|
||||
|
||||
short dx, dy;
|
||||
dx = x - ctx.input.mouse.pos.x;
|
||||
dy = y - ctx.input.mouse.pos.y;
|
||||
|
||||
ctx.input.mouse.delta.x = dx;
|
||||
ctx.input.mouse.delta.y = dy;
|
||||
|
||||
ctx.input.events.mouse_move = true;
|
||||
}
|
||||
|
||||
// Mouse was moved, report relative motion
|
||||
fn void Ctx.input_mouse_delta(&ctx, short dx, short dy)
|
||||
{
|
||||
ctx.input.mouse.delta.x = dx;
|
||||
ctx.input.mouse.delta.y = dy;
|
||||
|
||||
short mx, my;
|
||||
mx = ctx.input.mouse.pos.x + dx;
|
||||
my = ctx.input.mouse.pos.y + dy;
|
||||
|
||||
ctx.input.mouse.pos.x = clamp(mx, 0u16, ctx.width);
|
||||
ctx.input.mouse.pos.y = clamp(my, 0u16, ctx.height);
|
||||
|
||||
ctx.input.events.mouse_move = true;
|
||||
}
|
177
src/ugui_layout.c3
Normal file
177
src/ugui_layout.c3
Normal file
@ -0,0 +1,177 @@
|
||||
module ugui;
|
||||
|
||||
|
||||
fn void! Ctx.layout_set_row(&ctx)
|
||||
{
|
||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||
Elem *parent = ctx.cache.search(parent_id)!;
|
||||
|
||||
if (parent.type != ETYPE_DIV) {
|
||||
// what?
|
||||
return UgError.UNEXPECTED_ELEMENT?;
|
||||
}
|
||||
|
||||
parent.div.layout = LAYOUT_ROW;
|
||||
}
|
||||
|
||||
fn void! Ctx.layout_set_column(&ctx)
|
||||
{
|
||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||
Elem *parent = ctx.cache.search(parent_id)!;
|
||||
|
||||
if (parent.type != ETYPE_DIV) {
|
||||
// what?
|
||||
return UgError.UNEXPECTED_ELEMENT?;
|
||||
}
|
||||
|
||||
parent.div.layout = LAYOUT_COLUMN;
|
||||
}
|
||||
|
||||
fn void! Ctx.layout_set_floating(&ctx)
|
||||
{
|
||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||
Elem *parent = ctx.cache.search(parent_id)!;
|
||||
|
||||
if (parent.type != ETYPE_DIV) {
|
||||
// what?
|
||||
return UgError.UNEXPECTED_ELEMENT?;
|
||||
}
|
||||
|
||||
parent.div.layout = LAYOUT_FLOATING;
|
||||
}
|
||||
|
||||
fn void! Ctx.layout_next_row(&ctx)
|
||||
{
|
||||
Id parent_id = ctx.tree.get(ctx.active_div)!;
|
||||
Elem *parent = ctx.cache.search(parent_id)!;
|
||||
|
||||
if (parent.type != ETYPE_DIV) {
|
||||
// what?
|
||||
return UgError.UNEXPECTED_ELEMENT?;
|
||||
}
|
||||
|
||||
parent.div.origin_r = Point{
|
||||
.x = parent.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;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
Rect placement;
|
||||
Point origin;
|
||||
Div* div = &parent.div;
|
||||
|
||||
// 1. Select the right origin
|
||||
switch (div.layout) {
|
||||
case LAYOUT_ROW:
|
||||
origin = div.origin_r;
|
||||
case LAYOUT_COLUMN:
|
||||
origin = div.origin_c;
|
||||
case LAYOUT_FLOATING: // none
|
||||
default:
|
||||
// Error
|
||||
}
|
||||
|
||||
// the bottom-right border of the element box
|
||||
Point pl_corner;
|
||||
|
||||
// 2. Calculate the placement
|
||||
placement.x = (short)max(origin.x + rect.x, 0);
|
||||
placement.y = (short)max(origin.y + rect.y, 0);
|
||||
placement.w = rect.w > 0 ? rect.w : (short)max(parent.bounds.w - (placement.x - parent.bounds.x), 0);
|
||||
placement.h = rect.h > 0 ? rect.h : (short)max(parent.bounds.h - (placement.y - parent.bounds.y), 0);
|
||||
|
||||
pl_corner.x = placement.x + placement.w;
|
||||
pl_corner.y = placement.y + placement.h;
|
||||
|
||||
// 2.1 apply style, css box model
|
||||
if (style) {
|
||||
Rect margin = ctx.style.margin;
|
||||
Rect border = ctx.style.border;
|
||||
Rect padding = ctx.style.padding;
|
||||
|
||||
placement.x += margin.x;
|
||||
placement.y += margin.y;
|
||||
if (rect.w != 0) { placement.w += border.x+border.w + padding.x+padding.w; }
|
||||
if (rect.h != 0) { placement.h += border.y+border.h + padding.y+padding.h; }
|
||||
|
||||
pl_corner.x = placement.x + placement.w + margin.w;
|
||||
pl_corner.y = placement.y + placement.h + margin.h;
|
||||
}
|
||||
|
||||
// 3. Update the origins of the parent
|
||||
div.origin_r = Point{
|
||||
.x = pl_corner.x,
|
||||
.y = origin.y,
|
||||
};
|
||||
div.origin_c = Point{
|
||||
.x = origin.x,
|
||||
.y = pl_corner.y,
|
||||
};
|
||||
|
||||
// 4. Calculate the "scrolled" view
|
||||
Point off;
|
||||
Rect* cb = &div.children_bounds;
|
||||
if (div.scroll.can_x && div.scroll.on_x) {
|
||||
off.x = (short)(cb.w * div.scroll.value_x);
|
||||
}
|
||||
if (div.scroll.can_y && div.scroll.on_y) {
|
||||
off.y = (short)((float)(cb.h - parent.bounds.h) * div.scroll.value_y);
|
||||
}
|
||||
Rect view = {
|
||||
.x = parent.bounds.x + off.x,
|
||||
.y = parent.bounds.y + off.y,
|
||||
.w = parent.bounds.w,
|
||||
.h = parent.bounds.h,
|
||||
};
|
||||
|
||||
// 5. check if the placement overflows the children ounds, if so update them
|
||||
if (!point_in_rect(pl_corner, *cb)) {
|
||||
if (pl_corner.y > cb.y+cb.h) {
|
||||
cb.h = pl_corner.y - cb.y;
|
||||
}
|
||||
if (pl_corner.x > cb.x+cb.w) {
|
||||
cb.w += pl_corner.x - (cb.x + cb.w);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. check if the placement is inside the view
|
||||
if (rect_collision(placement, view)) {
|
||||
return Rect{
|
||||
.x = placement.x - off.x,
|
||||
.y = placement.y - off.y,
|
||||
.w = placement.w,
|
||||
.h = placement.h,
|
||||
};
|
||||
} else {
|
||||
return Rect{};
|
||||
}
|
||||
}
|
||||
|
108
src/ugui_slider.c3
Normal file
108
src/ugui_slider.c3
Normal file
@ -0,0 +1,108 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
|
||||
/* handle
|
||||
* +----+-----+---------------------+
|
||||
* | |#####| |
|
||||
* +----+-----+---------------------+
|
||||
*/
|
||||
fn ElemEvents! Ctx.slider_hor(&ctx, String label, Rect size)
|
||||
{
|
||||
Id id = label.hash();
|
||||
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Elem *c_elem = ctx.get_elem(id)!;
|
||||
// add it to the tree
|
||||
ctx.tree.add(id, ctx.active_div)!;
|
||||
|
||||
// 1. Fill the element fields
|
||||
c_elem.type = ETYPE_SLIDER;
|
||||
|
||||
// if the element is new or the parent was updated then redo layout
|
||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
||||
// 2. Layout
|
||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||
c_elem.slider.handle = Rect{
|
||||
.x = (short)(c_elem.bounds.x + (int)(c_elem.bounds.w*(1.0-0.25) * c_elem.slider.value)),
|
||||
.y = c_elem.bounds.y,
|
||||
.w = (short)(c_elem.bounds.w * 0.25),
|
||||
.h = c_elem.bounds.h,
|
||||
};
|
||||
}
|
||||
|
||||
c_elem.events = ctx.get_elem_events(c_elem);
|
||||
if (parent.flags.has_focus && c_elem.events.mouse_hover) {
|
||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) {
|
||||
short x = (short)clamp(ctx.input.mouse.pos.x - c_elem.slider.handle.w/2, c_elem.bounds.x, c_elem.bounds.x + c_elem.bounds.w - c_elem.slider.handle.w);
|
||||
float v = (float)(c_elem.slider.handle.x-c_elem.bounds.x) / (float)(c_elem.bounds.w-c_elem.slider.handle.w);
|
||||
c_elem.slider.handle.x = x;
|
||||
c_elem.slider.value = v;
|
||||
c_elem.events.update = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
Color bg_color = uint_to_rgba(0x0000ffff);
|
||||
Color handle_color = uint_to_rgba(0x0ff000ff);
|
||||
ctx.push_rect(c_elem.bounds, bg_color)!;
|
||||
ctx.push_rect(c_elem.slider.handle, handle_color)!;
|
||||
|
||||
return c_elem.events;
|
||||
}
|
||||
|
||||
/*
|
||||
* +-+
|
||||
* | |
|
||||
* | |
|
||||
* +-+
|
||||
* |#| handle
|
||||
* |#|
|
||||
* +-+
|
||||
* | |
|
||||
* | |
|
||||
* +-+
|
||||
*/
|
||||
fn ElemEvents! Ctx.slider_ver(&ctx, String label, Rect size)
|
||||
{
|
||||
Id id = label.hash();
|
||||
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Elem *c_elem = ctx.get_elem(id)!;
|
||||
// add it to the tree
|
||||
ctx.tree.add(id, ctx.active_div)!;
|
||||
|
||||
// 1. Fill the element fields
|
||||
c_elem.type = ETYPE_SLIDER;
|
||||
|
||||
// if the element is new or the parent was updated then redo layout
|
||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
||||
// 2. Layout
|
||||
c_elem.bounds = ctx.position_element(parent, size, true);
|
||||
c_elem.slider.handle = Rect{
|
||||
.x = c_elem.bounds.x,
|
||||
.y = (short)(c_elem.bounds.y + (int)(c_elem.bounds.h*(1.0-0.25) * c_elem.slider.value)),
|
||||
.w = c_elem.bounds.w,
|
||||
.h = (short)(c_elem.bounds.h * 0.25),
|
||||
};
|
||||
}
|
||||
|
||||
c_elem.events = ctx.get_elem_events(c_elem);
|
||||
if (parent.flags.has_focus && c_elem.events.mouse_hover) {
|
||||
if (point_in_rect(ctx.input.mouse.pos, c_elem.slider.handle) && c_elem.events.mouse_hold) {
|
||||
short y = (short)clamp(ctx.input.mouse.pos.y - c_elem.slider.handle.h/2, c_elem.bounds.y, c_elem.bounds.y + c_elem.bounds.h - c_elem.slider.handle.h);
|
||||
float v = (float)(c_elem.slider.handle.y-c_elem.bounds.y) / (float)(c_elem.bounds.h-c_elem.slider.handle.h);
|
||||
c_elem.slider.handle.y = y;
|
||||
c_elem.slider.value = v;
|
||||
c_elem.events.update = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the slider background and handle
|
||||
Color bg_color = uint_to_rgba(0x0000ffff);
|
||||
Color handle_color = uint_to_rgba(0x0ff000ff);
|
||||
ctx.push_rect(c_elem.bounds, bg_color)!;
|
||||
ctx.push_rect(c_elem.slider.handle, handle_color)!;
|
||||
|
||||
return c_elem.events;
|
||||
}
|
103
src/ugui_text.c3
Normal file
103
src/ugui_text.c3
Normal file
@ -0,0 +1,103 @@
|
||||
module ugui;
|
||||
|
||||
import std::io;
|
||||
import std::ascii;
|
||||
import grapheme;
|
||||
|
||||
<*
|
||||
@require off != null
|
||||
*>
|
||||
fn Codepoint str_to_codepoint(char[] str, usz* off)
|
||||
{
|
||||
Codepoint cp;
|
||||
isz b = grapheme::decode_utf8(str, str.len, &cp);
|
||||
if (b == 0 || b > str.len) {
|
||||
return 0;
|
||||
}
|
||||
*off = b;
|
||||
return cp;
|
||||
}
|
||||
|
||||
fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas)
|
||||
{
|
||||
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.get_glyph(cp, &n)!;
|
||||
line_len += gp.adv;
|
||||
if (n) { *update_atlas = true; }
|
||||
} else if (cp == '\n'){
|
||||
text_bounds.h += line_height + line_gap;
|
||||
line_len = 0;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
if (line_len > text_bounds.w) {
|
||||
text_bounds.w = line_len;
|
||||
}
|
||||
}
|
||||
|
||||
return text_bounds;
|
||||
}
|
||||
|
||||
fn void! Ctx.text_unbounded(&ctx, String label, String text)
|
||||
{
|
||||
Id id = label.hash();
|
||||
|
||||
Elem *parent = ctx.get_parent()!;
|
||||
Elem *c_elem = ctx.get_elem(id)!;
|
||||
// add it to the tree
|
||||
ctx.tree.add(id, ctx.active_div)!;
|
||||
|
||||
// 1. Fill the element fields
|
||||
// this resets the flags
|
||||
c_elem.type = ETYPE_TEXT;
|
||||
|
||||
short baseline = (short)ctx.font.ascender;
|
||||
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
|
||||
short line_gap = (short)ctx.font.linegap;
|
||||
bool update_atlas;
|
||||
// if the element is new or the parent was updated then redo layout
|
||||
if (c_elem.flags.is_new || parent.flags.updated) {
|
||||
Rect text_size = ctx.get_text_bounds(text, &update_atlas)!;
|
||||
|
||||
// 2. Layout
|
||||
c_elem.bounds = ctx.position_element(parent, text_size, true);
|
||||
|
||||
// 3. Fill the button specific fields
|
||||
c_elem.text.str = text;
|
||||
}
|
||||
|
||||
if (update_atlas) {
|
||||
// FIXME: atlas here is hardcoded, look at the todo in ugui_data
|
||||
Cmd up = {
|
||||
.type = CMD_UPDATE_ATLAS,
|
||||
.update_atlas = {
|
||||
.raw_buffer = ctx.font.atlas[0].buffer,
|
||||
.width = ctx.font.atlas[0].width,
|
||||
.height = ctx.font.atlas[0].height,
|
||||
},
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&up)!;
|
||||
}
|
||||
|
||||
Cmd bounds = {
|
||||
.type = CMD_RECT,
|
||||
.rect.rect = c_elem.bounds,
|
||||
.rect.color = uint_to_rgba(0x000000ff),
|
||||
};
|
||||
ctx.cmd_queue.enqueue(&bounds)!;
|
||||
|
||||
ctx.push_string(c_elem.bounds, text)!;
|
||||
}
|
333
src/vtree.c3
Normal file
333
src/vtree.c3
Normal file
@ -0,0 +1,333 @@
|
||||
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{0};
|
||||
$endif
|
||||
}
|
||||
|
||||
fn void! VTree.init(&tree, usz size)
|
||||
{
|
||||
tree.vector = mem::new_array(ElemType, size);
|
||||
defer catch { (void)mem::free(tree.vector); }
|
||||
|
||||
tree.refs = mem::new_array(isz, size);
|
||||
defer catch { (void)mem::free(tree.refs); }
|
||||
|
||||
tree.ordered_refs = mem::new_array(isz, size);
|
||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
||||
|
||||
// set all refs to -1, meaning invalid (free) element
|
||||
tree.refs[..] = -1;
|
||||
|
||||
tree.elements = 0;
|
||||
}
|
||||
|
||||
fn void VTree.free(&tree)
|
||||
{
|
||||
(void)mem::free(tree.vector);
|
||||
(void)mem::free(tree.refs);
|
||||
(void)mem::free(tree.ordered_refs);
|
||||
}
|
||||
|
||||
fn void VTree.pack(&tree)
|
||||
{
|
||||
// TODO: add a PACKED flag to skip this
|
||||
|
||||
isz free_spot = -1;
|
||||
for (usz i = 0; i < tree.size(); i++) {
|
||||
if (tree.refs[i] == -1) {
|
||||
free_spot = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
// find a item that can be packed
|
||||
if (free_spot >= 0 && tree.refs[i] >= 0) {
|
||||
isz old_ref = i;
|
||||
|
||||
// move the item
|
||||
tree.vector[free_spot] = tree.vector[i];
|
||||
tree.refs[free_spot] = tree.refs[i];
|
||||
|
||||
tree.vector[i] = @zero();
|
||||
tree.refs[i] = -1;
|
||||
|
||||
// and move all references
|
||||
for (usz j = 0; j < tree.size(); j++) {
|
||||
if (tree.refs[j] == old_ref) {
|
||||
tree.refs[j] = free_spot;
|
||||
}
|
||||
}
|
||||
|
||||
// mark the free spot as used
|
||||
free_spot = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn void! VTree.resize(&tree, usz newsize)
|
||||
{
|
||||
// return error when shrinking with too many elements
|
||||
if (newsize < tree.elements) {
|
||||
return VTreeError.CANNOT_SHRINK?;
|
||||
}
|
||||
|
||||
// pack the vector when shrinking to avoid data loss
|
||||
if ((int)newsize < tree.size()) {
|
||||
// FIXME: packing destroys all references to elements of vec
|
||||
// so shrinking may cause dangling pointers
|
||||
return VTreeError.CANNOT_SHRINK?;
|
||||
}
|
||||
|
||||
usz old_size = tree.size();
|
||||
|
||||
tree.vector = ((ElemType*)mem::realloc(tree.vector, newsize*ElemType.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.vector); }
|
||||
|
||||
tree.refs = ((isz*)mem::realloc(tree.refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.refs); }
|
||||
|
||||
tree.ordered_refs = ((isz*)mem::realloc(tree.ordered_refs, newsize*isz.sizeof))[:newsize];
|
||||
defer catch { (void)mem::free(tree.ordered_refs); }
|
||||
|
||||
if (newsize > tree.size()) {
|
||||
tree.vector[old_size..newsize-1] = @zero();
|
||||
tree.refs[old_size..newsize-1] = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// add an element to the tree, return it's ref
|
||||
fn isz! VTree.add(&tree, ElemType elem, isz parent)
|
||||
{
|
||||
// invalid parent
|
||||
if (!tree.ref_is_valid(parent)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// no space left
|
||||
if (tree.elements >= tree.size()) {
|
||||
return VTreeError.TREE_FULL?;
|
||||
}
|
||||
|
||||
// check if the parent exists
|
||||
// if there are no elements in the tree the first add will set the root
|
||||
if (!tree.ref_is_present(parent) && tree.elements != 0) {
|
||||
return VTreeError.REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
// get the first free spot
|
||||
isz free_spot = -1;
|
||||
for (usz i = 0; i < tree.size(); i++) {
|
||||
if (tree.refs[i] == -1) {
|
||||
free_spot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (free_spot < 0) {
|
||||
return VTreeError.TREE_FULL?;
|
||||
}
|
||||
|
||||
// finally add the element
|
||||
tree.vector[free_spot] = elem;
|
||||
tree.refs[free_spot] = parent;
|
||||
tree.elements++;
|
||||
|
||||
return free_spot;
|
||||
}
|
||||
|
||||
// prune the tree starting from the ref
|
||||
// returns the number of pruned elements
|
||||
fn usz! VTree.prune(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
tree.vector[ref] = @zero();
|
||||
tree.refs[ref] = -1;
|
||||
tree.elements--;
|
||||
|
||||
usz count = 1;
|
||||
for (usz i = 0; tree.elements > 0 && i < tree.size(); i++) {
|
||||
if (tree.refs[i] == ref) {
|
||||
count += tree.prune(i)!;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// find the size of the subtree starting from ref
|
||||
fn usz! VTree.subtree_size(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
usz count = 1;
|
||||
for (usz i = 0; i < tree.size(); i++) {
|
||||
// only root has the reference to itself
|
||||
if (tree.refs[i] == ref && ref != i) {
|
||||
count += tree.subtree_size(i)!;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
// iterate through the first level children, use a cursor like strtok_r
|
||||
fn isz! VTree.children_it(&tree, isz parent, isz *cursor)
|
||||
{
|
||||
if (cursor == null) {
|
||||
return VTreeError.INVALID_ARGUMENT?;
|
||||
}
|
||||
|
||||
// if the cursor is out of bounds then we are done for sure
|
||||
if (!tree.ref_is_valid(*cursor)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// same for the parent, if it's invalid it can't have children
|
||||
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
// find the first child, update the cursor and return the ref
|
||||
for (isz i = *cursor; i < tree.size(); i++) {
|
||||
if (tree.refs[i] == parent) {
|
||||
*cursor = i + 1;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// if no children are found return -1
|
||||
*cursor = -1;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* iterates trough every leaf of the subtree in the following manner
|
||||
* node [x], x: visit order
|
||||
* [0]
|
||||
* / | \
|
||||
* / [2] [3]
|
||||
* [1] |
|
||||
* / \ [6]
|
||||
* [4] [5]
|
||||
*/
|
||||
fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor)
|
||||
{
|
||||
if (cursor == null) {
|
||||
return VTreeError.INVALID_ARGUMENT?;
|
||||
}
|
||||
|
||||
isz[] queue = tree.ordered_refs;
|
||||
|
||||
// TODO: this could also be done when adding or removing elements
|
||||
// first call, create a ref array ordered like we desire
|
||||
if (*cursor == -1) {
|
||||
*cursor = 0;
|
||||
queue[..] = -1;
|
||||
|
||||
// iterate through the queue appending found children
|
||||
isz pos, off;
|
||||
do {
|
||||
// printf ("ref=%d\n", ref);
|
||||
for (isz i = 0; i < tree.size(); i++) {
|
||||
if (tree.refs[i] == ref) {
|
||||
queue[pos++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
for (; ref == queue[off] && off < tree.size(); off++);
|
||||
ref = queue[off];
|
||||
|
||||
} while (tree.ref_is_valid(ref));
|
||||
// This line is why tree.ordered_refs has to be size+1
|
||||
queue[off + 1] = -1;
|
||||
}
|
||||
|
||||
// PRINT_ARR(queue, tree.size());
|
||||
// return -1;
|
||||
|
||||
// on successive calls just iterate through the queue until we find an
|
||||
// invalid ref, if the user set the cursor to -1 it means it has found what
|
||||
// he needed, so free
|
||||
if (*cursor < 0) {
|
||||
return -1;
|
||||
} else if (tree.ref_is_valid(*cursor)) {
|
||||
return queue[(*cursor)++];
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
fn isz! VTree.parentof(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return VTreeError.REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
return tree.refs[ref];
|
||||
}
|
||||
|
||||
fn ElemType! VTree.get(&tree, isz ref)
|
||||
{
|
||||
if (!tree.ref_is_valid(ref)) {
|
||||
return VTreeError.INVALID_REFERENCE?;
|
||||
}
|
||||
|
||||
if (!tree.ref_is_present(ref)) {
|
||||
return VTreeError.REFERENCE_NOT_PRESENT?;
|
||||
}
|
||||
|
||||
return tree.vector[ref];
|
||||
}
|
||||
|
||||
fn void VTree.print(&tree)
|
||||
{
|
||||
for (isz i = 0; i < tree.size(); i++) {
|
||||
if (tree.refs[i] == -1) {
|
||||
continue;
|
||||
}
|
||||
io::printf("[%d] {parent=%d, data=", i, tree.refs[i]);
|
||||
io::print(tree.vector[i]);
|
||||
io::printn("}");
|
||||
}
|
||||
}
|
0
test/.gitkeep
Normal file
0
test/.gitkeep
Normal file
@ -1,11 +0,0 @@
|
||||
CFLAGS = -Wall -Wextra -Wpedantic -std=c11 -g
|
||||
LDFLAGS = -lSDL2 -lm
|
||||
|
||||
test: main.c ../ugui.c ../ugui.h ../def_style.h ../input.c
|
||||
gcc ${CFLAGS} -c ../ugui.c -o ugui.o
|
||||
gcc ${CFLAGS} -c ../input.c -o input.o
|
||||
gcc ${CFLAGS} -c main.c -o main.o
|
||||
gcc ${LDFLAGS} main.o ugui.o input.o -o test
|
||||
|
||||
clean:
|
||||
rm -f main.o ugui.o
|
268
test/main.c
268
test/main.c
@ -1,268 +0,0 @@
|
||||
#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.
@ -1,623 +0,0 @@
|
||||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||
// Sean Barrett 2014
|
||||
//
|
||||
// Useful for e.g. packing rectangular textures into an atlas.
|
||||
// Does not do rotation.
|
||||
//
|
||||
// Before #including,
|
||||
//
|
||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||
//
|
||||
// in the file that you want to have the implementation.
|
||||
//
|
||||
// Not necessarily the awesomest packing method, but better than
|
||||
// the totally naive one in stb_truetype (which is primarily what
|
||||
// this is meant to replace).
|
||||
//
|
||||
// Has only had a few tests run, may have issues.
|
||||
//
|
||||
// More docs to come.
|
||||
//
|
||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||
//
|
||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||
//
|
||||
// Please note: better rectangle packers are welcome! Please
|
||||
// implement them to the same API, but with a different init
|
||||
// function.
|
||||
//
|
||||
// Credits
|
||||
//
|
||||
// Library
|
||||
// Sean Barrett
|
||||
// Minor features
|
||||
// Martins Mozeiko
|
||||
// github:IntellectualKitty
|
||||
//
|
||||
// Bugfixes / warning fixes
|
||||
// Jeremy Jaussaud
|
||||
// Fabian Giesen
|
||||
//
|
||||
// Version history:
|
||||
//
|
||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||
// 0.99 (2019-02-07) warning fixes
|
||||
// 0.11 (2017-03-03) return packing success/fail result
|
||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||
// 0.09 (2016-08-27) fix compiler warnings
|
||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||
// 0.01: initial release
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// INCLUDE SECTION
|
||||
//
|
||||
|
||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||
|
||||
#define STB_RECT_PACK_VERSION 1
|
||||
|
||||
#ifdef STBRP_STATIC
|
||||
#define STBRP_DEF static
|
||||
#else
|
||||
#define STBRP_DEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct stbrp_context stbrp_context;
|
||||
typedef struct stbrp_node stbrp_node;
|
||||
typedef struct stbrp_rect stbrp_rect;
|
||||
|
||||
typedef int stbrp_coord;
|
||||
|
||||
#define STBRP__MAXVAL 0x7fffffff
|
||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||
// Assign packed locations to rectangles. The rectangles are of type
|
||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||
// are 'num_rects' many of them.
|
||||
//
|
||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||
// have the 'was_packed' flag set to 0.
|
||||
//
|
||||
// You should not try to access the 'rects' array from another thread
|
||||
// while this function is running, as the function temporarily reorders
|
||||
// the array while it executes.
|
||||
//
|
||||
// To pack into another rectangle, you need to call stbrp_init_target
|
||||
// again. To continue packing into the same rectangle, you can call
|
||||
// this function again. Calling this multiple times with multiple rect
|
||||
// arrays will probably produce worse packing results than calling it
|
||||
// a single time with the full rectangle array, but the option is
|
||||
// available.
|
||||
//
|
||||
// The function returns 1 if all of the rectangles were successfully
|
||||
// packed and 0 otherwise.
|
||||
|
||||
struct stbrp_rect
|
||||
{
|
||||
// reserved for your use:
|
||||
int id;
|
||||
|
||||
// input:
|
||||
stbrp_coord w, h;
|
||||
|
||||
// output:
|
||||
stbrp_coord x, y;
|
||||
int was_packed; // non-zero if valid packing
|
||||
|
||||
}; // 16 bytes, nominally
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||
// Initialize a rectangle packer to:
|
||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||
//
|
||||
// You must call this function every time you start packing into a new target.
|
||||
//
|
||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||
// the call (or calls) finish.
|
||||
//
|
||||
// Note: to guarantee best results, either:
|
||||
// 1. make sure 'num_nodes' >= 'width'
|
||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||
//
|
||||
// If you don't do either of the above things, widths will be quantized to multiples
|
||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||
//
|
||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||
// may run out of temporary storage and be unable to pack some rectangles.
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||
// Optionally call this function after init but before doing any packing to
|
||||
// change the handling of the out-of-temp-memory scenario, described above.
|
||||
// If you call init again, this will be reset to the default (false).
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||
// Optionally select which packing heuristic the library should use. Different
|
||||
// heuristics will produce better/worse results for different data sets.
|
||||
// If you call init again, this will be reset to the default.
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP_HEURISTIC_Skyline_default=0,
|
||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// the details of the following structures don't matter to you, but they must
|
||||
// be visible so you can handle the memory allocations for them
|
||||
|
||||
struct stbrp_node
|
||||
{
|
||||
stbrp_coord x,y;
|
||||
stbrp_node *next;
|
||||
};
|
||||
|
||||
struct stbrp_context
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int align;
|
||||
int init_mode;
|
||||
int heuristic;
|
||||
int num_nodes;
|
||||
stbrp_node *active_head;
|
||||
stbrp_node *free_head;
|
||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION SECTION
|
||||
//
|
||||
|
||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||
#ifndef STBRP_SORT
|
||||
#include <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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
5077
test/stb_truetype.h
5077
test/stb_truetype.h
File diff suppressed because it is too large
Load Diff
212
test/stbttf.h
212
test/stbttf.h
@ -1,212 +0,0 @@
|
||||
#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
|
14
test/test_bitsruct.c3
Normal file
14
test/test_bitsruct.c3
Normal file
@ -0,0 +1,14 @@
|
||||
bitstruct Bits : uint {
|
||||
bool a : 0;
|
||||
bool b : 1;
|
||||
bool c : 2;
|
||||
}
|
||||
|
||||
fn int main()
|
||||
{
|
||||
Bits a = {false, true, false};
|
||||
Bits b = {true, true, false};
|
||||
Bits c = a | b;
|
||||
|
||||
return 0;
|
||||
}
|
10
test/test_bittype.c3
Normal file
10
test/test_bittype.c3
Normal file
@ -0,0 +1,10 @@
|
||||
import std::io;
|
||||
import std::collections::bitset;
|
||||
|
||||
def Bits = bitset::BitSet(<128>);
|
||||
|
||||
fn void main()
|
||||
{
|
||||
Bits b;
|
||||
io::printn($typeof(b.data[0]).sizeof);
|
||||
}
|
14
test/test_custom_hash.c3
Normal file
14
test/test_custom_hash.c3
Normal file
@ -0,0 +1,14 @@
|
||||
import std::collections::map;
|
||||
|
||||
def Codepoint = uint;
|
||||
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();
|
||||
def CodeMap = map::HashMap(<Codepoint, Codepoint>);
|
||||
|
||||
|
||||
fn int main()
|
||||
{
|
||||
CodeMap m;
|
||||
m.new_init();
|
||||
m.free();
|
||||
return 0;
|
||||
}
|
6
test/test_font.c3
Normal file
6
test/test_font.c3
Normal file
@ -0,0 +1,6 @@
|
||||
import rl;
|
||||
|
||||
fn int main(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
26
test/test_union.c3
Normal file
26
test/test_union.c3
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
struct CmdA {
|
||||
int a, b;
|
||||
}
|
||||
|
||||
struct CmdB {
|
||||
float a, b;
|
||||
}
|
||||
|
||||
union AnyCmd {
|
||||
CmdA a;
|
||||
CmdB b;
|
||||
}
|
||||
|
||||
struct Cmd {
|
||||
int type;
|
||||
AnyCmd cmd;
|
||||
}
|
||||
|
||||
fn int main()
|
||||
{
|
||||
Cmd c;
|
||||
c.type = 1;
|
||||
c.cmd.a = {.a = 1, .b = 2};
|
||||
return 0;
|
||||
}
|
7
test/test_vtree.c3
Normal file
7
test/test_vtree.c3
Normal file
@ -0,0 +1,7 @@
|
||||
import std::io;
|
||||
import vtree;
|
||||
|
||||
fn int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
@ -1,41 +1,72 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
#define STB_TRUETYPE_IMPLEMENTATION
|
||||
#define STBTT_STATIC
|
||||
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||
module ugui;
|
||||
|
||||
#include <grapheme.h>
|
||||
#include <assert.h>
|
||||
import cache;
|
||||
//#include <grapheme.h>
|
||||
//#include <assert.h>
|
||||
|
||||
#include "font.h"
|
||||
#include "stb_truetype.h"
|
||||
#include "stb_image_write.h"
|
||||
#include "util.h"
|
||||
//#include "stb_truetype.h"
|
||||
//#include "stbimage_write.h"
|
||||
|
||||
#include "generic_cache.h"
|
||||
static inline unsigned int hash(unsigned int code)
|
||||
{
|
||||
// identity map the ascii range
|
||||
return code < 128 ? code : hash_u32(code);
|
||||
// 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>);
|
||||
|
||||
// identity map the ASCII range
|
||||
fn uint Codepoint.hash(Codepoint code) => code < 128 ? code : ((uint)code).hash();
|
||||
|
||||
struct FontAtlas {
|
||||
uint width, height;
|
||||
char* atlas;
|
||||
uint glyph_max_w, glyph_max_h;
|
||||
int size;
|
||||
int file_size;
|
||||
char *file;
|
||||
void *priv;
|
||||
}
|
||||
CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32)
|
||||
|
||||
|
||||
#define UTF8(c) (c&0x80)
|
||||
#define BDEPTH 1
|
||||
#define BORDER 4
|
||||
macro is_utf8(char c) => c & 0x80;
|
||||
const uint BDEPTH = 1;
|
||||
const uint BORDER = 4;
|
||||
|
||||
// FIXME: as of now only monospaced fonts work correctly since no kerning information
|
||||
// is stored
|
||||
// FIXME: as of now only monospaced fonts look decent since no
|
||||
// kerning information is stored
|
||||
|
||||
|
||||
struct priv {
|
||||
struct Priv @private {
|
||||
stbtt_fontinfo stb;
|
||||
float scale;
|
||||
int baseline;
|
||||
unsigned char *bitmap;
|
||||
struct cache c;
|
||||
};
|
||||
#define PRIV(x) ((struct priv *)x->priv)
|
||||
}
|
||||
//#define PRIV(x) ((struct priv *)x->priv)
|
||||
|
||||
|
||||
struct font_atlas * font_init(void)
|
4
text_rendering/.gitignore
vendored
4
text_rendering/.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
*.png
|
||||
test
|
||||
obj/**
|
||||
objlist
|
@ -1,18 +0,0 @@
|
||||
CC = gcc
|
||||
# instrumentation flags
|
||||
INFLAGS = -fsanitize=address,builtin,undefined
|
||||
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL ${INFLAGS}
|
||||
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 -Wno-unused-function -fno-omit-frame-pointer ${INFLAGS}
|
||||
|
||||
.PHONY: clean all
|
||||
all: test
|
||||
|
||||
font.o: font.c font.h stb_truetype.h stb_image_write.h util.h \
|
||||
generic_cache.h generic_hash.h
|
||||
main.o: main.c ren.h util.h
|
||||
ren.o: ren.c util.h font.h ren.h generic_stack.h
|
||||
util.o: util.c util.h
|
||||
test: font.o main.o ren.o util.o
|
||||
${CC} ${LDFLAGS} -o test font.o main.o ren.o util.o
|
||||
clean:
|
||||
rm -f test font.o main.o ren.o util.o
|
@ -1,8 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
in vec4 col;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragColor = col;
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
uniform ivec2 viewsize;
|
||||
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 2) in vec4 color;
|
||||
|
||||
out vec4 col;
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
|
||||
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
|
||||
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
|
||||
gl_Position = pos;
|
||||
col = color;
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
#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
|
@ -1,18 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// texture uv coordinate in texture space
|
||||
in vec2 uv;
|
||||
uniform sampler2DRect ts;
|
||||
|
||||
const vec3 textcolor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
|
||||
void main()
|
||||
{
|
||||
//gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
|
||||
gl_FragColor = vec4(textcolor, texture(ts, uv));
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
#version 330 core
|
||||
|
||||
// viewsize.x = viewport width in pixels; viewsize.y = viewport height in pixels
|
||||
uniform ivec2 viewsize;
|
||||
uniform ivec2 texturesize;
|
||||
|
||||
// both position and and uv are in pixels, they where converted to floats when
|
||||
// passed to the shader
|
||||
layout(location = 0) in vec2 position;
|
||||
layout(location = 1) in vec2 txcoord;
|
||||
|
||||
out vec2 uv;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 v = vec2(float(viewsize.x), float(viewsize.y));
|
||||
|
||||
// vec2 p = vec2(position.x*2.0f/v.x - 1.0f, position.y*2.0f/v.y - 1.0f);
|
||||
vec2 p = vec2(position.x*2.0/v.x - 1.0, 1.0 - position.y*2.0/v.y);
|
||||
vec4 pos = vec4(p.x, p.y, 0.0f, 1.0f);
|
||||
|
||||
gl_Position = pos;
|
||||
// since the texture is a GL_TEXTURE_RECTANGLE the coordintes do not need to
|
||||
// be normalized
|
||||
uv = vec2(txcoord.x, txcoord.y);
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
#ifndef CACHE_GENERIC_H
|
||||
#define CACHE_GENERIC_H
|
||||
|
||||
|
||||
#include "generic_hash.h"
|
||||
|
||||
#define CACHE_SIZE 512
|
||||
#define CACHE_NCYCLES (CACHE_SIZE*2/3)
|
||||
#define CACHE_BSIZE (((CACHE_SIZE+0x3f)&(~0x3f))>>6)
|
||||
#define CACHE_BRESET(b) for (int i = 0; i < CACHE_BSIZE; b[i++] = 0);
|
||||
#define CACHE_BSET(b, x) b[(x)>>6] |= (uint64_t)1<<((x)&63)
|
||||
#define CACHE_BTEST(b, x) (b[(x)>>6]&((uint64_t)1<<((x)&63)))
|
||||
|
||||
// FIXME: this cache implementation is not really generic since it expects an unsigned
|
||||
// as the code and not a generic type
|
||||
|
||||
|
||||
#define CACHE_CYCLE(c) \
|
||||
{ \
|
||||
if (++(c->cycles) > CACHE_NCYCLES) { \
|
||||
for (int i = 0; i < CACHE_BSIZE; i++) { \
|
||||
c->present[i] &= c->used[i]; \
|
||||
c->used[i] = 0; \
|
||||
} \
|
||||
c->cycles = 0; \
|
||||
} \
|
||||
}
|
||||
|
||||
|
||||
#define CACHE_DECL(name, type, hashfn, cmpfn) \
|
||||
HASH_DECL(name##table, uint32_t, uint32_t, hashfn, cmpfn) \
|
||||
struct name { \
|
||||
struct name##table_ref *table; \
|
||||
type *array; \
|
||||
uint64_t *present, *used; \
|
||||
int cycles; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
/* FIXME: check for allocation errors */ \
|
||||
struct name name##_init(void) \
|
||||
{ \
|
||||
struct name##table_ref *t = name##table_create(CACHE_SIZE); \
|
||||
type *a = malloc(sizeof(type)*CACHE_SIZE); \
|
||||
uint64_t *p = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
|
||||
uint64_t *u = malloc(sizeof(uint64_t)*CACHE_BSIZE); \
|
||||
CACHE_BRESET(p); \
|
||||
CACHE_BRESET(u); \
|
||||
return (struct name){.table=t, .array=a, .present=p, .used=u, 0}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
void name##_free(struct name *cache) \
|
||||
{ \
|
||||
if (cache) { \
|
||||
name##table_destroy(cache->table); \
|
||||
free(cache->array); \
|
||||
free(cache->present); \
|
||||
free(cache->used); \
|
||||
} \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_search(struct name *cache, uint32_t code) \
|
||||
{ \
|
||||
if (!cache) return NULL; \
|
||||
struct name##table_entry *r; \
|
||||
r = name##table_search(cache->table, code); \
|
||||
/* MISS */ \
|
||||
if (!r || !cmpfn(code, r->code)) \
|
||||
return NULL; \
|
||||
/* MISS, the data is not valid (not present) */ \
|
||||
if (!CACHE_BTEST(cache->present, r->data)) \
|
||||
return NULL; \
|
||||
/* HIT, set as recently used */ \
|
||||
CACHE_BSET(cache->used, r->data); \
|
||||
return (&cache->array[r->data]); \
|
||||
} \
|
||||
\
|
||||
\
|
||||
/* Look for a free spot in the present bitmap and return its index */ \
|
||||
/* If there is no free space left then just return the first position */ \
|
||||
int name##_get_free_spot(struct name *cache) \
|
||||
{ \
|
||||
if (!cache) return -1; \
|
||||
int x = 0; \
|
||||
for (int b = 0; b < CACHE_BSIZE; b++) { \
|
||||
if (cache->present[b] == 0) x = 64; \
|
||||
else x = __builtin_clzll(cache->present[b]); \
|
||||
x = 64-x; \
|
||||
if (!CACHE_BTEST(cache->present, x+64*b)) \
|
||||
return x+64*b; \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_insert_at(struct name *cache, const type *g, uint32_t code, int x)\
|
||||
{ \
|
||||
if (!cache) return NULL; \
|
||||
type *spot = NULL; \
|
||||
/* check if the spot is valid */ \
|
||||
if (x < 0) return NULL; \
|
||||
/* Set used and present */ \
|
||||
CACHE_BSET(cache->present, x); \
|
||||
CACHE_BSET(cache->used, x); \
|
||||
CACHE_CYCLE(cache) \
|
||||
spot = &(cache->array[x]); \
|
||||
*spot = *g; \
|
||||
struct name##table_entry e = { .code = code, .data = x}; \
|
||||
if (!name##table_insert(cache->table, &e)) \
|
||||
return NULL; \
|
||||
return spot; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
const type * name##_insert(struct name *cache, const type *g, uint32_t code, int *x)\
|
||||
{ \
|
||||
int y; \
|
||||
if (!x) x = &y; \
|
||||
*x = name##_get_free_spot(cache); \
|
||||
return name##_insert_at(cache, g, code, *x); \
|
||||
} \
|
||||
|
||||
|
||||
#endif
|
@ -1,134 +0,0 @@
|
||||
#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
|
@ -1,118 +0,0 @@
|
||||
#ifndef _STACK_GENERIC_H
|
||||
#define _STACK_GENERIC_H
|
||||
|
||||
|
||||
#define STACK_STEP 8
|
||||
#define STACK_SALT 0xbabb0cac
|
||||
|
||||
// FIXME: find a way to not re-hash the whole stack when removing one item
|
||||
|
||||
// incremental hash for every grow
|
||||
#if STACK_DISABLE_HASH
|
||||
#define STACK_HASH(p, s, h) {}
|
||||
#else
|
||||
#define STACK_HASH(p, s, h) \
|
||||
{ \
|
||||
unsigned char *v = (unsigned char *)(p); \
|
||||
for (int x = (s); x; x--) { \
|
||||
(h) += v[x-1]; \
|
||||
(h) += (h) << 10; \
|
||||
(h) ^= (h) >> 6; \
|
||||
} \
|
||||
(h) += (h) << 3; \
|
||||
(h) ^= (h) >> 11; \
|
||||
(h) += (h) << 15; \
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// TODO: add a rolling hash
|
||||
#define STACK_DECL(stackname, type) \
|
||||
struct stackname { \
|
||||
type *items; \
|
||||
int size, idx, old_idx; \
|
||||
unsigned int hash, old_hash; \
|
||||
}; \
|
||||
\
|
||||
\
|
||||
struct stackname stackname##_init(void) \
|
||||
{ \
|
||||
return (struct stackname){0, .hash = STACK_SALT}; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_grow(struct stackname *stack, int step) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->items = realloc(stack->items, (stack->size+step)*sizeof(type)); \
|
||||
if(!stack->items) \
|
||||
return -1; \
|
||||
memset(&(stack->items[stack->size]), 0, step*sizeof(*(stack->items))); \
|
||||
stack->size += step; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_push(struct stackname *stack, type *e) \
|
||||
{ \
|
||||
if (!stack || !e) \
|
||||
return -1; \
|
||||
if (stack->idx >= stack->size) \
|
||||
if (stackname##_grow(stack, STACK_STEP)) \
|
||||
return -1; \
|
||||
stack->items[stack->idx++] = *e; \
|
||||
STACK_HASH(e, sizeof(type), stack->hash); \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
type stackname##_pop(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack || stack->idx == 0 || stack->size == 0) \
|
||||
return (type){0}; \
|
||||
stack->hash = STACK_SALT; \
|
||||
STACK_HASH(stack->items, sizeof(type)*(stack->idx-1), stack->hash); \
|
||||
return stack->items[stack->idx--]; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_clear(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
stack->old_idx = stack->idx; \
|
||||
stack->old_hash = stack->hash; \
|
||||
stack->hash = STACK_SALT; \
|
||||
stack->idx = 0; \
|
||||
return 0; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->hash != stack->old_hash; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_size_changed(struct stackname *stack) \
|
||||
{ \
|
||||
if (!stack) \
|
||||
return -1; \
|
||||
return stack->size != stack->old_idx; \
|
||||
} \
|
||||
\
|
||||
\
|
||||
int stackname##_free(struct stackname *stack) \
|
||||
{ \
|
||||
if (stack) { \
|
||||
stackname##_clear(stack); \
|
||||
if (stack->items) \
|
||||
free(stack->items); \
|
||||
} \
|
||||
return 0; \
|
||||
} \
|
||||
|
||||
#endif
|
@ -1,46 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
genobj() {
|
||||
#echo "$1" | sed -e 's/^/obj\//' -e 's/\.c/\.o/'
|
||||
echo "$1" | sed -e 's/\.c/\.o/'
|
||||
}
|
||||
|
||||
genrule() {
|
||||
#gcc -MM "$1" | sed 's/^/obj\//'
|
||||
gcc -MM "$1"
|
||||
}
|
||||
|
||||
|
||||
rm -f objlist
|
||||
|
||||
cat > Makefile << EOF
|
||||
CC = gcc
|
||||
# instrumentation flags
|
||||
INFLAGS = -fsanitize=address,builtin,undefined
|
||||
LDFLAGS = -lm -lgrapheme -lSDL2 -lGLEW -lGL \${INFLAGS}
|
||||
CFLAGS = -ggdb3 -Wall -Wextra -pedantic -std=c11 \
|
||||
-Wno-unused-function -fno-omit-frame-pointer \${INFLAGS}
|
||||
|
||||
.PHONY: clean all
|
||||
all: test
|
||||
|
||||
EOF
|
||||
|
||||
for f in *.c; do
|
||||
genrule $f >> Makefile
|
||||
genobj $f >> objlist
|
||||
done
|
||||
|
||||
mainrule='test: '
|
||||
linkcmd=' ${CC} ${LDFLAGS} -o test '
|
||||
cleanrule='clean:
|
||||
rm -f test '
|
||||
while IFS="" read -r line; do
|
||||
mainrule="$mainrule $line"
|
||||
linkcmd="$linkcmd $line"
|
||||
cleanrule="$cleanrule $line"
|
||||
done < objlist
|
||||
|
||||
echo "$mainrule" >> Makefile
|
||||
echo "$linkcmd" >> Makefile
|
||||
echo "$cleanrule" >> Makefile
|
@ -1,96 +0,0 @@
|
||||
#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;
|
||||
}
|
@ -1,826 +0,0 @@
|
||||
#include <SDL2/SDL.h>
|
||||
#include <GL/glew.h>
|
||||
#include <SDL2/SDL_opengl.h>
|
||||
#include <SDL2/SDL_video.h>
|
||||
#include <ctype.h>
|
||||
#include <grapheme.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "util.h"
|
||||
#include "font.h"
|
||||
#include "ren.h"
|
||||
|
||||
|
||||
#define GLERR(x) \
|
||||
{ \
|
||||
int a = glGetError(); \
|
||||
if (a != GL_NO_ERROR) \
|
||||
printf("(%s:%d %s:%s) glError: 0x%x %s\n", \
|
||||
__FILE__, __LINE__, __func__, x, a, glerr[a&0xff]); \
|
||||
}
|
||||
#define GL(f) f; GLERR(#f)
|
||||
#define REN_RET(a,b) {ren_errno = b; return a;}
|
||||
|
||||
|
||||
enum REN_ERR {
|
||||
REN_SUCCESS = 0,
|
||||
REN_ERRNO,
|
||||
REN_INVAL,
|
||||
REN_VERTEX,
|
||||
REN_FRAGMENT,
|
||||
REN_PROGRAM,
|
||||
REN_COMPILE,
|
||||
REN_LINK,
|
||||
REN_TEXTURE,
|
||||
REN_CONTEXT,
|
||||
REN_GLEW,
|
||||
REN_FONT,
|
||||
REN_BUFFER,
|
||||
REN_UNIFORM,
|
||||
};
|
||||
|
||||
|
||||
// TODO: make a macro for enum-associated string arrays
|
||||
const char * ren_err_msg[] = {
|
||||
[REN_SUCCESS] = "Success",
|
||||
[REN_ERRNO] = "Look at errno",
|
||||
[REN_INVAL] = "Invalid or NULL arguments",
|
||||
[REN_VERTEX] = "Failed to create opengl vertex shader",
|
||||
[REN_FRAGMENT] = "Failed to create opengl fragment shader",
|
||||
[REN_PROGRAM] = "Failed to create opengl program",
|
||||
[REN_COMPILE] = "Failed to compile shaders",
|
||||
[REN_LINK] = "Failed to link shaders",
|
||||
[REN_TEXTURE] = "Failed to create texture",
|
||||
[REN_CONTEXT] = "Failed to create SDL OpenGL context",
|
||||
[REN_GLEW] = "GLEW Error",
|
||||
[REN_FONT] = "Font Error",
|
||||
[REN_BUFFER] = "Failed to create opengl buffer",
|
||||
[REN_UNIFORM] = "Failed to get uniform location",
|
||||
};
|
||||
|
||||
#define ELEM(x) [x&0xff] = #x,
|
||||
const char *glerr[] = {
|
||||
ELEM(GL_INVALID_ENUM)
|
||||
ELEM(GL_INVALID_VALUE)
|
||||
ELEM(GL_INVALID_OPERATION)
|
||||
ELEM(GL_OUT_OF_MEMORY)
|
||||
};
|
||||
#undef ELEM
|
||||
|
||||
|
||||
// different stacks
|
||||
#include "generic_stack.h"
|
||||
STACK_DECL(vtstack, struct v_text)
|
||||
STACK_DECL(vcstack, struct v_col)
|
||||
|
||||
|
||||
struct ren_font {
|
||||
struct font_atlas *font;
|
||||
GLuint texture;
|
||||
};
|
||||
|
||||
struct {
|
||||
SDL_GLContext *gl;
|
||||
struct ren_font *fonts;
|
||||
int fonts_no;
|
||||
GLuint font_prog;
|
||||
GLuint box_prog;
|
||||
GLuint font_buffer;
|
||||
GLuint box_buffer;
|
||||
GLint viewsize_loc;
|
||||
GLint box_viewsize_loc;
|
||||
GLint texturesize_loc;
|
||||
struct vtstack font_stack;
|
||||
struct vcstack box_stack;
|
||||
int width, height;
|
||||
int s_x, s_y, s_w, s_h;
|
||||
int tabsize;
|
||||
} ren = {0};
|
||||
|
||||
|
||||
static int ren_errno;
|
||||
|
||||
|
||||
// print shader compilation errors
|
||||
static int shader_compile_error(GLuint shader, const char *path)
|
||||
{
|
||||
GLint status;
|
||||
GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &status))
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
GL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &log_length))
|
||||
|
||||
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
|
||||
GL(glGetShaderInfoLog(shader, log_length, NULL, log_str))
|
||||
|
||||
const char *shader_type_str = NULL;
|
||||
GLint shader_type;
|
||||
GL(glGetShaderiv(shader, GL_SHADER_TYPE, &shader_type))
|
||||
switch(shader_type) {
|
||||
case GL_VERTEX_SHADER: shader_type_str = "vertex"; break;
|
||||
case GL_GEOMETRY_SHADER: shader_type_str = "geometry"; break;
|
||||
case GL_FRAGMENT_SHADER: shader_type_str = "fragment"; break;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Compile failure in %s shader %s:\n%s\n", shader_type_str, path, log_str);
|
||||
efree(log_str);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
// print shader link errors
|
||||
static int shader_link_error(GLuint prog)
|
||||
{
|
||||
GLint status;
|
||||
GL(glGetProgramiv(prog, GL_LINK_STATUS, &status))
|
||||
if (status != GL_FALSE)
|
||||
return 0;
|
||||
|
||||
GLint log_length;
|
||||
GL(glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &log_length))
|
||||
|
||||
GLchar *log_str = emalloc((log_length + 1)*sizeof(GLchar));
|
||||
GL(glGetProgramInfoLog(prog, log_length, NULL, log_str))
|
||||
fprintf(stderr, "Linker failure: %s\n", log_str);
|
||||
efree(log_str);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
static GLuint shader_compile(const char *path, const char *shader, GLuint type)
|
||||
{
|
||||
GLuint s;
|
||||
// initialize the vertex shader and get the corresponding id
|
||||
s = GL(glCreateShader(type))
|
||||
if (!s) REN_RET(0, REN_VERTEX)
|
||||
// get the shader into opengl
|
||||
GL(glShaderSource(s, 1, &shader, NULL))
|
||||
GL(glCompileShader(s))
|
||||
if (shader_compile_error(s, path))
|
||||
REN_RET(0, REN_COMPILE)
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
const char * ren_strerror(void)
|
||||
{
|
||||
return ren_err_msg[ren_errno % (sizeof(ren_err_msg)/sizeof(char *))];
|
||||
}
|
||||
|
||||
|
||||
// compile a vertex shader (vs_path) and a fragment shader (fs_path) into an opengl
|
||||
// program and return it's index
|
||||
static GLuint ren_compile_program(const char *vs_path, const char *fs_path)
|
||||
{
|
||||
GLuint gl_vertshader, gl_fragshader, prog;
|
||||
|
||||
if (!vs_path || !fs_path)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
char *str = NULL;
|
||||
|
||||
dump_file(vs_path, &str, NULL);
|
||||
if (!str) REN_RET(0, REN_ERRNO)
|
||||
gl_vertshader = shader_compile(vs_path, str, GL_VERTEX_SHADER);
|
||||
efree(str);
|
||||
if (!gl_vertshader)
|
||||
return 0;
|
||||
|
||||
dump_file(fs_path, &str, NULL);
|
||||
if (!str) REN_RET(0, REN_ERRNO)
|
||||
gl_fragshader = shader_compile(fs_path, str, GL_FRAGMENT_SHADER);
|
||||
efree(str);
|
||||
if (!gl_fragshader)
|
||||
return 0;
|
||||
|
||||
|
||||
// create the main program object, it is an amalgamation of all shaders
|
||||
prog = GL(glCreateProgram())
|
||||
if (!prog) REN_RET(0, REN_PROGRAM)
|
||||
|
||||
// attach the shaders to the program (set which shaders are present)
|
||||
GL(glAttachShader(prog, gl_vertshader))
|
||||
GL(glAttachShader(prog, gl_fragshader))
|
||||
// then link the program (basically the linking stage of the program)
|
||||
GL(glLinkProgram(prog))
|
||||
if (shader_link_error(prog))
|
||||
REN_RET(0, REN_LINK)
|
||||
|
||||
// after linking the shaders can be detached and the source freed from
|
||||
// memory since the program is ready to use
|
||||
GL(glDetachShader(prog, gl_vertshader))
|
||||
GL(glDetachShader(prog, gl_fragshader))
|
||||
|
||||
return prog;
|
||||
}
|
||||
|
||||
|
||||
static void set_texture_parameters(GLuint type, GLuint wrap_s, GLuint wrap_t, GLuint upscale, GLuint downscale)
|
||||
{
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap_s))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap_t))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, upscale))
|
||||
GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, downscale))
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturergb_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, buf))
|
||||
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturergba_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf))
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturer_2d(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_2D, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
|
||||
set_texture_parameters(GL_TEXTURE_2D, GL_REPEAT, GL_REPEAT, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
static GLuint ren_texturer_rect(const char *buf, int w, int h, int upscale, int downscale)
|
||||
{
|
||||
GLuint t;
|
||||
|
||||
if (!buf || w <= 0 || h <= 0)
|
||||
REN_RET(0, REN_INVAL)
|
||||
|
||||
GL(glGenTextures(1, &t))
|
||||
if (!t) REN_RET(0, REN_TEXTURE)
|
||||
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, t))
|
||||
GL(glTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, buf))
|
||||
// a limitation of recatngle textures is that the wrapping mode is limited
|
||||
// to either clamp-to-edge or clamp-to-border
|
||||
set_texture_parameters(GL_TEXTURE_RECTANGLE, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, upscale, downscale);
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
|
||||
// FIXME: update only the newly generated character instead of the whole texture
|
||||
static int update_font_texture(int idx)
|
||||
{
|
||||
GL(glUseProgram(ren.font_prog))
|
||||
GL(glTexSubImage2D(
|
||||
GL_TEXTURE_RECTANGLE,
|
||||
0, 0, 0,
|
||||
ren.fonts[idx].font->width,
|
||||
ren.fonts[idx].font->height,
|
||||
GL_RED,
|
||||
GL_UNSIGNED_BYTE,
|
||||
ren.fonts[idx].font->atlas))
|
||||
//font_dump(ren.fonts[idx].font, "./atlas.png");
|
||||
GL(glUseProgram(0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// loads a font and returns it's index in the fonts array
|
||||
static int ren_load_font(int size, const char *path)
|
||||
{
|
||||
int idx = ren.fonts_no;
|
||||
struct font_atlas *f;
|
||||
ren.fonts = erealloc(ren.fonts, sizeof(struct ren_font)*(idx+1));
|
||||
ren.fonts[idx].font = font_init();
|
||||
f = ren.fonts[idx].font;
|
||||
if (!f)
|
||||
REN_RET(-1, REN_FONT)
|
||||
|
||||
if (!path)
|
||||
path = DEFAULT_FONT;
|
||||
|
||||
if (font_load(f, path, size))
|
||||
REN_RET(-1, REN_FONT)
|
||||
//font_dump(f, "./atlas.png");
|
||||
|
||||
// load font texture (atlas)
|
||||
ren.fonts[idx].texture = ren_texturer_rect(
|
||||
(const char *)f->atlas,
|
||||
f->width,
|
||||
f->height,
|
||||
GL_LINEAR, GL_LINEAR);
|
||||
if (!ren.fonts[idx].texture)
|
||||
return -1;
|
||||
|
||||
ren.fonts_no = idx+1;
|
||||
return idx;
|
||||
}
|
||||
|
||||
|
||||
// returns the index to the ren.fonts array to the font with the correct size
|
||||
// return -1 on errror
|
||||
static int ren_get_font(int size)
|
||||
{
|
||||
for (int i = 0; i < ren.fonts_no; i++) {
|
||||
if (ren.fonts[i].font->size == size)
|
||||
return i;
|
||||
}
|
||||
// TODO: add a way to change font family
|
||||
return ren_load_font(size, NULL);
|
||||
}
|
||||
|
||||
|
||||
int ren_init(SDL_Window *w)
|
||||
{
|
||||
// Initialize OpenGL
|
||||
if (!w)
|
||||
REN_RET(-1, REN_INVAL)
|
||||
// using version 3 does not allow to use glVertexAttribPointer() without
|
||||
// vertex buffer objects, so use compatibility mode
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
ren.gl = SDL_GL_CreateContext(w);
|
||||
if (!ren.gl) {
|
||||
printf("SDL: %s\n", SDL_GetError());
|
||||
REN_RET(-1, REN_CONTEXT)
|
||||
}
|
||||
|
||||
GL(glEnable(GL_BLEND))
|
||||
GL(glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA))
|
||||
GL(glDisable(GL_CULL_FACE))
|
||||
GL(glDisable(GL_DEPTH_TEST))
|
||||
GL(glEnable(GL_SCISSOR_TEST))
|
||||
GL(glEnable(GL_TEXTURE_2D))
|
||||
GL(glEnable(GL_TEXTURE_RECTANGLE))
|
||||
|
||||
GLenum glew_err = glewInit();
|
||||
if (glew_err != GLEW_OK)
|
||||
REN_RET(glew_err, REN_GLEW);
|
||||
|
||||
// Create stacks
|
||||
ren.font_stack = vtstack_init();
|
||||
ren.box_stack = vcstack_init();
|
||||
// generate the font buffer object
|
||||
GL(glGenBuffers(1, &ren.font_buffer))
|
||||
if (!ren.font_buffer) REN_RET(-1, REN_BUFFER)
|
||||
GL(glGenBuffers(1, &ren.box_buffer))
|
||||
if (!ren.box_buffer) REN_RET(-1, REN_BUFFER)
|
||||
|
||||
// Compile font shaders
|
||||
ren.font_prog = ren_compile_program(FONT_VERSHADER, FONT_FRAGSHADER);
|
||||
if (!ren.font_prog) return -1;
|
||||
// create the uniforms, if the returned value is -1 then the uniform may have
|
||||
// been optimized away, in any case do not return, just give a warning
|
||||
ren.viewsize_loc = GL(glGetUniformLocation(ren.font_prog, "viewsize"))
|
||||
if (ren.viewsize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "viewsize");
|
||||
ren.texturesize_loc = GL(glGetUniformLocation(ren.font_prog, "texturesize"))
|
||||
if (ren.texturesize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "texturesize");
|
||||
|
||||
// Compile box shaders
|
||||
ren.box_prog = ren_compile_program(BOX_VERSHADER, BOX_FRAGSHADER);
|
||||
if (!ren.box_prog) return -1;
|
||||
ren.box_viewsize_loc = GL(glGetUniformLocation(ren.box_prog, "viewsize"))
|
||||
if (ren.box_viewsize_loc == -1)
|
||||
printf("uniform %s was optimized away\n", "viewsize");
|
||||
|
||||
// Finishing touches
|
||||
ren.tabsize = REN_TABSIZE;
|
||||
int width, height;
|
||||
SDL_GetWindowSize(w, &width, &height);
|
||||
ren_update_viewport(width, height);
|
||||
GL(glClearColor(0.3f, 0.3f, 0.3f, 0.f))
|
||||
GL(glClear(GL_COLOR_BUFFER_BIT))
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_clear(void)
|
||||
{
|
||||
GL(glScissor(0, 0, ren.width, ren.height))
|
||||
GL(glClear(GL_COLOR_BUFFER_BIT));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// idx refers to the fonts array index, this means that when drawing the font stack
|
||||
// only one font size at the time can be used
|
||||
static int ren_draw_font_stack(int idx)
|
||||
{
|
||||
struct font_atlas *font = ren.fonts[idx].font;
|
||||
GLuint font_texture = ren.fonts[idx].texture;
|
||||
|
||||
GL(glUseProgram(ren.font_prog))
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, font_texture))
|
||||
|
||||
GL(glViewport(0, 0, ren.width, ren.height))
|
||||
// this has caused me some trouble, convert from image coordiates to viewport
|
||||
GL(glScissor(ren.s_x, ren.height-ren.s_y-ren.s_h, ren.s_w, ren.s_h))
|
||||
GL(glUniform2i(ren.viewsize_loc, ren.width, ren.height))
|
||||
GL(glUniform2i(ren.texturesize_loc, font->width, font->height))
|
||||
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.font_buffer))
|
||||
if (vtstack_changed(&ren.font_stack)) {
|
||||
if (vtstack_size_changed(&ren.font_stack)) {
|
||||
GL(glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
ren.font_stack.idx*sizeof(struct v_text),
|
||||
ren.font_stack.items,
|
||||
GL_DYNAMIC_DRAW))
|
||||
} else {
|
||||
GL(glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
ren.font_stack.idx*sizeof(struct v_text),
|
||||
ren.font_stack.items))
|
||||
}
|
||||
}
|
||||
// when passing ints to glVertexAttribPointer they are automatically
|
||||
// converted to floats
|
||||
GL(glVertexAttribPointer(
|
||||
REN_VERTEX_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_text),
|
||||
0))
|
||||
GL(glVertexAttribPointer(
|
||||
REN_UV_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_text),
|
||||
(void*)sizeof(vec2_i)))
|
||||
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glEnableVertexAttribArray(REN_UV_IDX))
|
||||
|
||||
GL(glDrawArrays(GL_TRIANGLES, 0, ren.font_stack.idx))
|
||||
|
||||
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glDisableVertexAttribArray(REN_UV_IDX))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glBindTexture(GL_TEXTURE_RECTANGLE, 0))
|
||||
GL(glUseProgram(0))
|
||||
|
||||
vtstack_clear(&ren.font_stack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ren_draw_box_stack(void)
|
||||
{
|
||||
GL(glUseProgram(ren.box_prog))
|
||||
|
||||
GL(glViewport(0, 0, ren.width, ren.height))
|
||||
GL(glScissor(0, 0, ren.width, ren.height))
|
||||
GL(glUniform2i(ren.box_viewsize_loc, ren.width, ren.height))
|
||||
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, ren.box_buffer))
|
||||
if(vcstack_changed(&ren.box_stack)) {
|
||||
if (vcstack_size_changed(&ren.box_stack)) {
|
||||
GL(glBufferData(
|
||||
GL_ARRAY_BUFFER,
|
||||
ren.box_stack.idx*sizeof(struct v_col),
|
||||
ren.box_stack.items,
|
||||
GL_DYNAMIC_DRAW))
|
||||
} else {
|
||||
GL(glBufferSubData(
|
||||
GL_ARRAY_BUFFER,
|
||||
0,
|
||||
ren.box_stack.idx*sizeof(struct v_col),
|
||||
ren.box_stack.items))
|
||||
}
|
||||
}
|
||||
// when passing ints to glVertexAttribPointer they are automatically
|
||||
// converted to floats
|
||||
GL(glVertexAttribPointer(
|
||||
REN_VERTEX_IDX,
|
||||
2,
|
||||
GL_INT,
|
||||
GL_FALSE,
|
||||
sizeof(struct v_col),
|
||||
0))
|
||||
// the color gets normalized
|
||||
GL(glVertexAttribPointer(
|
||||
REN_COLOR_IDX,
|
||||
4,
|
||||
GL_INT,
|
||||
GL_TRUE,
|
||||
sizeof(struct v_col),
|
||||
(void*)sizeof(vec2_i)))
|
||||
GL(glEnableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glEnableVertexAttribArray(REN_COLOR_IDX))
|
||||
|
||||
GL(glDrawArrays(GL_TRIANGLES, 0, ren.box_stack.idx))
|
||||
|
||||
GL(glDisableVertexAttribArray(REN_VERTEX_IDX))
|
||||
GL(glDisableVertexAttribArray(REN_COLOR_IDX))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glUseProgram(0))
|
||||
|
||||
vcstack_clear(&ren.box_stack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_update_viewport(int w, int h)
|
||||
{
|
||||
ren.width = w;
|
||||
ren.height = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_set_scissor(int x, int y, int w, int h)
|
||||
{
|
||||
ren.s_x = x;
|
||||
ren.s_y = y;
|
||||
ren.s_w = w;
|
||||
ren.s_h = h;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int ren_push_glyph(const struct font_glyph *g, int gx, int gy)
|
||||
{
|
||||
/* x4,y4 x3,y3
|
||||
* o-------------+
|
||||
* |(x,y) /|
|
||||
* | / |
|
||||
* | 2 / |
|
||||
* | / |
|
||||
* | / |
|
||||
* | / 1 |
|
||||
* |/ |
|
||||
* +-------------+
|
||||
* x1,y1 x2,y2 */
|
||||
struct v_text v;
|
||||
struct font_glyph c;
|
||||
c = *g;
|
||||
//printf("g: u=%d v=%d w=%d h=%d a=%d x=%d y=%d\n", c.u, c.v, c.w, c.h, c.a, c.x, c.y);
|
||||
//printf("v: x=%d y=%d u=%d v=%d\n", v.pos.x, v.pos.y, v.uv.u, v.uv.v);
|
||||
// x1,y1
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x2,y2
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u+c.w, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x3,y3
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
|
||||
.uv = { .u = c.u+c.w, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x1,y1
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y+c.h },
|
||||
.uv = { .u = c.u, .v = c.v+c.h },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x3,y3
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x+c.w, .y = gy+c.y },
|
||||
.uv = { .u = c.u+c.w, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
// x4,y4
|
||||
v = (struct v_text){
|
||||
.pos = { .x = gx+c.x, .y = gy+c.y },
|
||||
.uv = { .u = c.u, .v = c.v },
|
||||
};
|
||||
vtstack_push(&ren.font_stack, &v);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static const struct font_glyph * get_glyph(unsigned int code, int idx)
|
||||
{
|
||||
const struct font_glyph *g;
|
||||
int updated;
|
||||
g = font_get_glyph_texture(ren.fonts[idx].font, code, &updated);
|
||||
if (!g)
|
||||
REN_RET(NULL, REN_FONT);
|
||||
if (updated) {
|
||||
if (update_font_texture(idx))
|
||||
REN_RET(NULL, REN_TEXTURE);
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
|
||||
// TODO: reduce repeating patterns in ren_get_text_box() and ren_render_text()
|
||||
int ren_get_text_box(const char *str, int *rw, int *rh, int size)
|
||||
{
|
||||
int w = 0, h = 0, x = 0, y = 0;
|
||||
const struct font_glyph *g;
|
||||
size_t off, ret;
|
||||
uint32_t cp;
|
||||
int idx = ren_get_font(size);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
|
||||
h = y = ren.fonts[idx].font->glyph_max_h;
|
||||
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
|
||||
if (iscntrl(cp)) goto skip_get;
|
||||
if (!(g = get_glyph(cp, idx)))
|
||||
return -1;
|
||||
|
||||
x += g->x + g->a;
|
||||
// FIXME: generalize this thing
|
||||
skip_get:
|
||||
switch (cp) {
|
||||
case '\t': {
|
||||
const struct font_glyph *sp = get_glyph(' ', idx);
|
||||
if (!sp) return -1;
|
||||
x += (sp->x + sp->a)*ren.tabsize;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
x = 0;
|
||||
break;
|
||||
case '\n':
|
||||
// TODO: encode and/or store line height
|
||||
y += ren.fonts[idx].font->glyph_max_h;
|
||||
x = 0;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (x > w) w = x;
|
||||
if (y > h) h = y;
|
||||
}
|
||||
|
||||
if (rw) *rw = w;
|
||||
if (rh) *rh = h;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_render_text(const char *str, int x, int y, int w, int h, int size)
|
||||
{
|
||||
const struct font_glyph *g;
|
||||
size_t ret, off;
|
||||
uint32_t cp;
|
||||
int gx = x, gy = y;
|
||||
int idx = ren_get_font(size);
|
||||
if (idx < 0)
|
||||
return -1;
|
||||
for (off = 0; (ret = grapheme_decode_utf8(str+off, SIZE_MAX, &cp)) > 0 && cp != 0; off += ret) {
|
||||
// skip special characters that render a box (not present in font)
|
||||
if (iscntrl(cp)) goto skip_render;
|
||||
if (!(g = get_glyph(cp, idx)))
|
||||
return -1;
|
||||
|
||||
// only push the glyph if it is inside the bounding box
|
||||
if (gx <= x+w && gy <= y+h)
|
||||
ren_push_glyph(g, gx, gy);
|
||||
// TODO: possible kerning needs to be applied here
|
||||
// TODO: handle other unicode control characters such as the
|
||||
// right-to-left isolate (\u2067)
|
||||
gx += g->x + g->a;
|
||||
skip_render:
|
||||
switch (cp) {
|
||||
case '\t': {
|
||||
const struct font_glyph *sp = get_glyph(' ', idx);
|
||||
if (!sp) return -1;
|
||||
gx += (sp->x + sp->a)*ren.tabsize;
|
||||
}
|
||||
break;
|
||||
case '\r':
|
||||
gx = x;
|
||||
break;
|
||||
case '\n':
|
||||
gy += ren.fonts[idx].font->glyph_max_h;
|
||||
gx = x;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
ren_set_scissor(x, y, w, h);
|
||||
ren_draw_font_stack(idx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// re-normalize color from 0-255 to 0-0x7fffffff, technically i'm dividing by 256 here
|
||||
#define RENORM(x) (((unsigned long long)(x)*0x7fffffff)>>8)
|
||||
#define R(x) (x&0xff)
|
||||
#define G(x) ((x>>8)&0xff)
|
||||
#define B(x) ((x>>16)&0xff)
|
||||
#define A(x) ((x>>24)&0xff)
|
||||
static int ren_push_box(int x, int y, int w, int h, unsigned int color)
|
||||
{
|
||||
/* x4,y4 x3,y3
|
||||
* o-------------+
|
||||
* |(x,y) /|
|
||||
* | / |
|
||||
* | 2 / |
|
||||
* | / |
|
||||
* | / |
|
||||
* | / 1 |
|
||||
* |/ |
|
||||
* +-------------+
|
||||
* x1,y1 x2,y2 */
|
||||
struct v_col v;
|
||||
vec4_i c = {
|
||||
.r = RENORM(R(color)),
|
||||
.g = RENORM(G(color)),
|
||||
.b = RENORM(B(color)),
|
||||
.a = RENORM(A(color)),
|
||||
};
|
||||
// x1, y1
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x2, y2
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x3, y3
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x1, y1
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y+h }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x3, y3
|
||||
v = (struct v_col){ .pos = { .x = x+w, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
// x4, y4
|
||||
v = (struct v_col){ .pos = { .x = x, .y = y }, .col = c };
|
||||
vcstack_push(&ren.box_stack, &v);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_render_box(int x, int y, int w, int h, unsigned int color)
|
||||
{
|
||||
ren_push_box(x, y, w, h, color);
|
||||
ren_draw_box_stack();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int ren_free(void)
|
||||
{
|
||||
GL(glUseProgram(0))
|
||||
GL(glBindBuffer(GL_ARRAY_BUFFER, 0))
|
||||
GL(glDeleteProgram(ren.box_prog));
|
||||
GL(glDeleteProgram(ren.font_prog));
|
||||
GL(glDeleteBuffers(1, &ren.font_buffer))
|
||||
for (int i = 0; i < ren.fonts_no; i++) {
|
||||
GL(glDeleteTextures(1, &ren.fonts[i].texture))
|
||||
font_free(ren.fonts[i].font);
|
||||
}
|
||||
SDL_GL_DeleteContext(ren.gl);
|
||||
vtstack_free(&ren.font_stack);
|
||||
vcstack_free(&ren.box_stack);
|
||||
return 0;
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
#ifndef _RENDERER_H
|
||||
#define _RENDERER_H
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
|
||||
|
||||
#define DEFAULT_FONT "/usr/share/fonts/TTF/FiraCode-Regular.ttf"
|
||||
#define FONT_VERSHADER "./font_vertshader.glsl"
|
||||
#define FONT_FRAGSHADER "./font_fragshader.glsl"
|
||||
#define BOX_VERSHADER "./box_vertshader.glsl"
|
||||
#define BOX_FRAGSHADER "./box_fragshader.glsl"
|
||||
#define REN_VERTEX_IDX 0
|
||||
#define REN_UV_IDX 1
|
||||
#define REN_COLOR_IDX 2
|
||||
#define REN_TABSIZE 8
|
||||
|
||||
|
||||
typedef struct {
|
||||
union { int x, u; };
|
||||
union { int y, v; };
|
||||
} vec2_i;
|
||||
|
||||
typedef struct {
|
||||
union { int x, r; };
|
||||
union { int y, g; };
|
||||
union { int z, b; };
|
||||
union { int w, a; };
|
||||
} vec4_i;
|
||||
|
||||
// textured vertex
|
||||
struct v_text {
|
||||
vec2_i pos;
|
||||
vec2_i uv;
|
||||
};
|
||||
|
||||
// colored vertex
|
||||
struct v_col {
|
||||
vec2_i pos;
|
||||
vec4_i col;
|
||||
};
|
||||
|
||||
|
||||
int ren_init(SDL_Window *sdl_window);
|
||||
int ren_free(void);
|
||||
const char * ren_strerror(void);
|
||||
int ren_update_viewport(int w, int h);
|
||||
int ren_set_scissor(int x, int y, int w, int h);
|
||||
int ren_get_text_box(const char *str, int *rw, int *rh, int size);
|
||||
int ren_render_text(const char *str, int x, int y, int w, int h, int size);
|
||||
int ren_render_box(int x, int y, int w, int h, unsigned int color);
|
||||
int ren_clear(void);
|
||||
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -1,126 +0,0 @@
|
||||
#define _POSIX_C_SOURCE 200809l
|
||||
|
||||
#include <sys/mman.h>
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <err.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
|
||||
const char *bit_rep[16] = {
|
||||
[ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011",
|
||||
[ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111",
|
||||
[ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011",
|
||||
[12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111",
|
||||
};
|
||||
|
||||
|
||||
|
||||
void * emalloc(unsigned long int size)
|
||||
{
|
||||
void *r = malloc(size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "malloc() of size %ld", size);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void * ecalloc(unsigned long int nmemb, unsigned long int size)
|
||||
{
|
||||
void *r = calloc(nmemb, size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "calloc() of size %ld, nmemb %ld", size, nmemb);
|
||||
return r;
|
||||
}
|
||||
|
||||
void * erealloc(void *ptr, unsigned long int size)
|
||||
{
|
||||
void *r = realloc(ptr, size);
|
||||
if (!r)
|
||||
err(EXIT_FAILURE, "ralloc() of 0x%lx, to size %ld", (unsigned long)ptr, size);
|
||||
return r;
|
||||
}
|
||||
|
||||
|
||||
void efree(void *ptr)
|
||||
{
|
||||
if (ptr)
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
|
||||
void map_file(const unsigned char **str, int *size, const char *path)
|
||||
{
|
||||
if (!path) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "NULL filename");
|
||||
}
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "Cannot open file %s", path);
|
||||
*size = lseek(fileno(fp), 0, SEEK_END);
|
||||
if (*size == (off_t)-1)
|
||||
err(EXIT_FAILURE, "lseek() failed");
|
||||
*str = mmap(0, *size, PROT_READ, MAP_PRIVATE, fileno(fp), 0);
|
||||
if (*str == (void*)-1)
|
||||
err(EXIT_FAILURE, "mmap() failed");
|
||||
if (fclose(fp))
|
||||
err(EXIT_FAILURE, "Error closing file");
|
||||
}
|
||||
|
||||
|
||||
void dump_file(const char *path, char **buf, int *buf_len)
|
||||
{
|
||||
if (!path) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "NULL filename");
|
||||
}
|
||||
if (!buf) {
|
||||
errno = EINVAL;
|
||||
err(EXIT_FAILURE, "No buffer specified");
|
||||
}
|
||||
int m = 0;
|
||||
if (!buf_len)
|
||||
buf_len = &m;
|
||||
FILE *fp = fopen(path, "r");
|
||||
if (!fp)
|
||||
err(EXIT_FAILURE, "Cannot open file %s", path);
|
||||
*buf_len = lseek(fileno(fp), 0, SEEK_END);
|
||||
rewind(fp);
|
||||
if (*buf_len == (off_t)-1)
|
||||
err(EXIT_FAILURE, "lseek() failed");
|
||||
*buf = emalloc(*buf_len+1);
|
||||
memset(*buf, 0, *buf_len+1);
|
||||
int ret = fread(*buf, 1, *buf_len, fp);
|
||||
if (ret != *buf_len)
|
||||
err(EXIT_FAILURE, "fread() returned short %s", ferror(fp) ? "stream error" : feof(fp) ? "EOF reached" : "unknown error");
|
||||
if (fclose(fp))
|
||||
err(EXIT_FAILURE, "Error closing file");
|
||||
}
|
||||
|
||||
|
||||
void print_byte(unsigned char byte)
|
||||
{
|
||||
printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]);
|
||||
}
|
||||
|
||||
|
||||
static struct timespec clock_start, clock_stop;
|
||||
|
||||
void stopwatch_start(void)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_start);
|
||||
}
|
||||
|
||||
|
||||
double stopwatch_get(void)
|
||||
{
|
||||
clock_gettime(CLOCK_MONOTONIC_COARSE, &clock_stop);
|
||||
return (clock_stop.tv_sec-clock_start.tv_sec)+(double)(clock_stop.tv_nsec-clock_start.tv_nsec)/(double)1000000000L;
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#ifndef _UTIL_H
|
||||
#define _UTIL_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
void * emalloc(unsigned long int size);
|
||||
void * ecalloc(unsigned long int nmemb, unsigned long int size);
|
||||
void * erealloc(void *ptr, unsigned long int size);
|
||||
void efree(void *ptr);
|
||||
|
||||
void map_file(const unsigned char **str, int *size, const char *path);
|
||||
void dump_file(const char *path, char **buf, int *buf_len);
|
||||
|
||||
void print_byte(unsigned char byte);
|
||||
|
||||
void stopwatch_start(void);
|
||||
double stopwatch_get(void);
|
||||
|
||||
#define TIME_SEC(f) \
|
||||
{ \
|
||||
stopwatch_start(); \
|
||||
f; \
|
||||
printf("\"%s\" took %f seconds", #f, stopwatch_get()); \
|
||||
}
|
||||
|
||||
#define TIME_MS(f) \
|
||||
{ \
|
||||
stopwatch_start(); \
|
||||
f; \
|
||||
printf("\"%s\" took %f ms", #f, stopwatch_get()*1000.0f); \
|
||||
}
|
||||
|
||||
#endif
|
285
ugui.h
285
ugui.h
@ -1,285 +0,0 @@
|
||||
#ifndef _UG_HEADER
|
||||
#define _UG_HEADER
|
||||
|
||||
#define UG_STACK(T) struct { T *items; int idx, size, sorted; }
|
||||
#define BIT(n) (1 << n)
|
||||
#define RGBA_FORMAT(x) { .a=x&0xff, .b=(x>>8)&0xff, .g=(x>>16)&0xff, .r=(x>>24)&0xff }
|
||||
#define RGB_FORMAT(x) { .a=0xff, .b=x&0xff, .g=(x>>8)&0xff, .r=(x>>16)&0xff }
|
||||
#define SIZE_PX(x) { .size.i=x, .unit=UG_UNIT_PX }
|
||||
#define SIZE_MM(x) { .size.f=x, .unit=UG_UNIT_MM }
|
||||
#define SIZE_PT(x) { .size.f=x, .unit=UG_UNIT_PT }
|
||||
#define SQUARE(x) .w = x, .h = x
|
||||
|
||||
// basic types
|
||||
typedef unsigned int ug_id_t;
|
||||
typedef struct { union {int x, w;}; union {int y, h;}; } ug_vec2_t;
|
||||
typedef struct { unsigned char a, b, g, r; } ug_color_t;
|
||||
typedef struct { int x, y, w, h; } ug_rect_t;
|
||||
typedef struct { union {int i; float f;} size; int unit; } ug_size_t;
|
||||
// div has information about the phisical dimension
|
||||
typedef struct { ug_size_t x, y, w, h;} ug_div_t;
|
||||
|
||||
typedef enum {
|
||||
UG_UNIT_PX = 0,
|
||||
UG_UNIT_MM,
|
||||
UG_UNIT_PT,
|
||||
} ug_unit_t;
|
||||
|
||||
|
||||
// element type
|
||||
typedef struct {
|
||||
ug_id_t id;
|
||||
unsigned short int type;
|
||||
unsigned short int flags;
|
||||
ug_rect_t rect, rca;
|
||||
const char *name;
|
||||
union {
|
||||
struct {
|
||||
const char *txt;
|
||||
} btn;
|
||||
};
|
||||
} ug_element_t;
|
||||
|
||||
enum {
|
||||
UG_ELEM_BUTTON, // button
|
||||
UG_ELEM_TXTBTN, // textual button, a button but without frame
|
||||
UG_ELEM_CHECK, // checkbox
|
||||
UG_ELEM_RADIO, // radio button
|
||||
UG_ELEM_TOGGLE, // toggle button
|
||||
UG_ELEM_LABEL, // simple text
|
||||
UG_ELEM_UPDOWN, // text with two buttons up and down
|
||||
UG_ELEM_TEXTINPUT, // text input box
|
||||
UG_ELEM_TEXTBOX, // text surrounded by a box
|
||||
UG_ELEM_IMG, // image, icon
|
||||
UG_ELEM_SPACE, // takes up space
|
||||
};
|
||||
|
||||
enum {
|
||||
ELEM_CLIPPED = BIT(0),
|
||||
};
|
||||
|
||||
|
||||
// container type, a container is an entity that contains layouts, a container has
|
||||
// a haight a width and their maximum values, in essence a container is a rectangular
|
||||
// area that can be resized and sits somewhere on the drawable region
|
||||
// the z index of a container is determined by it's position on the stack
|
||||
typedef struct {
|
||||
ug_id_t id;
|
||||
const char *name;
|
||||
ug_rect_t rect;
|
||||
// absolute position rect
|
||||
ug_rect_t rca;
|
||||
unsigned int flags;
|
||||
// layouting and elements
|
||||
// total space used by elements, x and y are the starting coordinates
|
||||
// for elements
|
||||
ug_rect_t space;
|
||||
// origin for in-row and in-column elements
|
||||
ug_vec2_t c_orig, r_orig;
|
||||
UG_STACK(ug_element_t) elem_stack;
|
||||
ug_id_t selected_elem, hover_elem;
|
||||
} ug_container_t;
|
||||
|
||||
// the container flags
|
||||
enum {
|
||||
UG_CNT_FLOATING = BIT(0), // is on top of everything else
|
||||
UG_CNT_RESIZE_RIGHT = BIT(1), // can be resized from the right border
|
||||
UG_CNT_RESIZE_BOTTOM = BIT(2), // can be resized from the bottom border
|
||||
UG_CNT_RESIZE_LEFT = BIT(3), // can be resized from the left border
|
||||
UG_CNT_RESIZE_TOP = BIT(4), // can be resized from the top border
|
||||
UG_CNT_SCROLL_X = BIT(5), // can have horizontal scrolling
|
||||
UG_CNT_SCROLL_Y = BIT(6), // can have vertical scrolling
|
||||
UG_CNT_MOVABLE = BIT(7), // can be moved around
|
||||
// container state
|
||||
CNT_STATE_NONE = BIT(30),
|
||||
CNT_STATE_MOVING = BIT(29),
|
||||
CNT_STATE_RESIZE_T = BIT(28),
|
||||
CNT_STATE_RESIZE_B = BIT(27),
|
||||
CNT_STATE_RESIZE_L = BIT(26),
|
||||
CNT_STATE_RESIZE_R = BIT(25),
|
||||
CNT_STATE_RESIZE_D = BIT(24),
|
||||
CNT_STATE_DELETE = BIT(23), // The container is marked for removal
|
||||
// layouting
|
||||
CNT_LAYOUT_COLUMN = BIT(22),
|
||||
};
|
||||
|
||||
// style, defines default height, width, color, margins, borders, etc
|
||||
// all dimensions should be taken as a reference when doing the layout since
|
||||
// ultimately it's the layout that decides them. For example when deciding how to
|
||||
// allocate space one can say that the default size of a region that allocates a
|
||||
// slider has the style's default dimensions for a slider
|
||||
typedef struct {
|
||||
struct { ug_color_t bg, fg; } color;
|
||||
ug_size_t margin;
|
||||
struct {
|
||||
ug_color_t color;
|
||||
ug_size_t size;
|
||||
} border;
|
||||
struct {
|
||||
struct { ug_color_t bg, fg; } color;
|
||||
ug_size_t height, font_size;
|
||||
} title;
|
||||
struct {
|
||||
struct { ug_color_t act, bg, fg, sel, br; } color;
|
||||
ug_size_t font_size, border;
|
||||
} btn;
|
||||
} ug_style_t;
|
||||
|
||||
|
||||
// render commands
|
||||
struct ug_cmd_rect { int x, y, w, h; ug_color_t color; };
|
||||
struct ug_cmd_text { int x, y, size; ug_color_t color; const char *str; };
|
||||
|
||||
typedef struct {
|
||||
unsigned int type;
|
||||
union {
|
||||
struct ug_cmd_rect rect;
|
||||
struct ug_cmd_text text;
|
||||
};
|
||||
} ug_cmd_t;
|
||||
|
||||
typedef enum {
|
||||
UG_CMD_NULL = 0,
|
||||
UG_CMD_RECT,
|
||||
UG_CMD_TEXT,
|
||||
} ug_cmd_type_t;
|
||||
|
||||
// window side
|
||||
enum {
|
||||
UG_SIDE_TOP = 0,
|
||||
UG_SIDE_BOTTOM,
|
||||
UG_SIDE_LEFT,
|
||||
UG_SIDE_RIGHT,
|
||||
};
|
||||
|
||||
// mouse buttons
|
||||
enum {
|
||||
UG_BTN_LEFT = BIT(0),
|
||||
UG_BTN_MIDDLE = BIT(1),
|
||||
UG_BTN_RIGHT = BIT(2),
|
||||
UG_BTN_4 = BIT(3),
|
||||
UG_BTN_5 = BIT(4),
|
||||
};
|
||||
|
||||
// context
|
||||
typedef struct {
|
||||
// some style information
|
||||
const ug_style_t *style;
|
||||
// style_px is a style cache where all measurements are already in pixels
|
||||
const ug_style_t *style_px;
|
||||
// ppi: pixels per inch
|
||||
// ppm: pixels per millimeter
|
||||
// ppd: pixels per dot
|
||||
float ppi, ppm, ppd;
|
||||
float last_ppi, last_ppm, last_ppd;
|
||||
// containers need to know how big the "main container" is so that all
|
||||
// the relative positioning work
|
||||
ug_vec2_t size;
|
||||
ug_rect_t origin;
|
||||
// which context and element we are hovering
|
||||
ug_id_t hover_cnt;
|
||||
// active is updated on mousedown and released on mouseup
|
||||
// the id of the "active" element, active means different things for
|
||||
// different elements, for exaple active for a button means to be pressed,
|
||||
// and for a text box it means to be focused
|
||||
ug_id_t active_cnt, last_active_cnt;
|
||||
// id of the selected container, used for layout
|
||||
// NOTE: since the stacks can be relocated with realloc it is better not
|
||||
// to use a pointer here, even tough it would be better for efficiency
|
||||
ug_id_t selected_cnt;
|
||||
// count the frames for fun
|
||||
unsigned long int frame;
|
||||
// mouse data
|
||||
struct {
|
||||
ug_vec2_t pos;
|
||||
ug_vec2_t last_pos;
|
||||
ug_vec2_t delta;
|
||||
ug_vec2_t scroll_delta;
|
||||
// mouse.update: a mask of the mouse buttons that are being updated
|
||||
unsigned char update;
|
||||
// mouse.hold: a mask of the buttons that are being held
|
||||
unsigned char hold;
|
||||
} mouse;
|
||||
// keyboard key pressed
|
||||
struct {
|
||||
unsigned char update;
|
||||
unsigned char hold;
|
||||
} key;
|
||||
// input text buffer
|
||||
char input_text[32];
|
||||
// stacks
|
||||
UG_STACK(ug_container_t) cnt_stack;
|
||||
UG_STACK(ug_cmd_t) cmd_stack;
|
||||
// command stack iterator
|
||||
int cmd_it;
|
||||
} ug_ctx_t;
|
||||
|
||||
|
||||
// context initialization
|
||||
ug_ctx_t * ug_ctx_new(void);
|
||||
void ug_ctx_free(ug_ctx_t *ctx);
|
||||
// updates the context with user information
|
||||
// ppi: pixels per inch, the scale used for all em/mm to pixel calculations
|
||||
// scale: it is the scale between the physical pixels on the screen and the pixels
|
||||
// on the buffer, if unsure set to 1.0
|
||||
int ug_ctx_set_displayinfo(ug_ctx_t *ctx, float scale, float ppi);
|
||||
int ug_ctx_set_drawableregion(ug_ctx_t *ctx, ug_vec2_t size);
|
||||
int ug_ctx_set_style(ug_ctx_t *ctx, const ug_style_t *style);
|
||||
|
||||
|
||||
// define containers, name is used as a salt for the container id, all sizes are
|
||||
// the default ones, resizing is done automagically with internal state
|
||||
|
||||
// a floating container can be placed anywhere and can be resized, acts like a
|
||||
// window inside another window
|
||||
int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div);
|
||||
// like a floating container but cannot be resized
|
||||
int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div);
|
||||
// a menu bar is a container of fixed height, cannot be resized and sits at the
|
||||
// top of the window
|
||||
int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height);
|
||||
// a sidebar is a variable size container anchored to one side of the window
|
||||
int ug_container_sidebar(ug_ctx_t *ctx, const char *name, ug_size_t size, int side);
|
||||
// a body is a container that scales with the window, sits at it's center and cannot
|
||||
// be resized, it also fills all the available space
|
||||
int ug_container_body(ug_ctx_t *ctx, const char *name);
|
||||
// mark a conatiner for removal, it will be freed at the next frame beginning if
|
||||
// name is NULL then use the selected container
|
||||
int ug_container_remove(ug_ctx_t *ctx, const char *name);
|
||||
// get the drawable area of the container, if name is NULL then use the selected
|
||||
// container
|
||||
ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name);
|
||||
|
||||
// layouting, the following functions define how different ui elements are placed
|
||||
// inside the selected container. A new element is aligned respective to the
|
||||
// previous element and/or to the container, particularly elements can be placed
|
||||
// in a row or a column.
|
||||
int ug_layout_row(ug_ctx_t *ctx);
|
||||
int ug_layout_column(ug_ctx_t *ctx);
|
||||
int ug_layout_next_row(ug_ctx_t *ctx);
|
||||
int ug_layout_next_column(ug_ctx_t *ctx);
|
||||
|
||||
// elements
|
||||
int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
|
||||
int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim);
|
||||
|
||||
// Input functions
|
||||
int ug_input_mousemove(ug_ctx_t *ctx, int x, int y);
|
||||
int ug_input_mousedown(ug_ctx_t *ctx, unsigned int mask);
|
||||
int ug_input_mouseup(ug_ctx_t *ctx, unsigned int mask);
|
||||
int ug_input_scroll(ug_ctx_t *ctx, int x, int y);
|
||||
// TODO: other input functions
|
||||
|
||||
// Frame handling
|
||||
int ug_frame_begin(ug_ctx_t *ctx);
|
||||
int ug_frame_end(ug_ctx_t *ctx);
|
||||
|
||||
// Commands
|
||||
// get the next command, save iteration state inside 'iterator'
|
||||
ug_cmd_t * ug_cmd_next(ug_ctx_t *ctx);
|
||||
|
||||
|
||||
#undef UG_STACK
|
||||
#undef BIT
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user