started work on an atlas interface, DOES NOT WORK
This commit is contained in:
parent
5a89e9ec7d
commit
fa4d3ed0ec
15
TODO
15
TODO
@ -1,4 +1,6 @@
|
||||
# TODOs, semi-random sorting
|
||||
|
||||
[ ] Check every instance of foreach to see if I am using by-copy or by-reference correctly
|
||||
[x] Implement glyph draw command
|
||||
[x] Implement div.view and scrollbars
|
||||
[ ] Port font system from C to C3 (rewrite1)
|
||||
@ -7,26 +9,33 @@
|
||||
[ ] Use an arena allocator for cache
|
||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||
[ ] Better handling of the active and focused widgets, try
|
||||
to maintain focus until mouse release (fix scroll bars)
|
||||
to maintain focus until mouse release (fix scroll bars)
|
||||
[ ] Write a description for each file and the structs, interfaces provided
|
||||
|
||||
## Commands
|
||||
|
||||
[x] rect commads should have:
|
||||
* border width
|
||||
* border radius
|
||||
_ border width
|
||||
_ border radius
|
||||
[x] add a command to update an atlas
|
||||
|
||||
## Atlases
|
||||
|
||||
[ ] Add an interface to create, destroy, update and get atlases based on their ids
|
||||
[ ] Implement multiple font atlases
|
||||
[ ] Create and use ShortIds for atlases
|
||||
|
||||
## Fonts
|
||||
|
||||
[ ] Fix the missing alpha channel
|
||||
[x] Fix the alignment
|
||||
|
||||
## Raylib
|
||||
|
||||
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
||||
[x] Implement pixel radius rounding for border radius
|
||||
|
||||
## Widgets
|
||||
|
||||
[ ] Dynamic text box to implement an fps counter
|
||||
[ ] Button with label
|
||||
|
183
src/ugui_atlas.c3
Normal file
183
src/ugui_atlas.c3
Normal file
@ -0,0 +1,183 @@
|
||||
module ugui;
|
||||
|
||||
import std::core::mem;
|
||||
import std::math::random;
|
||||
|
||||
random::SimpleRandom atlas_seed;
|
||||
|
||||
fault UgAtlasError {
|
||||
CANNOT_PLACE,
|
||||
INVALID_FORMAT,
|
||||
MISSING_ATLAS,
|
||||
}
|
||||
|
||||
enum AtlasFormat {
|
||||
ATLAS_GRAYSCALE,
|
||||
ATLAS_ALPHA,
|
||||
}
|
||||
|
||||
// black and white atlas
|
||||
struct Atlas {
|
||||
AtlasFormat format;
|
||||
Id id;
|
||||
ushort width, height;
|
||||
char[] buffer;
|
||||
|
||||
Point row;
|
||||
ushort row_h;
|
||||
}
|
||||
|
||||
// FIXME: allocate atlases dynamically
|
||||
fn void! Ctx.init_atlases(&ctx)
|
||||
{
|
||||
atlas_seed.set_seed("ciao mamma");
|
||||
ctx.atlas = mem::new_array(Atlas, MAX_ATLAS);
|
||||
}
|
||||
|
||||
fn void Ctx.free_atlases(&ctx)
|
||||
{
|
||||
foreach (a: ctx.atlas) {
|
||||
a.free();
|
||||
}
|
||||
mem::free(ctx.atlas);
|
||||
}
|
||||
|
||||
// FIXME: this could be slow
|
||||
fn Atlas*! Ctx.get_atlas(&ctx, Id id)
|
||||
{
|
||||
foreach (i, a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
return &ctx.atlas[i];
|
||||
}
|
||||
}
|
||||
return UgAtlasError.MISSING_ATLAS?;
|
||||
}
|
||||
|
||||
// TODO: use the same allocator as all the other parts of the code
|
||||
fn Atlas*! Ctx.request_new_atlas(&ctx, AtlasFormat format, Id *rid, ushort width, ushort height)
|
||||
{
|
||||
usz idx;
|
||||
bool free = false;
|
||||
for (; idx < ctx.atlas.len; idx++) {
|
||||
if (ctx.atlas[idx].id == 0) {
|
||||
free = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (free) {
|
||||
*rid = ctx.unique_atlas_id();
|
||||
ctx.atlas[idx].new(format, *rid, width, height)!;
|
||||
return &ctx.atlas[idx];
|
||||
} else {
|
||||
// TODO: reallocate the atlas vector
|
||||
return UgAtlasError.MISSING_ATLAS?;
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: WTF
|
||||
fn Id Ctx.unique_atlas_id(&ctx) {
|
||||
Id id = atlas_seed.next_long();
|
||||
bool ok = true;
|
||||
foreach (a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
return ok ? id : ctx.unique_atlas_id();
|
||||
}
|
||||
|
||||
fn void Ctx.discard_atlas(&ctx, Id id)
|
||||
{
|
||||
foreach (&a: ctx.atlas) {
|
||||
if (a.id == id) {
|
||||
a.free();
|
||||
*a = Atlas{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Try to place a sprite into an atlas of the correct type and return it's id
|
||||
// new_width and new_height are used in case a new atlas has to be requested, in that
|
||||
// case they are used as width and height for the new atlas
|
||||
// FIXME: maybe there has to be a default value
|
||||
fn Id! Ctx.place_sprite(&ctx, Point* uv, AtlasFormat format, char[] pixels, ushort w, ushort h, ushort new_width, ushort new_height)
|
||||
{
|
||||
// try to place the sprite into an existing atlas
|
||||
foreach (&a: ctx.atlas) {
|
||||
if (a.id != 0 && a.format == format) {
|
||||
Point! ouv = a.place(pixels, w, h);
|
||||
if (catch ouv) {
|
||||
continue;
|
||||
}
|
||||
*uv = ouv;
|
||||
return a.id;
|
||||
}
|
||||
}
|
||||
|
||||
// fail... try to request a new atlas and place it there
|
||||
Id id;
|
||||
Atlas* atlas = ctx.request_new_atlas(format, &id, new_width, new_height)!;
|
||||
*uv = atlas.place(pixels, w, h)!;
|
||||
return id;
|
||||
}
|
||||
|
||||
fn void! Atlas.new(&atlas, AtlasFormat format, Id id, ushort width, ushort height)
|
||||
{
|
||||
atlas.format = format;
|
||||
atlas.id = id;
|
||||
atlas.width = width;
|
||||
atlas.height = height;
|
||||
|
||||
usz bpp = 1;
|
||||
switch (format) {
|
||||
case ATLAS_GRAYSCALE: nextcase;
|
||||
case ATLAS_ALPHA:
|
||||
bpp = 1;
|
||||
default:
|
||||
return UgAtlasError.INVALID_FORMAT?;
|
||||
}
|
||||
usz size = ((usz)atlas.width*atlas.height) * bpp;
|
||||
atlas.buffer = mem::new_array(char, size);
|
||||
}
|
||||
|
||||
fn void Atlas.free(&atlas)
|
||||
{
|
||||
free(atlas.buffer);
|
||||
}
|
||||
|
||||
// place a rect inside the atlas
|
||||
// uses a row first algorithm
|
||||
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
|
||||
// FIXME: untested with non-1bpp buffer depths
|
||||
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h)
|
||||
{
|
||||
Point p;
|
||||
|
||||
// TODO: simplify this and use rect_collision() and such
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
atlas.row.x = 0;
|
||||
atlas.row.y = atlas.row.y + atlas.row_h;
|
||||
atlas.row_h = 0;
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
return UgAtlasError.CANNOT_PLACE?;
|
||||
}
|
||||
}
|
||||
|
||||
for (usz y = 0; y < h; y++) {
|
||||
for (usz x = 0; x < w; x++) {
|
||||
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x];
|
||||
}
|
||||
}
|
||||
|
||||
atlas.row.x += w;
|
||||
if (h > atlas.row_h) {
|
||||
atlas.row_h = h;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
@ -127,6 +127,7 @@ const uint STACK_STEP = 10;
|
||||
const uint MAX_ELEMS = 128;
|
||||
const uint MAX_CMDS = 256;
|
||||
const uint ROOT_ID = 1;
|
||||
const uint MAX_ATLAS = 2;
|
||||
|
||||
// command type
|
||||
enum CmdType {
|
||||
@ -193,6 +194,8 @@ struct Ctx {
|
||||
// total size in pixels of the context
|
||||
ushort width, height;
|
||||
Style style;
|
||||
|
||||
Atlas[] atlas;
|
||||
Font font;
|
||||
|
||||
bool has_focus;
|
||||
|
@ -34,7 +34,7 @@ struct Glyph {
|
||||
ushort u, v;
|
||||
ushort w, h;
|
||||
short adv, ox, oy;
|
||||
short idx; // atlas index
|
||||
Id atlas_id;
|
||||
}
|
||||
|
||||
const uint FONT_CACHED = 512;
|
||||
@ -47,19 +47,6 @@ fault UgFontError {
|
||||
RENDER_ERROR,
|
||||
}
|
||||
|
||||
fault UgAtlasError {
|
||||
CANNOT_PLACE,
|
||||
}
|
||||
|
||||
// black and white atlas
|
||||
struct AtlasBW {
|
||||
ushort width, height;
|
||||
char[] buffer;
|
||||
|
||||
Point row;
|
||||
ushort row_h;
|
||||
}
|
||||
|
||||
struct Font {
|
||||
schrift::Sft sft;
|
||||
String path;
|
||||
@ -67,59 +54,14 @@ struct Font {
|
||||
|
||||
float size;
|
||||
float ascender, descender, linegap; // Line Metrics
|
||||
AtlasBW[] atlas;
|
||||
}
|
||||
|
||||
fn void! AtlasBW.new(&atlas, ushort width, ushort height)
|
||||
{
|
||||
atlas.width = width;
|
||||
atlas.height = height;
|
||||
atlas.buffer = mem::new_array(char, (usz)atlas.width*atlas.height);
|
||||
}
|
||||
|
||||
fn void AtlasBW.free(&atlas)
|
||||
{
|
||||
free(atlas.buffer);
|
||||
}
|
||||
|
||||
// place a rect inside the atlas
|
||||
// uses a row first algorithm
|
||||
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
|
||||
fn Point! AtlasBW.place(&atlas, char[] pixels, ushort w, ushort h)
|
||||
{
|
||||
Point p;
|
||||
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
atlas.row.x = 0;
|
||||
atlas.row.y = atlas.row.y + atlas.row_h;
|
||||
atlas.row_h = 0;
|
||||
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
|
||||
p = atlas.row;
|
||||
} else {
|
||||
return UgAtlasError.CANNOT_PLACE?;
|
||||
}
|
||||
}
|
||||
|
||||
for (usz y = 0; y < h; y++) {
|
||||
for (usz x = 0; x < w; x++) {
|
||||
atlas.buffer[(usz)(p.y+y)*atlas.width + (p.x+x)] = pixels[y*w + x];
|
||||
}
|
||||
}
|
||||
|
||||
atlas.row.x += w;
|
||||
if (h > atlas.row_h) {
|
||||
atlas.row_h = h;
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
fn void! Font.load(&font, String path, uint height, float scale = 1)
|
||||
|
||||
fn void! Ctx.font_load(&cxt, String path, uint height, float scale = 1)
|
||||
{
|
||||
Font* font = ctx.font;
|
||||
font.table.new_init(capacity: FONT_CACHED);
|
||||
|
||||
|
||||
font.size = height*scale;
|
||||
|
||||
font.sft = schrift::Sft{
|
||||
@ -139,24 +81,14 @@ fn void! Font.load(&font, String path, uint height, float scale = 1)
|
||||
font.descender = (float)lmetrics.descender;
|
||||
font.linegap = (float)lmetrics.lineGap;
|
||||
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.linegap);
|
||||
|
||||
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
|
||||
// like the letter 'A'
|
||||
font.atlas = mem::new_array(AtlasBW, 1);
|
||||
ushort size = (ushort)font.size*256;
|
||||
font.atlas[0].new(size, size)!;
|
||||
|
||||
// preallocate the ASCII range
|
||||
// for (char c = ' '; c < '~'; c++) {
|
||||
// font.get_glyph((Codepoint)c)!;
|
||||
// }
|
||||
}
|
||||
|
||||
fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
|
||||
fn Glyph*! Ctx.get_glyph(&ctx, Codepoint code, bool* is_new = null)
|
||||
{
|
||||
Font* font = &ctx.font;
|
||||
Glyph*! gp;
|
||||
gp = font.table.get_ref(code);
|
||||
|
||||
|
||||
if (catch excuse = gp) {
|
||||
if (excuse != SearchResult.MISSING) {
|
||||
return excuse?;
|
||||
@ -171,7 +103,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
|
||||
|
||||
schrift::SftGlyph gid;
|
||||
schrift::SftGMetrics gmtx;
|
||||
|
||||
|
||||
if (schrift::lookup(&font.sft, code, &gid) < 0) {
|
||||
return UgFontError.MISSING_GLYPH?;
|
||||
}
|
||||
@ -199,8 +131,12 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
|
||||
//io::printfn("code=%c, w=%d, h=%d, ox=%d, oy=%d, adv=%d",
|
||||
// glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
|
||||
|
||||
Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!;
|
||||
glyph.idx = 0;
|
||||
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
|
||||
// like the letter 'A'
|
||||
ushort size = (ushort)font.size*256;
|
||||
Point uv;
|
||||
Id id = ctx.place_sprite(&uv, ATLAS_ALPHA, pixels, glyph.w, glyph.h, size, size)!;
|
||||
glyph.atlas_id = id;
|
||||
glyph.u = uv.x;
|
||||
glyph.v = uv.y;
|
||||
|
||||
@ -212,10 +148,8 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
|
||||
return font.table.get_ref(code);
|
||||
}
|
||||
|
||||
// FIXME: maybe some atlases should be exclusive to fonts
|
||||
fn void Font.free(&font)
|
||||
{
|
||||
foreach (atlas: font.atlas) {
|
||||
atlas.free();
|
||||
}
|
||||
schrift::freefont(font.sft.font);
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas)
|
||||
off += x;
|
||||
bool n;
|
||||
if (!ascii::is_cntrl((char)cp)) {
|
||||
gp = ctx.font.get_glyph(cp, &n)!;
|
||||
gp = ctx.get_glyph(cp, &n)!;
|
||||
line_len += gp.adv;
|
||||
if (n) { *update_atlas = true; }
|
||||
} else if (cp == '\n'){
|
||||
|
Loading…
Reference in New Issue
Block a user