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
|
# 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 glyph draw command
|
||||||
[x] Implement div.view and scrollbars
|
[x] Implement div.view and scrollbars
|
||||||
[ ] Port font system from C to C3 (rewrite1)
|
[ ] Port font system from C to C3 (rewrite1)
|
||||||
@ -7,26 +9,33 @@
|
|||||||
[ ] Use an arena allocator for cache
|
[ ] Use an arena allocator for cache
|
||||||
[ ] Do not redraw if there was no update (no layout and no draw)
|
[ ] Do not redraw if there was no update (no layout and no draw)
|
||||||
[ ] Better handling of the active and focused widgets, try
|
[ ] 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
|
## Commands
|
||||||
|
|
||||||
[x] rect commads should have:
|
[x] rect commads should have:
|
||||||
* border width
|
_ border width
|
||||||
* border radius
|
_ border radius
|
||||||
[x] add a command to update an atlas
|
[x] add a command to update an atlas
|
||||||
|
|
||||||
## Atlases
|
## Atlases
|
||||||
|
|
||||||
[ ] Add an interface to create, destroy, update and get atlases based on their ids
|
[ ] Add an interface to create, destroy, update and get atlases based on their ids
|
||||||
[ ] Implement multiple font atlases
|
[ ] Implement multiple font atlases
|
||||||
|
[ ] Create and use ShortIds for atlases
|
||||||
|
|
||||||
## Fonts
|
## Fonts
|
||||||
|
|
||||||
[ ] Fix the missing alpha channel
|
[ ] Fix the missing alpha channel
|
||||||
[x] Fix the alignment
|
[x] Fix the alignment
|
||||||
|
|
||||||
## Raylib
|
## Raylib
|
||||||
|
|
||||||
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
[ ] Implement type (Rect, Color, Point) conversion functions between rl:: and ugui::
|
||||||
[x] Implement pixel radius rounding for border radius
|
[x] Implement pixel radius rounding for border radius
|
||||||
|
|
||||||
## Widgets
|
## Widgets
|
||||||
|
|
||||||
[ ] Dynamic text box to implement an fps counter
|
[ ] Dynamic text box to implement an fps counter
|
||||||
[ ] Button with label
|
[ ] 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_ELEMS = 128;
|
||||||
const uint MAX_CMDS = 256;
|
const uint MAX_CMDS = 256;
|
||||||
const uint ROOT_ID = 1;
|
const uint ROOT_ID = 1;
|
||||||
|
const uint MAX_ATLAS = 2;
|
||||||
|
|
||||||
// command type
|
// command type
|
||||||
enum CmdType {
|
enum CmdType {
|
||||||
@ -193,6 +194,8 @@ struct Ctx {
|
|||||||
// total size in pixels of the context
|
// total size in pixels of the context
|
||||||
ushort width, height;
|
ushort width, height;
|
||||||
Style style;
|
Style style;
|
||||||
|
|
||||||
|
Atlas[] atlas;
|
||||||
Font font;
|
Font font;
|
||||||
|
|
||||||
bool has_focus;
|
bool has_focus;
|
||||||
|
@ -34,7 +34,7 @@ struct Glyph {
|
|||||||
ushort u, v;
|
ushort u, v;
|
||||||
ushort w, h;
|
ushort w, h;
|
||||||
short adv, ox, oy;
|
short adv, ox, oy;
|
||||||
short idx; // atlas index
|
Id atlas_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint FONT_CACHED = 512;
|
const uint FONT_CACHED = 512;
|
||||||
@ -47,19 +47,6 @@ fault UgFontError {
|
|||||||
RENDER_ERROR,
|
RENDER_ERROR,
|
||||||
}
|
}
|
||||||
|
|
||||||
fault UgAtlasError {
|
|
||||||
CANNOT_PLACE,
|
|
||||||
}
|
|
||||||
|
|
||||||
// black and white atlas
|
|
||||||
struct AtlasBW {
|
|
||||||
ushort width, height;
|
|
||||||
char[] buffer;
|
|
||||||
|
|
||||||
Point row;
|
|
||||||
ushort row_h;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Font {
|
struct Font {
|
||||||
schrift::Sft sft;
|
schrift::Sft sft;
|
||||||
String path;
|
String path;
|
||||||
@ -67,59 +54,14 @@ struct Font {
|
|||||||
|
|
||||||
float size;
|
float size;
|
||||||
float ascender, descender, linegap; // Line Metrics
|
float ascender, descender, linegap; // Line Metrics
|
||||||
AtlasBW[] atlas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn void! AtlasBW.new(&atlas, ushort width, ushort height)
|
|
||||||
{
|
fn void! Ctx.font_load(&cxt, String path, uint height, float scale = 1)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
Font* font = ctx.font;
|
||||||
font.table.new_init(capacity: FONT_CACHED);
|
font.table.new_init(capacity: FONT_CACHED);
|
||||||
|
|
||||||
font.size = height*scale;
|
font.size = height*scale;
|
||||||
|
|
||||||
font.sft = schrift::Sft{
|
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.descender = (float)lmetrics.descender;
|
||||||
font.linegap = (float)lmetrics.lineGap;
|
font.linegap = (float)lmetrics.lineGap;
|
||||||
//io::printfn("ascender:%d, descender:%d, linegap:%d", font.ascender, font.descender, font.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;
|
Glyph*! gp;
|
||||||
gp = font.table.get_ref(code);
|
gp = font.table.get_ref(code);
|
||||||
|
|
||||||
if (catch excuse = gp) {
|
if (catch excuse = gp) {
|
||||||
if (excuse != SearchResult.MISSING) {
|
if (excuse != SearchResult.MISSING) {
|
||||||
return excuse?;
|
return excuse?;
|
||||||
@ -171,7 +103,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code, bool* is_new = null)
|
|||||||
|
|
||||||
schrift::SftGlyph gid;
|
schrift::SftGlyph gid;
|
||||||
schrift::SftGMetrics gmtx;
|
schrift::SftGMetrics gmtx;
|
||||||
|
|
||||||
if (schrift::lookup(&font.sft, code, &gid) < 0) {
|
if (schrift::lookup(&font.sft, code, &gid) < 0) {
|
||||||
return UgFontError.MISSING_GLYPH?;
|
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",
|
//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);
|
// glyph.code, glyph.w, glyph.h, glyph.ox, glyph.oy, glyph.adv);
|
||||||
|
|
||||||
Point uv = font.atlas[0].place(pixels, glyph.w, glyph.h)!;
|
// TODO: allocate buffer based on FONT_CACHED and the size of a sample letter
|
||||||
glyph.idx = 0;
|
// 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.u = uv.x;
|
||||||
glyph.v = uv.y;
|
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);
|
return font.table.get_ref(code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: maybe some atlases should be exclusive to fonts
|
||||||
fn void Font.free(&font)
|
fn void Font.free(&font)
|
||||||
{
|
{
|
||||||
foreach (atlas: font.atlas) {
|
|
||||||
atlas.free();
|
|
||||||
}
|
|
||||||
schrift::freefont(font.sft.font);
|
schrift::freefont(font.sft.font);
|
||||||
}
|
}
|
||||||
|
@ -34,7 +34,7 @@ fn Rect! Ctx.get_text_bounds(&ctx, String text, bool* update_atlas)
|
|||||||
off += x;
|
off += x;
|
||||||
bool n;
|
bool n;
|
||||||
if (!ascii::is_cntrl((char)cp)) {
|
if (!ascii::is_cntrl((char)cp)) {
|
||||||
gp = ctx.font.get_glyph(cp, &n)!;
|
gp = ctx.get_glyph(cp, &n)!;
|
||||||
line_len += gp.adv;
|
line_len += gp.adv;
|
||||||
if (n) { *update_atlas = true; }
|
if (n) { *update_atlas = true; }
|
||||||
} else if (cp == '\n'){
|
} else if (cp == '\n'){
|
||||||
|
Loading…
Reference in New Issue
Block a user