Compare commits
17 Commits
Author | SHA1 | Date |
---|---|---|
Alessandro Mauri | 2dcc1b582c | 8 months ago |
Alessandro Mauri | a374a37971 | 8 months ago |
Alessandro Mauri | 5427b191c2 | 8 months ago |
Alessandro Mauri | 3a8a55d177 | 9 months ago |
Alessandro Mauri | a4974c8df8 | 9 months ago |
Alessandro Mauri | 97295df516 | 11 months ago |
Alessandro Mauri | 305df93182 | 11 months ago |
Alessandro Mauri | d6358944ac | 11 months ago |
Alessandro Mauri | 59acce1150 | 11 months ago |
Alessandro Mauri | d4c97e1f4f | 11 months ago |
Alessandro Mauri | 28b5ee16fb | 11 months ago |
Alessandro Mauri | 7909306d7a | 11 months ago |
Alessandro Mauri | 99df8ad38d | 11 months ago |
Alessandro Mauri | 156c3b3959 | 11 months ago |
Alessandro Mauri | 94837ed410 | 11 months ago |
Alessandro Mauri | 4aefe8b42d | 11 months ago |
Alessandro Mauri | 71080476b1 | 1 year ago |
@ -0,0 +1,41 @@ |
|||||||
|
# linux kernel style formatting |
||||||
|
BasedOnStyle: LLVM |
||||||
|
IndentWidth: 8 |
||||||
|
UseTab: AlignWithSpaces |
||||||
|
|
||||||
|
BreakBeforeBraces: Linux |
||||||
|
AllowShortIfStatementsOnASingleLine: false |
||||||
|
IndentCaseLabels: false |
||||||
|
ColumnLimit: 85 |
||||||
|
|
||||||
|
InsertBraces: true |
||||||
|
SortIncludes: Never |
||||||
|
BinPackParameters: false |
||||||
|
BinPackArguments: false |
||||||
|
Cpp11BracedListStyle: true |
||||||
|
SpaceBeforeCpp11BracedList: true |
||||||
|
SeparateDefinitionBlocks: Always |
||||||
|
AlignAfterOpenBracket: BlockIndent |
||||||
|
InsertNewlineAtEOF: true |
||||||
|
|
||||||
|
AlignConsecutiveDeclarations: |
||||||
|
Enabled: true |
||||||
|
AcrossEmptyLines: false |
||||||
|
AcrossComments: false |
||||||
|
AlignCompound: true |
||||||
|
PadOperators: false |
||||||
|
|
||||||
|
AlignConsecutiveMacros: |
||||||
|
Enabled: true |
||||||
|
AcrossEmptyLines: false |
||||||
|
AcrossComments: true |
||||||
|
|
||||||
|
AlignConsecutiveBitFields: |
||||||
|
Enabled: true |
||||||
|
AcrossEmptyLines: false |
||||||
|
AcrossComments: true |
||||||
|
|
||||||
|
AlignConsecutiveAssignments: |
||||||
|
Enabled: true |
||||||
|
AcrossEmptyLines: false |
||||||
|
AcrossComments: true |
@ -1,5 +1,8 @@ |
|||||||
microgui |
microgui |
||||||
|
ugui |
||||||
*.o |
*.o |
||||||
test/test |
test/test |
||||||
**/compile_commands.json |
**/compile_commands.json |
||||||
**/.cache |
**/.cache |
||||||
|
test |
||||||
|
raylib/* |
||||||
|
@ -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 |
@ -0,0 +1,15 @@ |
|||||||
|
CFLAGS = -Wall -Wextra -pedantic -std=c11 -g -Iraylib/include
|
||||||
|
CC = gcc
|
||||||
|
LDFLAGS = -Lraylib/lib -lm
|
||||||
|
|
||||||
|
all: ugui |
||||||
|
|
||||||
|
ugui: ugui.o vectree.o cache.o timer.o raylib/lib/libraylib.a |
||||||
|
|
||||||
|
ugui.o: ugui.c ugui.h |
||||||
|
|
||||||
|
vectree.o: vectree.c ugui.h |
||||||
|
|
||||||
|
cache.o: cache.c ugui.h |
||||||
|
|
||||||
|
timer.o: timer.c timer.h |
@ -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 |
|
@ -0,0 +1,211 @@ |
|||||||
|
// LRU cache:
|
||||||
|
/*
|
||||||
|
* The cache uses a pool (array) containing all the elements and a hash table |
||||||
|
* associating the position in the pool with the element id |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "ugui.h" |
||||||
|
|
||||||
|
// To have less collisions TABLE_SIZE has to be larger than the cache size
|
||||||
|
#define CACHE_SIZE 265 |
||||||
|
#define TABLE_SIZE (CACHE_SIZE * 1.5f) |
||||||
|
#define CACHE_NCYCLES (CACHE_SIZE * 2 / 3) |
||||||
|
#define CACHE_BSIZE (((CACHE_SIZE + 0x3f) & (~0x3f)) >> 6) |
||||||
|
#define CACHE_BRESET(b) \ |
||||||
|
for (int i = 0; i < CACHE_BSIZE; b[i++] = 0) \
|
||||||
|
; |
||||||
|
#define CACHE_BSET(b, x) b[(x) >> 6] |= (uint64_t)1 << ((x)&63) |
||||||
|
#define CACHE_BTEST(b, x) (b[(x) >> 6] & ((uint64_t)1 << ((x)&63))) |
||||||
|
|
||||||
|
/* Hash Table Implementation ----------------------------------------------------- */ |
||||||
|
|
||||||
|
#define HASH_MAXSIZE 4096 |
||||||
|
|
||||||
|
// hash table (id -> cache index)
|
||||||
|
typedef struct { |
||||||
|
UgId id; |
||||||
|
uint32_t index; |
||||||
|
} IdElem; |
||||||
|
|
||||||
|
typedef struct _IdTable { |
||||||
|
uint32_t items, size, exp; |
||||||
|
IdElem bucket[]; |
||||||
|
} IdTable; |
||||||
|
|
||||||
|
IdTable *table_create(uint32_t size) |
||||||
|
{ |
||||||
|
if (!size || size > HASH_MAXSIZE) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
/* round to the greater power of two */ |
||||||
|
/* FIXME: check for intger overflow here */ |
||||||
|
uint32_t exp = 32 - __builtin_clz(size - 1); |
||||||
|
size = 1 << (exp); |
||||||
|
/* FIXME: check for intger overflow here */ |
||||||
|
IdTable *ht = malloc(sizeof(IdTable) + sizeof(IdElem) * size); |
||||||
|
if (ht) { |
||||||
|
ht->items = 0; |
||||||
|
ht->size = size; |
||||||
|
memset(ht->bucket, 0, sizeof(IdTable) * size); |
||||||
|
} |
||||||
|
return ht; |
||||||
|
} |
||||||
|
|
||||||
|
void table_destroy(IdTable *ht) |
||||||
|
{ |
||||||
|
if (ht) { |
||||||
|
free(ht); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Find and return the element by pointer
|
||||||
|
IdElem *table_search(IdTable *ht, UgId id) |
||||||
|
{ |
||||||
|
if (!ht) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
// In this case id is the hash
|
||||||
|
uint32_t idx = id % ht->size; |
||||||
|
|
||||||
|
for (uint32_t x = 0, i; x < ht->size; x++) { |
||||||
|
i = (idx + x) % ht->size; |
||||||
|
if (ht->bucket[i].id == 0 || ht->bucket[i].id == id) { |
||||||
|
return &(ht->bucket[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
// FIXME: this simply overrides the found item
|
||||||
|
IdElem *table_insert(IdTable *ht, IdElem entry) |
||||||
|
{ |
||||||
|
IdElem *r = table_search(ht, entry.id); |
||||||
|
if (r != NULL) { |
||||||
|
if (r->id != 0) { |
||||||
|
ht->items++; |
||||||
|
} |
||||||
|
*r = entry; |
||||||
|
} |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
IdElem *table_remove(IdTable *ht, UgId id) |
||||||
|
{ |
||||||
|
if (!ht) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
IdElem *r = table_search(ht, id); |
||||||
|
if (r) { |
||||||
|
r->id = 0; |
||||||
|
} |
||||||
|
return r; |
||||||
|
} |
||||||
|
|
||||||
|
/* Cache Implementation ---------------------------------------------------------- */ |
||||||
|
|
||||||
|
// Every CACHE_CYCLES operations mark not-present the unused elements
|
||||||
|
#define CACHE_CYCLE(c) \ |
||||||
|
do { \
|
||||||
|
if (++(c->cycles) > CACHE_NCYCLES) { \
|
||||||
|
for (int i = 0; i < CACHE_BSIZE; i++) { \
|
||||||
|
c->present[i] &= c->used[i]; \
|
||||||
|
c->used[i] = 0; \
|
||||||
|
} \
|
||||||
|
c->cycles = 0; \
|
||||||
|
} \
|
||||||
|
} while (0) |
||||||
|
|
||||||
|
/* FIXME: check for allocation errors */ |
||||||
|
UgElemCache ug_cache_init(void) |
||||||
|
{ |
||||||
|
IdTable *t = table_create(TABLE_SIZE); |
||||||
|
UgElem *a = malloc(sizeof(UgElem) * CACHE_SIZE); |
||||||
|
uint64_t *p = malloc(sizeof(uint64_t) * CACHE_BSIZE); |
||||||
|
uint64_t *u = malloc(sizeof(uint64_t) * CACHE_BSIZE); |
||||||
|
CACHE_BRESET(p); |
||||||
|
CACHE_BRESET(u); |
||||||
|
return (UgElemCache) {.table = t, .array = a, .present = p, .used = u, 0}; |
||||||
|
} |
||||||
|
|
||||||
|
void ug_cache_free(UgElemCache *cache) |
||||||
|
{ |
||||||
|
if (cache) { |
||||||
|
table_destroy(cache->table); |
||||||
|
free(cache->array); |
||||||
|
free(cache->present); |
||||||
|
free(cache->used); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
UgElem *ug_cache_search(UgElemCache *cache, UgId id) |
||||||
|
{ |
||||||
|
if (!cache) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
IdElem *r; |
||||||
|
r = table_search(cache->table, id); |
||||||
|
/* MISS */ |
||||||
|
if (!r || id != r->id) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
/* MISS, the data is not valid (not present) */ |
||||||
|
if (!CACHE_BTEST(cache->present, r->index)) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
/* HIT, set as recently used */ |
||||||
|
CACHE_BSET(cache->used, r->index); |
||||||
|
return (&cache->array[r->index]); |
||||||
|
} |
||||||
|
|
||||||
|
/* Look for a free spot in the present bitmap and return its index */ |
||||||
|
/* If there is no free space left then just return the first position */ |
||||||
|
int ug_cache_get_free_spot(UgElemCache *cache) |
||||||
|
{ |
||||||
|
if (!cache) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
int x = 0; |
||||||
|
for (int b = 0; b < CACHE_BSIZE; b++) { |
||||||
|
if (cache->present[b] == 0) { |
||||||
|
x = 64; |
||||||
|
} else { |
||||||
|
x = __builtin_clzll(cache->present[b]); |
||||||
|
} |
||||||
|
x = 64 - x; |
||||||
|
if (!CACHE_BTEST(cache->present, x + 64 * b)) { |
||||||
|
return x + 64 * b; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
UgElem *ug_cache_insert_at(UgElemCache *cache, const UgElem *g, uint32_t index) |
||||||
|
{ |
||||||
|
if (!cache) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
UgElem *spot = NULL; |
||||||
|
|
||||||
|
/* Set used and present */ |
||||||
|
CACHE_BSET(cache->present, index); |
||||||
|
CACHE_BSET(cache->used, index); |
||||||
|
CACHE_CYCLE(cache); |
||||||
|
spot = &(cache->array[index]); |
||||||
|
*spot = *g; |
||||||
|
IdElem e = {.id = g->id, .index = index}; |
||||||
|
if (!table_insert(cache->table, e)) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
return spot; |
||||||
|
} |
||||||
|
|
||||||
|
// Insert an element in the cache
|
||||||
|
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index) |
||||||
|
{ |
||||||
|
*index = ug_cache_get_free_spot(cache); |
||||||
|
return ug_cache_insert_at(cache, g, *index); |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
-Iraylib/include |
||||||
|
-Wall |
||||||
|
-Wextra |
||||||
|
-pedantic |
||||||
|
-std=c11 |
@ -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,0 +1,193 @@ |
|||||||
|
#include <stdlib.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <string.h> |
||||||
|
#include <errno.h> |
||||||
|
#include <stdarg.h> |
||||||
|
|
||||||
|
#include <linux/limits.h> |
||||||
|
#include <sys/stat.h> |
||||||
|
#include <sys/types.h> |
||||||
|
|
||||||
|
#define PATHS_NO 3 |
||||||
|
|
||||||
|
static char temp[PATH_MAX] = {0}; |
||||||
|
static int valid_paths = 0; |
||||||
|
static char paths[PATHS_NO][PATH_MAX] = {0}; // 0: xdg, 1: fallback, 2: global
|
||||||
|
|
||||||
|
static const mode_t CONFDIR_MODE = S_IRWXU | S_IRWXU | S_IRWXU | S_IRWXU; |
||||||
|
|
||||||
|
static void err(const char *fmt, ...) |
||||||
|
{ |
||||||
|
va_list ap; |
||||||
|
va_start(ap, fmt); |
||||||
|
|
||||||
|
fprintf(stderr, "[libconf]: "); |
||||||
|
vfprintf(stderr, fmt, ap); |
||||||
|
fprintf(stderr, "\n"); |
||||||
|
|
||||||
|
va_end(ap); |
||||||
|
} |
||||||
|
|
||||||
|
// implements "mkdir -p"
|
||||||
|
static int mkdir_p(const char *path) |
||||||
|
{ |
||||||
|
char tmp_path[PATH_MAX] = {0}; |
||||||
|
strncpy(tmp_path, path, PATH_MAX - 1); |
||||||
|
struct stat st; |
||||||
|
|
||||||
|
for (char *tk = strtok(tmp_path, "/"); tk != NULL; tk = strtok(NULL, "/")) { |
||||||
|
if (tk != tmp_path) { |
||||||
|
tk[-1] = '/'; |
||||||
|
} |
||||||
|
|
||||||
|
if (stat(tmp_path, &st)) { |
||||||
|
if (errno != ENOENT) { |
||||||
|
err("could not stat() %s: %s", |
||||||
|
tmp_path, |
||||||
|
strerror(errno)); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
if (mkdir(tmp_path, CONFDIR_MODE)) { |
||||||
|
err("could not mkdir() %s: %s", |
||||||
|
tmp_path, |
||||||
|
strerror(errno)); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} else if (!S_ISDIR(st.st_mode)) { |
||||||
|
err("could not create directory %s: file exists but it is " |
||||||
|
"not a directory", |
||||||
|
tmp_path); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: add a way to add a custom directory to the paths, for example via an
|
||||||
|
// environment variable
|
||||||
|
// TODO: maybe add a LIBCONF_PATH that overrides the defaults
|
||||||
|
/* default paths are:
|
||||||
|
* 1. ${XDG_CONFIG_HOME}/libconf/appid.d/ |
||||||
|
* 2. ${HOME}/.config/libconf/appid.d/ |
||||||
|
* 3. etc/libconf/appid.d/ |
||||||
|
*/ |
||||||
|
static int fill_paths(const char *id) |
||||||
|
{ |
||||||
|
// TODO: verify id
|
||||||
|
if (id == NULL) { |
||||||
|
err("must provide a valid app id"); |
||||||
|
return 1; |
||||||
|
} |
||||||
|
|
||||||
|
const char *xdg = getenv("XDG_CONFIG_HOME"); |
||||||
|
if (xdg != NULL) { |
||||||
|
snprintf(paths[0], PATH_MAX, "%s/libconf/%s.d", xdg, id); |
||||||
|
} |
||||||
|
|
||||||
|
const char *home = getenv("HOME"); |
||||||
|
if (home) { |
||||||
|
snprintf(temp, PATH_MAX, "%s/.config/libconf/%s.d", home, id); |
||||||
|
|
||||||
|
if (mkdir_p(temp) == 0) { |
||||||
|
strcpy(paths[1], temp); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// do not create global config path since most likely we don't have
|
||||||
|
// the necessary permissions to do so
|
||||||
|
snprintf(paths[2], PATH_MAX, "/etc/libconf/%s.d", id); |
||||||
|
|
||||||
|
for (size_t i = 0; i < PATHS_NO; i++) { |
||||||
|
printf("paths[%ld] = %s\n", i, paths[i]); |
||||||
|
} |
||||||
|
|
||||||
|
valid_paths = 1; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// get config file path
|
||||||
|
const char *lcnf_get_conf_file(const char *id, const char *name, int global) |
||||||
|
{ |
||||||
|
static char str[PATH_MAX] = {0}; |
||||||
|
|
||||||
|
if (id == NULL) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
if (!valid_paths) { |
||||||
|
if (fill_paths(id)) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// quick path for global config file
|
||||||
|
if (global && paths[2][0] != '\0') { |
||||||
|
snprintf(str, PATH_MAX, "%s/%s", paths[2], name); |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
int found = 0; |
||||||
|
for (size_t i = 0; i < PATHS_NO; i++) { |
||||||
|
if (paths[i][0] == '\0') { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
struct stat st; |
||||||
|
snprintf(str, PATH_MAX, "%s/%s", paths[i], name); |
||||||
|
|
||||||
|
if (stat(str, &st) && errno != ENOENT) { |
||||||
|
err("could not stat %s: %s", str, strerror(errno)); |
||||||
|
} |
||||||
|
|
||||||
|
if (S_ISREG(st.st_mode)) { |
||||||
|
found = 1; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return found ? str : NULL; |
||||||
|
} |
||||||
|
|
||||||
|
// get config file directory path
|
||||||
|
const char *lcnf_get_conf_dir(const char *id, int global) |
||||||
|
{ |
||||||
|
if (id == NULL) { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
if (global) { |
||||||
|
return paths[2][0] != '\0' ? paths[2] : NULL; |
||||||
|
} |
||||||
|
|
||||||
|
if (paths[0][0] != '\0') { |
||||||
|
return paths[0]; |
||||||
|
} else if (paths[1][0] != '\0') { |
||||||
|
return paths[1]; |
||||||
|
} else { |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: watch directory for file updates
|
||||||
|
int lcnf_watch_conf_dir(const char *id, int global); |
||||||
|
|
||||||
|
// TODO: collect all files into a temporary one, useful for when you have multiple
|
||||||
|
// configuration files like 00-foo.conf, 01-bar.conf and 99-baz.conf
|
||||||
|
const char *lcnf_collect_conf_files(const char *id, int global); |
||||||
|
|
||||||
|
#if 1 |
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
const char *p = lcnf_get_conf_file("pippo", "pippo.toml", 0); |
||||||
|
if (p) { |
||||||
|
printf("config found: %s\n", p); |
||||||
|
} else { |
||||||
|
printf("config not found\n"); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,7 @@ |
|||||||
|
#ifndef LIBCONF_H_ |
||||||
|
#define LIBCONF_H_ |
||||||
|
|
||||||
|
const char *lcnf_get_conf_file(const char *id, const char *name, int global); |
||||||
|
const char *lcnf_get_conf_dir(const char *id, int global); |
||||||
|
|
||||||
|
#endif |
@ -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
@ -0,0 +1,8 @@ |
|||||||
|
#!/bin/sh |
||||||
|
|
||||||
|
mkdir -p raylib |
||||||
|
wget https://github.com/raysan5/raylib/releases/download/5.0/raylib-5.0_linux_amd64.tar.gz |
||||||
|
tar -xvf raylib-5.0_linux_amd64.tar.gz |
||||||
|
mv ./raylib-5.0_linux_amd64/* ./raylib/ |
||||||
|
|
||||||
|
rm -rf raylib-5.0_linux_amd64 raylib-5.0_linux_amd64.tar.gz |
@ -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; |
|
||||||
} |
|
@ -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.
Before Width: | Height: | 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; |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
@ -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; |
|
||||||
} |
|
@ -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,0 +1,214 @@ |
|||||||
|
//#!/usr/bin/tcc -run
|
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
|
||||||
|
#define HASH_SALT (0xbabb0cac) |
||||||
|
#define HASH_RATIO64 ((unsigned long int)11400714819322457583u) |
||||||
|
|
||||||
|
|
||||||
|
struct ntree_node |
||||||
|
{ |
||||||
|
unsigned int id; |
||||||
|
const char *name; |
||||||
|
int children_no, children_size; |
||||||
|
struct ntree_node *children, *parent; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
static char tmp_name_buffer[16] = {"node:"}; |
||||||
|
|
||||||
|
|
||||||
|
unsigned int hash_str_to_uint(const char *s) |
||||||
|
{ |
||||||
|
unsigned int h = HASH_SALT; |
||||||
|
const unsigned char *v = (const unsigned char *)(s); |
||||||
|
for (int x = *s; x; x--) { |
||||||
|
h += v[x-1]; |
||||||
|
h += h << 10; |
||||||
|
h ^= h >> 6; |
||||||
|
} |
||||||
|
h += h << 3; |
||||||
|
h ^= h >> 11; |
||||||
|
h += h << 15; |
||||||
|
return h; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
unsigned int hash_u32(unsigned int c) |
||||||
|
{ |
||||||
|
return (unsigned int)((((unsigned long int)c<<31)*HASH_RATIO64)>>32); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
const char * generate_new_name(void) |
||||||
|
{ |
||||||
|
static int count = 0; |
||||||
|
unsigned int h = hash_u32(count++); |
||||||
|
snprintf(tmp_name_buffer+sizeof("node:"), 16-sizeof("node:"), "%x", h); |
||||||
|
return tmp_name_buffer; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int tree_prune(struct ntree_node *node) |
||||||
|
{ |
||||||
|
if (node == NULL) |
||||||
|
return 0; |
||||||
|
|
||||||
|
if ((node->children_no == 0 && node->children != NULL) || |
||||||
|
(node->children_no != 0 && node->children == NULL)) { |
||||||
|
printf("ERR: (%x %s) Inconsistent node children", node->id, node->name); |
||||||
|
} else for (int i = 0; i < node->children_no; i++) { |
||||||
|
tree_prune(&(node->children[i])); |
||||||
|
} |
||||||
|
|
||||||
|
if (node->children != NULL) { |
||||||
|
free(node->children); |
||||||
|
} |
||||||
|
free((void *)node->name); |
||||||
|
if (node->parent != NULL) |
||||||
|
node->parent->children_no--; |
||||||
|
*node = (struct ntree_node){0}; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
struct ntree_node * tree_append(struct ntree_node *parent, const char *name) |
||||||
|
{ |
||||||
|
if (name == NULL) |
||||||
|
return NULL; |
||||||
|
if (strlen(name) == 0) |
||||||
|
name = generate_new_name(); |
||||||
|
|
||||||
|
// generate the children information
|
||||||
|
unsigned int id = hash_str_to_uint(name); |
||||||
|
char *str = malloc(strlen(name)+1); |
||||||
|
if (str == NULL) |
||||||
|
return NULL; |
||||||
|
strcpy(str, name); |
||||||
|
|
||||||
|
// grow the parent buffer if necessary
|
||||||
|
if (parent->children_no >= parent->children_size) { |
||||||
|
struct ntree_node *temp = NULL; |
||||||
|
temp = realloc(parent->children, (parent->children_size+1)*sizeof(struct ntree_node)); |
||||||
|
if (temp == NULL) { |
||||||
|
free(str); |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
parent->children = temp; |
||||||
|
parent->children[parent->children_size] = (struct ntree_node){0}; |
||||||
|
parent->children_size++; |
||||||
|
} |
||||||
|
|
||||||
|
// find an open spot for the child
|
||||||
|
struct ntree_node *child = NULL; |
||||||
|
for (int i = 0; i < parent->children_size; i++) { |
||||||
|
if (parent->children[i].id == 0) |
||||||
|
child = &(parent->children[i]); |
||||||
|
} |
||||||
|
if (child == NULL) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
child->name = str; |
||||||
|
child->id = id; |
||||||
|
child->children = NULL; |
||||||
|
child->children_no = 0; |
||||||
|
child->parent = parent; |
||||||
|
parent->children_no++; |
||||||
|
//printf("append to %s, children: %d\n", parent->name, parent->children_no);
|
||||||
|
return child; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
struct ntree_node * tree_find_id(struct ntree_node *root, unsigned int id) |
||||||
|
{ |
||||||
|
if (id == 0 || root == NULL) |
||||||
|
return NULL; |
||||||
|
|
||||||
|
if (root->id == id) |
||||||
|
return root; |
||||||
|
|
||||||
|
else for (int i = 0; i < root->children_size; i++) { |
||||||
|
if (root->children[i].id != 0 && tree_find_id(&(root->children[i]), id) != NULL) |
||||||
|
return &(root->children[i]); |
||||||
|
} |
||||||
|
return NULL; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// TODO: add a foreach_child function
|
||||||
|
struct ntree_node * tree_find(struct ntree_node *root, const char *name) |
||||||
|
{ |
||||||
|
if (name == NULL || strlen(name) == 0 || root == NULL) |
||||||
|
return NULL; |
||||||
|
unsigned int id = hash_str_to_uint(name); |
||||||
|
return tree_find_id(root, id); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int tree_size(struct ntree_node *root) |
||||||
|
{ |
||||||
|
if (root == NULL) |
||||||
|
return 0; |
||||||
|
|
||||||
|
int count = root->children_no; |
||||||
|
if (count > 0) { |
||||||
|
for (int i = 0; i < root->children_size; i++) { |
||||||
|
if (root->children[i].id != 0) |
||||||
|
count += tree_size(&(root->children[i])); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static int tree_print_ind(struct ntree_node *node, int dd) |
||||||
|
{ |
||||||
|
for (int i = 0; i < dd; i++) printf(" "); |
||||||
|
printf("[%s]\n", node->name); |
||||||
|
for (int i = 0; i < node->children_size; i++) { |
||||||
|
if (node->children[i].id == 0) continue; |
||||||
|
tree_print_ind(&(node->children[i]), dd+1); |
||||||
|
} |
||||||
|
return node->children_no; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int tree_print(struct ntree_node *root) |
||||||
|
{ |
||||||
|
if (root == NULL) |
||||||
|
return 1; |
||||||
|
|
||||||
|
tree_print_ind(root, 0); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int main(void) |
||||||
|
{ |
||||||
|
char *root_name = malloc(sizeof("root")); |
||||||
|
strcpy(root_name, "root"); |
||||||
|
struct ntree_node root = {.name = root_name}; |
||||||
|
struct ntree_node *n; |
||||||
|
|
||||||
|
n = tree_append(&root, "node 0:0"); |
||||||
|
tree_append(n, "node 0:0:0"); |
||||||
|
tree_append(&root, "node 0:1"); |
||||||
|
tree_append(&root, "node 0:2"); |
||||||
|
printf("Number of nodes %d\n", tree_size(&root)); |
||||||
|
tree_print(&root); |
||||||
|
|
||||||
|
tree_prune(tree_find(&root, "node 0:0")); |
||||||
|
printf("Number of nodes %d\n", tree_size(&root)); |
||||||
|
|
||||||
|
tree_prune(&root); |
||||||
|
printf("Number of nodes %d\n", tree_size(&root)); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
@ -0,0 +1,348 @@ |
|||||||
|
#ifndef _VECTREE_H |
||||||
|
#define _VECTREE_H |
||||||
|
|
||||||
|
#ifdef VTREE_DTYPE |
||||||
|
|
||||||
|
typedef struct { |
||||||
|
int size, elements; |
||||||
|
VTREE_DTYPE *vector; |
||||||
|
int *refs; |
||||||
|
} Vtree; |
||||||
|
|
||||||
|
int vtree_init(Vtree *tree, unsigned int size);
|
||||||
|
int vtree_pack(Vtree *tree); |
||||||
|
int vtree_resize(Vtree *tree, unsigned int newsize); |
||||||
|
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent); |
||||||
|
int vtree_prune(Vtree *tree, int ref); |
||||||
|
int vtree_subtree_size(Vtree *tree, int ref); |
||||||
|
int vtree_children_it(Vtree *tree, int parent, int *cursor); |
||||||
|
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor); |
||||||
|
int vtree_destroy(Vtree *tree); |
||||||
|
|
||||||
|
#ifdef VTREE_IMPL |
||||||
|
|
||||||
|
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) |
||||||
|
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) |
||||||
|
|
||||||
|
int vtree_init(Vtree *tree, unsigned int size)
|
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector = malloc(sizeof(VTREE_DTYPE) * size); |
||||||
|
if (tree->vector == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->refs = malloc(sizeof(int) * size); |
||||||
|
if (tree->refs == NULL) { |
||||||
|
free(tree->vector); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// set all refs to -1, meaning invalid (free) element
|
||||||
|
for (unsigned int i = 0; i < size; i++) { |
||||||
|
tree->refs[i] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
// fill vector with zeroes
|
||||||
|
memset(tree->vector, 0, size * sizeof(VTREE_DTYPE)); |
||||||
|
|
||||||
|
tree->size = size; |
||||||
|
tree->elements = 0; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int vtree_destroy(Vtree *tree) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
free(tree->vector); |
||||||
|
free(tree->refs); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int vtree_pack(Vtree *tree) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: add a PACKED flag to skip this
|
||||||
|
|
||||||
|
int free_spot = -1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == -1) { |
||||||
|
free_spot = i; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// find a item that can be packed
|
||||||
|
if (free_spot >= 0 && tree->refs[i] >= 0) { |
||||||
|
int old_ref = i; |
||||||
|
|
||||||
|
// move the item
|
||||||
|
tree->vector[free_spot] = tree->vector[i]; |
||||||
|
tree->refs[free_spot] = tree->refs[i]; |
||||||
|
|
||||||
|
tree->vector[i] = (VTREE_DTYPE){0}; |
||||||
|
tree->refs[i] = -1; |
||||||
|
|
||||||
|
// and move all references
|
||||||
|
for (int x = 0; x < tree->size; x++) { |
||||||
|
if (tree->refs[x] == old_ref) { |
||||||
|
tree->refs[x] = free_spot; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// mark the free spot as used
|
||||||
|
free_spot = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int vtree_resize(Vtree *tree, unsigned int newsize)
|
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// return error when shrinking with too many elements
|
||||||
|
if ((int)newsize < tree->elements) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// pack the vector when shrinking to avoid data loss
|
||||||
|
if ((int)newsize < tree->size) { |
||||||
|
//if (vtree_pack(tree) < 0) {
|
||||||
|
// return -1;
|
||||||
|
//}
|
||||||
|
// TODO: allow shrinking, since packing destroys all references
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
VTREE_DTYPE *newvec = realloc(tree->vector, newsize * sizeof(VTREE_DTYPE)); |
||||||
|
if (newvec == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int *newrefs = realloc(tree->refs, newsize * sizeof(int)); |
||||||
|
if (newrefs == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector = newvec; |
||||||
|
tree->refs = newrefs; |
||||||
|
if ((int)newsize > tree->size) { |
||||||
|
for (int i = tree->size; i < (int)newsize; i++) { |
||||||
|
tree->vector[i] = (VTREE_DTYPE){0}; |
||||||
|
tree->refs[i] = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tree->size = newsize; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// add an element to the tree, return it's ref
|
||||||
|
int vtree_add(Vtree *tree, VTREE_DTYPE elem, int parent) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// invalid parent
|
||||||
|
if (!IS_VALID_REF(tree, parent)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// no space left
|
||||||
|
if (tree->elements >= tree->size) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// check if the parent exists
|
||||||
|
// if there are no elements in the tree the first add will set the root
|
||||||
|
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// get the first free spot
|
||||||
|
int free_spot = -1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == -1) { |
||||||
|
free_spot = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (free_spot < 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// finally add the element
|
||||||
|
tree->vector[free_spot] = elem; |
||||||
|
tree->refs[free_spot] = parent; |
||||||
|
tree->elements++; |
||||||
|
|
||||||
|
return free_spot; |
||||||
|
} |
||||||
|
|
||||||
|
// prune the tree starting from the ref
|
||||||
|
// returns the number of pruned elements
|
||||||
|
int vtree_prune(Vtree *tree, int ref) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!IS_VALID_REF(tree, ref)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!REF_IS_PRESENT(tree, ref)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector[ref] = (VTREE_DTYPE){0}; |
||||||
|
tree->refs[ref] = -1; |
||||||
|
|
||||||
|
int count = 1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == ref) { |
||||||
|
count += vtree_prune(tree, i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
// find the size of the subtree starting from ref
|
||||||
|
int vtree_subtree_size(Vtree *tree, int ref) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!IS_VALID_REF(tree, ref)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!REF_IS_PRESENT(tree, ref)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int count = 1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
// only root has the reference to itself
|
||||||
|
if (tree->refs[i] == ref && ref != i) { |
||||||
|
count += vtree_subtree_size(tree, i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
// iterate through the first level children, use a cursor like strtok_r
|
||||||
|
int vtree_children_it(Vtree *tree, int parent, int *cursor) |
||||||
|
{ |
||||||
|
if (tree == NULL || cursor == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// if the cursor is out of bounds then we are done for sure
|
||||||
|
if (!IS_VALID_REF(tree, *cursor)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// same for the parent, if it's invalid it can't have children
|
||||||
|
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// find the first child, update the cursor and return the ref
|
||||||
|
for (int i = *cursor; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == parent) { |
||||||
|
*cursor = i + 1; |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if no children are found return -1
|
||||||
|
*cursor = -1; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/* iterates trough every leaf of the subtree in the following manner
|
||||||
|
* node [x], x: visit order |
||||||
|
* [0] |
||||||
|
* / | \
|
||||||
|
* / [2] [3] |
||||||
|
* [1] | |
||||||
|
* / \ [6] |
||||||
|
* [4] [5] |
||||||
|
*/ |
||||||
|
int vtree_level_order_it(Vtree *tree, int ref, int **queue_p, int *cursor) |
||||||
|
{ |
||||||
|
if (tree == NULL || queue_p == NULL || cursor == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int *queue = *queue_p; |
||||||
|
|
||||||
|
// TODO: this could also be done when adding or removing elements
|
||||||
|
// first call, create a ref array ordered like we desire
|
||||||
|
if (queue == NULL) { |
||||||
|
*cursor = 0; |
||||||
|
// create a queue of invalid refs, size is the worst case
|
||||||
|
queue = malloc(sizeof(int) * tree->size); |
||||||
|
if (queue == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
queue[i] = -1; |
||||||
|
} |
||||||
|
*queue_p = queue; |
||||||
|
|
||||||
|
// iterate through the queue appending found children
|
||||||
|
int pos = 0, off = 0; |
||||||
|
do { |
||||||
|
//printf ("ref=%d\n", ref);
|
||||||
|
|
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == ref) { |
||||||
|
queue[pos++] = i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (;ref == queue[off] && off < tree->size; off++); |
||||||
|
ref = queue[off]; |
||||||
|
|
||||||
|
} while (IS_VALID_REF(tree, ref)); |
||||||
|
} |
||||||
|
|
||||||
|
//PRINT_ARR(queue, tree->size);
|
||||||
|
//return -1;
|
||||||
|
|
||||||
|
// on successive calls just iterate through the queue until we find an
|
||||||
|
// invalid ref
|
||||||
|
int ret = queue[(*cursor)++]; |
||||||
|
if (!IS_VALID_REF(tree, ret)) { |
||||||
|
free(queue); |
||||||
|
} |
||||||
|
|
||||||
|
return ret; |
||||||
|
} |
||||||
|
#endif // VTREE_IMPL
|
||||||
|
|
||||||
|
#endif // VTREE_DTYPE
|
||||||
|
|
||||||
|
#endif |
||||||
|
|
@ -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
|
|
@ -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. |
|
||||||
------------------------------------------------------------------------------ |
|
||||||
*/ |
|
File diff suppressed because it is too large
Load Diff
@ -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 |
|
@ -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,195 +0,0 @@ |
|||||||
#define _POSIX_C_SOURCE 200809l |
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION |
|
||||||
#define STBTT_STATIC |
|
||||||
#define STB_IMAGE_WRITE_IMPLEMENTATION |
|
||||||
|
|
||||||
#include <grapheme.h> |
|
||||||
#include <assert.h> |
|
||||||
|
|
||||||
#include "font.h" |
|
||||||
#include "stb_truetype.h" |
|
||||||
#include "stb_image_write.h" |
|
||||||
#include "util.h" |
|
||||||
|
|
||||||
#include "generic_cache.h" |
|
||||||
static inline unsigned int hash(unsigned int code) |
|
||||||
{ |
|
||||||
// identity map the ascii range
|
|
||||||
return code < 128 ? code : hash_u32(code); |
|
||||||
} |
|
||||||
CACHE_DECL(cache, struct font_glyph, hash, hash_cmp_u32) |
|
||||||
|
|
||||||
|
|
||||||
#define UTF8(c) (c&0x80) |
|
||||||
#define BDEPTH 1 |
|
||||||
#define BORDER 4 |
|
||||||
|
|
||||||
// FIXME: as of now only monospaced fonts work correctly since no kerning information
|
|
||||||
// is stored
|
|
||||||
|
|
||||||
|
|
||||||
struct priv { |
|
||||||
stbtt_fontinfo stb; |
|
||||||
float scale; |
|
||||||
int baseline; |
|
||||||
unsigned char *bitmap; |
|
||||||
struct cache c; |
|
||||||
}; |
|
||||||
#define PRIV(x) ((struct priv *)x->priv) |
|
||||||
|
|
||||||
|
|
||||||
struct font_atlas * font_init(void) |
|
||||||
{ |
|
||||||
struct font_atlas *p = emalloc(sizeof(struct font_atlas)); |
|
||||||
memset(p, 0, sizeof(struct font_atlas)); |
|
||||||
p->priv = emalloc(sizeof(struct priv)); |
|
||||||
memset(p->priv, 0, sizeof(struct priv)); |
|
||||||
PRIV(p)->c = cache_init(); |
|
||||||
return p; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// loads a font into memory, storing all the ASCII characters in the atlas, each font
|
|
||||||
// atlas structure holds glyphs of a specific size in pixels
|
|
||||||
// NOTE: size includes ascend and descend (so 12 does not mean that 'A' is 12px tall)
|
|
||||||
int font_load(struct font_atlas *atlas, const char *path, int size) |
|
||||||
{ |
|
||||||
if (!atlas || !path) |
|
||||||
return -1; |
|
||||||
|
|
||||||
int err; |
|
||||||
|
|
||||||
dump_file(path, &(atlas->file), &(atlas->file_size)); |
|
||||||
|
|
||||||
err = stbtt_InitFont(&(PRIV(atlas)->stb), (unsigned char *)atlas->file, 0); |
|
||||||
if (err == 0) return -1; |
|
||||||
|
|
||||||
int ascent, descent, linegap, baseline; |
|
||||||
int x0,y0,x1,y1; |
|
||||||
float scale; |
|
||||||
stbtt_GetFontVMetrics(&(PRIV(atlas)->stb), &ascent, &descent, &linegap); |
|
||||||
stbtt_GetFontBoundingBox(&(PRIV(atlas)->stb), &x0, &y0, &x1, &y1); |
|
||||||
scale = stbtt_ScaleForPixelHeight(&(PRIV(atlas)->stb), size); |
|
||||||
baseline = scale * -y0; |
|
||||||
atlas->glyph_max_w = (scale*x1) - (scale*x0); |
|
||||||
atlas->glyph_max_h = (baseline+scale*y1) - (baseline+scale*y0); |
|
||||||
atlas->atlas = emalloc(CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); |
|
||||||
memset(atlas->atlas, 0, CACHE_SIZE*BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); |
|
||||||
PRIV(atlas)->baseline = atlas->glyph_max_h - baseline; |
|
||||||
PRIV(atlas)->scale = scale; |
|
||||||
PRIV(atlas)->bitmap = emalloc(BDEPTH*atlas->glyph_max_w*atlas->glyph_max_h); |
|
||||||
// FIXME: make this a square atlas
|
|
||||||
atlas->width = atlas->glyph_max_w*CACHE_SIZE/4; |
|
||||||
atlas->height = atlas->glyph_max_h*4; |
|
||||||
atlas->size = size; |
|
||||||
|
|
||||||
// preallocate all ascii characters
|
|
||||||
for (char c = ' '; c <= '~'; c++) { |
|
||||||
if (!font_get_glyph_texture(atlas, c, NULL)) |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
int font_free(struct font_atlas *atlas) |
|
||||||
{ |
|
||||||
efree(atlas->atlas); |
|
||||||
efree(atlas->file); |
|
||||||
efree(PRIV(atlas)->bitmap); |
|
||||||
cache_free(&PRIV(atlas)->c); |
|
||||||
efree(atlas->priv); |
|
||||||
efree(atlas); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
// TODO: time and take the median of the time it takes to generate the cache and
|
|
||||||
// the time it takes to draw the glyph
|
|
||||||
const struct font_glyph * font_get_glyph_texture(struct font_atlas *atlas, unsigned int code, int *updated) |
|
||||||
{ |
|
||||||
int _u = 0; |
|
||||||
if (!updated) updated = &_u; |
|
||||||
|
|
||||||
const struct font_glyph *r; |
|
||||||
if ((r = cache_search(&PRIV(atlas)->c, code)) != NULL) { |
|
||||||
*updated = 0; |
|
||||||
return r; |
|
||||||
} |
|
||||||
|
|
||||||
*updated = 1; |
|
||||||
// generate the sdf and put it into the cache
|
|
||||||
// TODO: generate the whole block at once
|
|
||||||
int idx = stbtt_FindGlyphIndex(&PRIV(atlas)->stb, code); |
|
||||||
int x0,y0,x1,y1,gw,gh,l,off_x,off_y,adv,base; |
|
||||||
base = atlas->glyph_max_h - PRIV(atlas)->baseline; |
|
||||||
stbtt_GetGlyphBitmapBoxSubpixel( |
|
||||||
&PRIV(atlas)->stb, |
|
||||||
idx, |
|
||||||
PRIV(atlas)->scale, |
|
||||||
PRIV(atlas)->scale, |
|
||||||
0,0, |
|
||||||
&x0,&y0, |
|
||||||
&x1, &y1); |
|
||||||
gw = x1 - x0; |
|
||||||
gh = y1 - y0; |
|
||||||
stbtt_GetGlyphHMetrics(&PRIV(atlas)->stb, idx, &adv, &l); |
|
||||||
adv *= PRIV(atlas)->scale; |
|
||||||
off_x = PRIV(atlas)->scale*l; |
|
||||||
off_y = atlas->glyph_max_h+y0; |
|
||||||
stbtt_MakeGlyphBitmapSubpixel( |
|
||||||
&PRIV(atlas)->stb, |
|
||||||
PRIV(atlas)->bitmap, |
|
||||||
atlas->glyph_max_w, |
|
||||||
atlas->glyph_max_h, |
|
||||||
atlas->glyph_max_w, |
|
||||||
PRIV(atlas)->scale, |
|
||||||
PRIV(atlas)->scale, |
|
||||||
0, 0, |
|
||||||
idx); |
|
||||||
|
|
||||||
// TODO: bounds check usign atlas height
|
|
||||||
// TODO: clear spot area in the atlas before writing on it
|
|
||||||
unsigned int spot = cache_get_free_spot(&PRIV(atlas)->c); |
|
||||||
unsigned int ty = ((atlas->glyph_max_w * spot) / atlas->width) * atlas->glyph_max_h; |
|
||||||
unsigned int tx = (atlas->glyph_max_w * spot) % atlas->width; |
|
||||||
unsigned int w = atlas->width; |
|
||||||
|
|
||||||
unsigned char *a = (void *)atlas->atlas; |
|
||||||
|
|
||||||
//printf("max:%d %d spot:%d : %d %d %d %d\n", atlas->glyph_max_w, atlas->glyph_max_h, spot, tx, ty, off_x, off_y);
|
|
||||||
|
|
||||||
for (int y = 0; y < gh; y++) { |
|
||||||
for (int x = 0; x < gw; x++) { |
|
||||||
int c, r; |
|
||||||
r = (ty+y)*w; |
|
||||||
c = tx+x; |
|
||||||
a[r+c] = PRIV(atlas)->bitmap[y*atlas->glyph_max_w+x]; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct font_glyph g = { |
|
||||||
.codepoint = code, |
|
||||||
.u = tx, |
|
||||||
.v = ty, |
|
||||||
.w = gw, |
|
||||||
.h = gh, |
|
||||||
.x = off_x, |
|
||||||
.y = off_y-base, |
|
||||||
.a = adv, |
|
||||||
}; |
|
||||||
return cache_insert_at(&PRIV(atlas)->c, &g, g.codepoint, spot); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
void font_dump(const struct font_atlas *atlas, const char *path) |
|
||||||
{ |
|
||||||
stbi_write_png( |
|
||||||
path, |
|
||||||
atlas->width, |
|
||||||
atlas->height, |
|
||||||
BDEPTH, |
|
||||||
atlas->atlas, |
|
||||||
BDEPTH*atlas->width); |
|
||||||
} |
|
@ -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,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 |
|
@ -0,0 +1,69 @@ |
|||||||
|
#define _POSIX_C_SOURCE 200809l |
||||||
|
|
||||||
|
#include <time.h> |
||||||
|
|
||||||
|
#include "timer.h" |
||||||
|
|
||||||
|
const clockid_t CLOCK_ID = CLOCK_PROCESS_CPUTIME_ID; |
||||||
|
|
||||||
|
struct _timer { |
||||||
|
struct timespec start, stop; |
||||||
|
struct timespec times[TIMER_MAX_PARTIAL]; |
||||||
|
} timer = {0}; |
||||||
|
|
||||||
|
int timer_start(void) { return clock_gettime(CLOCK_ID, &timer.start); } |
||||||
|
|
||||||
|
int timer_reset(void) |
||||||
|
{ |
||||||
|
timer = (struct _timer) {0}; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int timer_stop(void) { return clock_gettime(CLOCK_ID, &timer.stop); } |
||||||
|
|
||||||
|
// partial clocks also update the stop time
|
||||||
|
int timer_partial(int idx) |
||||||
|
{ |
||||||
|
if (idx > TIMER_MAX_PARTIAL || idx < 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
clock_gettime(CLOCK_ID, &timer.stop); |
||||||
|
return clock_gettime(CLOCK_ID, &(timer.times[idx])); |
||||||
|
} |
||||||
|
|
||||||
|
size_t timer_get_us(int idx) |
||||||
|
{ |
||||||
|
if (idx > TIMER_MAX_PARTIAL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
struct timespec ts = {0}; |
||||||
|
if (idx < 0) { |
||||||
|
ts = timer.stop; |
||||||
|
} else { |
||||||
|
ts = timer.times[idx]; |
||||||
|
} |
||||||
|
ts.tv_sec -= timer.start.tv_sec; |
||||||
|
ts.tv_nsec -= timer.start.tv_nsec; |
||||||
|
|
||||||
|
// FIXME: check overflow
|
||||||
|
return (ts.tv_nsec / 1000) + (ts.tv_sec * 1000000); |
||||||
|
} |
||||||
|
|
||||||
|
double timer_get_sec(int idx) |
||||||
|
{ |
||||||
|
if (idx > TIMER_MAX_PARTIAL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
struct timespec ts = {0}; |
||||||
|
if (idx < 0) { |
||||||
|
ts = timer.stop; |
||||||
|
} else { |
||||||
|
ts = timer.times[idx]; |
||||||
|
} |
||||||
|
ts.tv_sec -= timer.start.tv_sec; |
||||||
|
ts.tv_nsec -= timer.start.tv_nsec; |
||||||
|
|
||||||
|
return (double)ts.tv_sec + ((double)ts.tv_nsec / 1e9); |
||||||
|
} |
@ -0,0 +1,16 @@ |
|||||||
|
#ifndef UG_TIMER_H_ |
||||||
|
#define UG_TIMER_H_ |
||||||
|
|
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#define TIMER_MAX_PARTIAL 10 |
||||||
|
|
||||||
|
int timer_start(void); |
||||||
|
int timer_stop(void); |
||||||
|
int timer_reset(void); |
||||||
|
int timer_partial(int idx); |
||||||
|
|
||||||
|
size_t timer_get_us(int idx); |
||||||
|
double timer_get_sec(int idx); |
||||||
|
|
||||||
|
#endif |
@ -1,285 +1,105 @@ |
|||||||
#ifndef _UG_HEADER |
#ifndef _UGUI_H |
||||||
#define _UG_HEADER |
#define _UGUI_H |
||||||
|
|
||||||
#define UG_STACK(T) struct { T *items; int idx, size, sorted; } |
#include <stdint.h> |
||||||
#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 struct { |
||||||
typedef unsigned int ug_id_t; |
int32_t x, y, w, h; |
||||||
typedef struct { union {int x, w;}; union {int y, h;}; } ug_vec2_t; |
} UgRect; |
||||||
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; |
|
||||||
|
|
||||||
|
typedef struct { |
||||||
|
int32_t x, y; |
||||||
|
} UgPoint; |
||||||
|
|
||||||
// element type
|
|
||||||
typedef struct { |
typedef struct { |
||||||
ug_id_t id; |
uint8_t r, g, b, a; |
||||||
unsigned short int type; |
} UgColor; |
||||||
unsigned short int flags; |
|
||||||
ug_rect_t rect, rca; |
|
||||||
const char *name; |
|
||||||
union { |
|
||||||
struct { |
|
||||||
const char *txt; |
|
||||||
} btn; |
|
||||||
}; |
|
||||||
} ug_element_t; |
|
||||||
|
|
||||||
enum { |
typedef uint64_t UgId; |
||||||
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 { |
typedef enum { |
||||||
ELEM_CLIPPED = BIT(0), |
ETYPE_NONE = 0, |
||||||
|
ETYPE_DIV, |
||||||
|
ETYPE_BUTTON, |
||||||
|
} UgElemType; |
||||||
|
|
||||||
|
enum UgElemFlags { |
||||||
|
ELEM_UPDATED = 1 << 0, |
||||||
|
ELEM_HASFOCUS = 1 << 1, |
||||||
}; |
}; |
||||||
|
|
||||||
|
enum UgElemEvent { |
||||||
// container type, a container is an entity that contains layouts, a container has
|
EVENT_KEY_PRESS = 1 << 0, |
||||||
// a haight a width and their maximum values, in essence a container is a rectangular
|
EVENT_KEY_RELEASE = 1 << 1, |
||||||
// area that can be resized and sits somewhere on the drawable region
|
EVENT_KEY_HOLD = 1 << 2, |
||||||
// the z index of a container is determined by it's position on the stack
|
EVENT_MOUSE_HOVER = 1 << 3, |
||||||
typedef struct { |
EVENT_MOUSE_PRESS = 1 << 4, |
||||||
ug_id_t id; |
EVENT_MOUSE_RELEASE = 1 << 5, |
||||||
const char *name; |
EVENT_MOUSE_HOLD = 1 << 6, |
||||||
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 { |
typedef struct { |
||||||
struct { ug_color_t bg, fg; } color; |
UgId id; |
||||||
ug_size_t margin; |
uint32_t flags; |
||||||
struct { |
uint32_t event; |
||||||
ug_color_t color; |
UgRect rect; |
||||||
ug_size_t size; |
UgElemType type; |
||||||
} 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; |
|
||||||
|
|
||||||
|
// type-specific fields
|
||||||
// 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 { |
union { |
||||||
struct ug_cmd_rect rect; |
struct UgDiv { |
||||||
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 { |
enum { |
||||||
UG_SIDE_TOP = 0, |
DIV_LAYOUT_ROW = 0, |
||||||
UG_SIDE_BOTTOM, |
DIV_LAYOUT_COLUMN, |
||||||
UG_SIDE_LEFT, |
DIV_LAYOUT_FLOATING, |
||||||
UG_SIDE_RIGHT, |
} layout; |
||||||
|
|
||||||
|
UgPoint origin_r, origin_c; |
||||||
|
UgColor color_bg; |
||||||
|
} div; // Div
|
||||||
}; |
}; |
||||||
|
} UgElem; |
||||||
|
|
||||||
// mouse buttons
|
// TODO: add a packed flag
|
||||||
enum { |
// TODO: add a fill index to skip some searching for free spots
|
||||||
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 { |
typedef struct { |
||||||
// some style information
|
int size, elements; |
||||||
const ug_style_t *style; |
UgId *vector; // vector of element ids
|
||||||
// style_px is a style cache where all measurements are already in pixels
|
int *refs, *ordered_refs; |
||||||
const ug_style_t *style_px; |
} UgTree; |
||||||
// 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
|
typedef struct { |
||||||
// window inside another window
|
struct _IdTable *table; |
||||||
int ug_container_floating(ug_ctx_t *ctx, const char *name, ug_div_t div); |
UgElem *array; |
||||||
// like a floating container but cannot be resized
|
uint64_t *present, *used; |
||||||
int ug_container_popup(ug_ctx_t *ctx, const char *name, ug_div_t div); |
int cycles; |
||||||
// a menu bar is a container of fixed height, cannot be resized and sits at the
|
} UgElemCache; |
||||||
// top of the window
|
|
||||||
int ug_container_menu_bar(ug_ctx_t *ctx, const char *name, ug_size_t height); |
typedef struct _UgCtx UgCtx; |
||||||
// 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); |
// tree implementation
|
||||||
// a body is a container that scales with the window, sits at it's center and cannot
|
int ug_tree_init(UgTree *tree, unsigned int size); |
||||||
// be resized, it also fills all the available space
|
int ug_tree_pack(UgTree *tree); |
||||||
int ug_container_body(ug_ctx_t *ctx, const char *name); |
int ug_tree_resize(UgTree *tree, unsigned int newsize); |
||||||
// mark a conatiner for removal, it will be freed at the next frame beginning if
|
int ug_tree_add(UgTree *tree, UgId elem, int parent); |
||||||
// name is NULL then use the selected container
|
int ug_tree_prune(UgTree *tree, int ref); |
||||||
int ug_container_remove(ug_ctx_t *ctx, const char *name); |
int ug_tree_subtree_size(UgTree *tree, int ref); |
||||||
// get the drawable area of the container, if name is NULL then use the selected
|
int ug_tree_children_it(UgTree *tree, int parent, int *cursor); |
||||||
// container
|
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor); |
||||||
ug_rect_t ug_container_get_rect(ug_ctx_t *ctx, const char *name); |
int ug_tree_parentof(UgTree *tree, int node); |
||||||
|
int ug_tree_destroy(UgTree *tree); |
||||||
// layouting, the following functions define how different ui elements are placed
|
UgId ug_tree_get(UgTree *tree, int node); |
||||||
// inside the selected container. A new element is aligned respective to the
|
|
||||||
// previous element and/or to the container, particularly elements can be placed
|
// cache implementation
|
||||||
// in a row or a column.
|
UgElemCache ug_cache_init(void); |
||||||
int ug_layout_row(ug_ctx_t *ctx); |
void ug_cache_free(UgElemCache *cache); |
||||||
int ug_layout_column(ug_ctx_t *ctx); |
UgElem *ug_cache_search(UgElemCache *cache, UgId id); |
||||||
int ug_layout_next_row(ug_ctx_t *ctx); |
UgElem *ug_cache_insert_new(UgElemCache *cache, const UgElem *g, uint32_t *index); |
||||||
int ug_layout_next_column(ug_ctx_t *ctx); |
|
||||||
|
int ug_init(UgCtx *ctx); |
||||||
// elements
|
int ug_destroy(UgCtx *ctx); |
||||||
int ug_element_button(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim); |
int ug_frame_begin(UgCtx *ctx); |
||||||
int ug_element_textbtn(ug_ctx_t *ctx, const char *name, const char *txt, ug_div_t dim); |
int ug_frame_end(UgCtx *ctx); |
||||||
|
|
||||||
// Input functions
|
#endif // _UGUI_H
|
||||||
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 |
|
||||||
|
@ -0,0 +1,355 @@ |
|||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
#include "ugui.h" |
||||||
|
|
||||||
|
#define IS_VALID_REF(t, r) ((r) >= 0 && (r) < (t)->size) |
||||||
|
#define REF_IS_PRESENT(t, r) ((t)->refs[r] >= 0) |
||||||
|
|
||||||
|
int ug_tree_init(UgTree *tree, unsigned int size) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector = malloc(sizeof(UgId) * size); |
||||||
|
if (tree->vector == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->refs = malloc(sizeof(int) * size); |
||||||
|
if (tree->refs == NULL) { |
||||||
|
free(tree->vector); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// ordered refs are used in the iterators
|
||||||
|
tree->ordered_refs = malloc(sizeof(int) * (size + 1)); |
||||||
|
if (tree->ordered_refs == NULL) { |
||||||
|
free(tree->vector); |
||||||
|
free(tree->refs); |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// set all refs to -1, meaning invalid (free) element
|
||||||
|
for (unsigned int i = 0; i < size; i++) { |
||||||
|
tree->refs[i] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
// fill vector with zeroes
|
||||||
|
memset(tree->vector, 0, size * sizeof(UgId)); |
||||||
|
|
||||||
|
tree->size = size; |
||||||
|
tree->elements = 0; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int ug_tree_destroy(UgTree *tree) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
free(tree->vector); |
||||||
|
free(tree->refs); |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int ug_tree_pack(UgTree *tree) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: add a PACKED flag to skip this
|
||||||
|
|
||||||
|
int free_spot = -1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == -1) { |
||||||
|
free_spot = i; |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
// find a item that can be packed
|
||||||
|
if (free_spot >= 0 && tree->refs[i] >= 0) { |
||||||
|
int old_ref = i; |
||||||
|
|
||||||
|
// move the item
|
||||||
|
tree->vector[free_spot] = tree->vector[i]; |
||||||
|
tree->refs[free_spot] = tree->refs[i]; |
||||||
|
|
||||||
|
tree->vector[i] = 0; |
||||||
|
tree->refs[i] = -1; |
||||||
|
|
||||||
|
// and move all references
|
||||||
|
for (int x = 0; x < tree->size; x++) { |
||||||
|
if (tree->refs[x] == old_ref) { |
||||||
|
tree->refs[x] = free_spot; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// mark the free spot as used
|
||||||
|
free_spot = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int ug_tree_resize(UgTree *tree, unsigned int newsize) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// return error when shrinking with too many elements
|
||||||
|
if ((int)newsize < tree->elements) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// pack the vector when shrinking to avoid data loss
|
||||||
|
if ((int)newsize < tree->size) { |
||||||
|
// if (ug_tree_pack(tree) < 0) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// TODO: allow shrinking, since packing destroys all references
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
UgId *newvec = realloc(tree->vector, newsize * sizeof(UgId)); |
||||||
|
if (newvec == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int *newrefs = realloc(tree->refs, newsize * sizeof(int)); |
||||||
|
if (newrefs == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int *neworrefs = realloc(tree->ordered_refs, (newsize + 1) * sizeof(int)); |
||||||
|
if (neworrefs == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector = newvec; |
||||||
|
tree->refs = newrefs; |
||||||
|
tree->ordered_refs = neworrefs; |
||||||
|
if ((int)newsize > tree->size) { |
||||||
|
for (int i = tree->size; i < (int)newsize; i++) { |
||||||
|
tree->vector[i] = 0; |
||||||
|
tree->refs[i] = -1; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
tree->size = newsize; |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
// add an element to the tree, return it's ref
|
||||||
|
int ug_tree_add(UgTree *tree, UgId elem, int parent) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// invalid parent
|
||||||
|
if (!IS_VALID_REF(tree, parent)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// no space left
|
||||||
|
if (tree->elements >= tree->size) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// check if the parent exists
|
||||||
|
// if there are no elements in the tree the first add will set the root
|
||||||
|
if (!REF_IS_PRESENT(tree, parent) && tree->elements != 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// get the first free spot
|
||||||
|
int free_spot = -1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == -1) { |
||||||
|
free_spot = i; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (free_spot < 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// finally add the element
|
||||||
|
tree->vector[free_spot] = elem; |
||||||
|
tree->refs[free_spot] = parent; |
||||||
|
tree->elements++; |
||||||
|
|
||||||
|
return free_spot; |
||||||
|
} |
||||||
|
|
||||||
|
// prune the tree starting from the ref
|
||||||
|
// returns the number of pruned elements
|
||||||
|
int ug_tree_prune(UgTree *tree, int ref) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!IS_VALID_REF(tree, ref)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!REF_IS_PRESENT(tree, ref)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
tree->vector[ref] = 0; |
||||||
|
tree->refs[ref] = -1; |
||||||
|
tree->elements--; |
||||||
|
|
||||||
|
int count = 1; |
||||||
|
for (int i = 0; tree->elements > 0 && i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == ref) { |
||||||
|
count += ug_tree_prune(tree, i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
// find the size of the subtree starting from ref
|
||||||
|
int ug_tree_subtree_size(UgTree *tree, int ref) |
||||||
|
{ |
||||||
|
if (tree == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!IS_VALID_REF(tree, ref)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
if (!REF_IS_PRESENT(tree, ref)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int count = 1; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
// only root has the reference to itself
|
||||||
|
if (tree->refs[i] == ref && ref != i) { |
||||||
|
count += ug_tree_subtree_size(tree, i); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return count; |
||||||
|
} |
||||||
|
|
||||||
|
// iterate through the first level children, use a cursor like strtok_r
|
||||||
|
int ug_tree_children_it(UgTree *tree, int parent, int *cursor) |
||||||
|
{ |
||||||
|
if (tree == NULL || cursor == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// if the cursor is out of bounds then we are done for sure
|
||||||
|
if (!IS_VALID_REF(tree, *cursor)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// same for the parent, if it's invalid it can't have children
|
||||||
|
if (!IS_VALID_REF(tree, parent) || !REF_IS_PRESENT(tree, parent)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// find the first child, update the cursor and return the ref
|
||||||
|
for (int i = *cursor; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == parent) { |
||||||
|
*cursor = i + 1; |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// if no children are found return -1
|
||||||
|
*cursor = -1; |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/* iterates trough every leaf of the subtree in the following manner
|
||||||
|
* node [x], x: visit order |
||||||
|
* [0] |
||||||
|
* / | \
|
||||||
|
* / [2] [3] |
||||||
|
* [1] | |
||||||
|
* / \ [6] |
||||||
|
* [4] [5] |
||||||
|
*/ |
||||||
|
int ug_tree_level_order_it(UgTree *tree, int ref, int *cursor) |
||||||
|
{ |
||||||
|
if (tree == NULL || cursor == NULL) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int *queue = tree->ordered_refs; |
||||||
|
|
||||||
|
// TODO: this could also be done when adding or removing elements
|
||||||
|
// first call, create a ref array ordered like we desire
|
||||||
|
if (*cursor == -1) { |
||||||
|
*cursor = 0; |
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
queue[i] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
// iterate through the queue appending found children
|
||||||
|
int pos = 0, off = 0; |
||||||
|
do { |
||||||
|
// printf ("ref=%d\n", ref);
|
||||||
|
|
||||||
|
for (int i = 0; i < tree->size; i++) { |
||||||
|
if (tree->refs[i] == ref) { |
||||||
|
queue[pos++] = i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (; ref == queue[off] && off < tree->size; off++) |
||||||
|
; |
||||||
|
ref = queue[off]; |
||||||
|
|
||||||
|
} while (IS_VALID_REF(tree, ref)); |
||||||
|
// This line is why tree->ordered_refs has to be size+1
|
||||||
|
queue[off + 1] = -1; |
||||||
|
} |
||||||
|
|
||||||
|
// PRINT_ARR(queue, tree->size);
|
||||||
|
// return -1;
|
||||||
|
|
||||||
|
// on successive calls just iterate through the queue until we find an
|
||||||
|
// invalid ref, if the user set the cursor to -1 it means it has found what
|
||||||
|
// he needed, so free
|
||||||
|
if (*cursor < 0) { |
||||||
|
return -1; |
||||||
|
} else if (IS_VALID_REF(tree, *cursor)) { |
||||||
|
return queue[(*cursor)++]; |
||||||
|
} |
||||||
|
|
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
int ug_tree_parentof(UgTree *tree, int node) |
||||||
|
{ |
||||||
|
if (tree == NULL || !IS_VALID_REF(tree, node) || |
||||||
|
!REF_IS_PRESENT(tree, node)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return tree->refs[node]; |
||||||
|
} |
||||||
|
|
||||||
|
UgId ug_tree_get(UgTree *tree, int node) |
||||||
|
{ |
||||||
|
if (tree == NULL || !IS_VALID_REF(tree, node)) { |
||||||
|
return 0; |
||||||
|
} |
||||||
|
return tree->vector[node]; |
||||||
|
} |
Loading…
Reference in new issue