update project to c3 0.7.1

This commit is contained in:
Alessandro Mauri 2025-05-05 16:23:26 +02:00
parent 34e75f8c06
commit 79a2d66880
18 changed files with 324 additions and 238 deletions

34
TODO
View File

@ -13,6 +13,8 @@ to maintain focus until mouse release (fix scroll bars)
[x] Clip element bounds to parent div, specifically text [x] Clip element bounds to parent div, specifically text
[ ] Resizeable divs [ ] Resizeable divs
[ ] Implement a z index and sort command buffer based on that [ ] Implement a z index and sort command buffer based on that
[ ] Ctx.set_z_index()
[ ] Sort command buffer on insertion
[ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it [ ] Standardize element handling, for example all buttons do almost the same thing, so write a lot of boiler plate and reuse it
[x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division [x] The id combination in gen_id() uses an intger division, which is costly, use another combination function that is non-linear and doesn't use division
[ ] Animations, somehow [ ] Animations, somehow
@ -33,12 +35,19 @@ to maintain focus until mouse release (fix scroll bars)
[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for [ ] Subdivide modules into ugui::ug for exported functions and ugui::core for
internal use functions (used to create widgets) internal use functions (used to create widgets)
[ ] The render loop RAPES the gpu, valve pls fix [ ] The render loop RAPES the gpu, valve pls fix
[ ] The way the element structures are implemented wastes a lot of memory since
each struct Elem, struct Cmd, etc. is as big as the largest element. It would
be better to use a different allcation strategy.
[ ] Add a way to handle time events like double clicks
## Layout ## Layout
[ ] Text reflow [ ] Text reflow
[x] Flexbox [x] Flexbox
[ ] Center elements to the row/column [ ] Center elements to the row/column
[ ] Text wrapping
[ ] Consider a multi-pass recursive approach to layout (like https://github.com/nicbarker/clay)
instead of the curren multi-frame approach.
## Input ## Input
@ -51,15 +60,16 @@ to maintain focus until mouse release (fix scroll bars)
## 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
[ ] New window command, useful for popups [ ] New window command, useful for popups
## Atlases ## Atlas
[ ] 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
[ ] Pixel format conversion
## Fonts ## Fonts
@ -79,3 +89,21 @@ _ border radius
[ ] Icon Buttons [ ] Icon Buttons
[x] Switch [x] Switch
[x] Checkbox [x] Checkbox
[ ] Selectable text box
## Main / exaple
[ ] Create maps from ids to textures and images instead of hardcoding them
## API
[ ] Introduce a Layout structure that specifies the positioning of elements inside
a Div element. This would allow specifying alignment, maximum and minimum sizing
margins between children, etc.
This is different from style which is applied per-element.
[ ] Remove Ids for element that don't need them. Such elements are button, toggles,
and all elements which do not have internal data that has to be cached and/or
queried by the user for later use. This allows for smaller caches and in general
reduces some load, since most of the stuff is recomputed for every frame.

View File

@ -1,8 +1,8 @@
module schrift; module schrift;
def SftFont = void*; alias SftFont = void*;
def SftUChar = uint; alias SftUChar = uint;
def SftGlyph = uint; alias SftGlyph = uint;
const int SFT_DOWNWARD_Y = 0x01; const int SFT_DOWNWARD_Y = 0x01;

View File

@ -1,4 +1,4 @@
module cache(<Key, Value, SIZE>); module cache{Key, Value, SIZE};
/* LRU Cache /* LRU Cache
* The cache uses a pool (array) to store all the elements, each element has * The cache uses a pool (array) to store all the elements, each element has
@ -14,12 +14,13 @@ module cache(<Key, Value, SIZE>);
// happens at the same time // happens at the same time
import std::core::mem; import std::core::mem;
import std::core::mem::allocator;
import std::collections::bitset; import std::collections::bitset;
import std::collections::map; import std::collections::map;
def BitArr = bitset::BitSet(<SIZE>) @private; alias BitArr = bitset::BitSet{SIZE};
def IdTable = map::HashMap(<Key, usz>) @private; alias IdTable = map::HashMap{Key, usz};
def IdTableEntry = map::Entry(<Key, usz>) @private; alias IdTableEntry = map::Entry{Key, usz};
const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0); const usz CACHE_NCYCLES = (usz)(SIZE * 2.0/3.0);
@ -42,9 +43,9 @@ macro Cache.cycle(&cache) @private {
} }
} }
fn void! Cache.init(&cache) fn void? Cache.init(&cache)
{ {
cache.table.new_init(capacity: SIZE); cache.table.init(allocator::heap(), capacity: SIZE);
// FIXME: this shit is SLOW // FIXME: this shit is SLOW
foreach (idx, bit : cache.used) { cache.used[idx] = false; } foreach (idx, bit : cache.used) { cache.used[idx] = false; }
foreach (idx, bit : cache.present) { cache.present[idx] = false; } foreach (idx, bit : cache.present) { cache.present[idx] = false; }
@ -57,7 +58,7 @@ fn void Cache.free(&cache)
(void)mem::free(cache.pool); (void)mem::free(cache.pool);
} }
fn Value*! Cache.search(&cache, Key id) fn Value*? Cache.search(&cache, Key id)
{ {
// get_entry() faults on miss // get_entry() faults on miss
IdTableEntry* entry = cache.table.get_entry(id)!; IdTableEntry* entry = cache.table.get_entry(id)!;
@ -65,14 +66,14 @@ fn Value*! Cache.search(&cache, Key id)
/* MISS, wrong key */ /* MISS, wrong key */
if (entry.key != id) { if (entry.key != id) {
cache.table.remove(id)!; cache.table.remove(id)!;
return SearchResult.MISSING?; return NOT_FOUND?;
} }
/* MISS, the data is not valid (not present) */ /* MISS, the data is not valid (not present) */
if (!cache.present[entry.value]) { if (!cache.present[entry.value]) {
// if the data is not present but it is still in the table, remove it // if the data is not present but it is still in the table, remove it
cache.table.remove(id)!; cache.table.remove(id)!;
return SearchResult.MISSING?; return NOT_FOUND?;
} }
/* HIT, set as recently used */ /* HIT, set as recently used */
@ -82,7 +83,7 @@ fn Value*! Cache.search(&cache, Key id)
fn void Cache.remove(&cache, Key id) fn void Cache.remove(&cache, Key id)
{ {
IdTableEntry*! entry = cache.table.get_entry(id); IdTableEntry*? entry = cache.table.get_entry(id);
if (catch entry) { if (catch entry) {
return; return;
} }
@ -105,7 +106,7 @@ fn usz Cache.get_free_spot(&cache) @private
return 0; return 0;
} }
fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private fn Value*? Cache.insert_at(&cache, Value *g, Key id, usz index) @private
{ {
// TODO: verify index, g and id // TODO: verify index, g and id
Value* spot; Value* spot;
@ -122,17 +123,17 @@ fn Value*! Cache.insert_at(&cache, Value *g, Key id, usz index) @private
} }
// Insert an element in the cache, returns the index // Insert an element in the cache, returns the index
fn Value*! Cache.insert_new(&cache, Value* g, Key id) fn Value*? Cache.insert_new(&cache, Value* g, Key id)
{ {
usz index = cache.get_free_spot(); usz index = cache.get_free_spot();
return cache.insert_at(g, id, index); return cache.insert_at(g, id, index);
} }
fn Value*! Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null) fn Value*? Cache.get_or_insert(&cache, Value* g, Key id, bool *is_new = null)
{ {
Value*! c = cache.search(id); Value*? c = cache.search(id);
if (catch e = c) { if (catch e = c) {
if (e != SearchResult.MISSING) { if (e != NOT_FOUND) {
return e?; return e?;
} else { } else {
// if the element is new (inserted) set the is_new flag // if the element is new (inserted) set the is_new flag

View File

@ -1,11 +1,8 @@
module fifo(<Type>); module fifo{Type};
import std::core::mem; import std::core::mem;
fault FifoErr { faultdef FULL, EMPTY;
FULL,
EMPTY,
}
// TODO: specify the allocator // TODO: specify the allocator
@ -15,7 +12,7 @@ struct Fifo {
usz count; usz count;
} }
fn void! Fifo.init(&fifo, usz size) fn void? Fifo.init(&fifo, usz size)
{ {
fifo.arr = mem::new_array(Type, size); fifo.arr = mem::new_array(Type, size);
fifo.out = 0; fifo.out = 0;
@ -27,20 +24,20 @@ fn void Fifo.free(&fifo)
(void)mem::free(fifo.arr); (void)mem::free(fifo.arr);
} }
fn void! Fifo.enqueue(&fifo, Type *elem) fn void? Fifo.enqueue(&fifo, Type *elem)
{ {
if (fifo.count >= fifo.arr.len) { if (fifo.count >= fifo.arr.len) {
return FifoErr.FULL?; return FULL?;
} }
usz in = (fifo.out + fifo.count) % fifo.arr.len; usz in = (fifo.out + fifo.count) % fifo.arr.len;
fifo.arr[in] = *elem; fifo.arr[in] = *elem;
fifo.count++; fifo.count++;
} }
fn Type*! Fifo.dequeue(&fifo) fn Type*? Fifo.dequeue(&fifo)
{ {
if (fifo.count == 0) { if (fifo.count == 0) {
return FifoErr.EMPTY?; return EMPTY?;
} }
Type *ret = &fifo.arr[fifo.out]; Type *ret = &fifo.arr[fifo.out];
fifo.count--; fifo.count--;

View File

@ -7,7 +7,7 @@ import std::time;
import std::collections::ringbuffer; import std::collections::ringbuffer;
import std::core::string; import std::core::string;
def Times = ringbuffer::RingBuffer(<time::NanoDuration, 128>); alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]};
fn void Times.print_stats(&times) fn void Times.print_stats(&times)
{ {
@ -40,7 +40,7 @@ fn TimeStats Times.get_stats(&times)
} }
avg = (NanoDuration)((ulong)avg/128.0); avg = (NanoDuration)((ulong)avg/128.0);
return TimeStats{.min = min, .max = max, .avg = avg}; return {.min = min, .max = max, .avg = avg};
} }
@ -115,12 +115,12 @@ void main()
macro rl::Color ugui::Color.conv(color) macro rl::Color ugui::Color.conv(color)
{ {
return rl::Color{.r = color.r, .g = color.g, .b = color.b, .a = color.a}; return {.r = color.r, .g = color.g, .b = color.b, .a = color.a};
} }
macro rl::Rectangle Rect.conv(rect) macro rl::Rectangle Rect.conv(rect)
{ {
return rl::Rectangle{.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h}; return {.x = rect.x, .y = rect.y, .width = rect.w, .height = rect.h};
} }
fn int main(String[] args) fn int main(String[] args)
@ -169,11 +169,13 @@ fn int main(String[] args)
mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL); mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL);
ui.input_mod_keys(mod); ui.input_mod_keys(mod);
/*
for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) { for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) {
ZString kname = rl::getKeyName(key); ZString kname = rl::getKeyName(key);
if (kname == null) continue; if (kname == null) continue;
ui.input_text_unicode(kname.str_view()); ui.input_text_unicode(kname.str_view());
} }
*/
/* Start Input Handling */ /* Start Input Handling */
@ -203,29 +205,29 @@ fn int main(String[] args)
if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break; if (ui.check_key_combo(ugui::KMOD_CTRL, "q")) break;
ui.div_begin("main", Rect{.w=-100})!!; ui.div_begin("main", {.w=-100})!!;
{| {
ui.layout_set_column()!!; ui.layout_set_column()!!;
if (ui.button("button0", Rect{0,0,30,30}, toggle)!!.mouse_press) { if (ui.button("button0", {0,0,30,30}, toggle)!!.mouse_press) {
io::printn("press button0"); io::printn("press button0");
toggle = !toggle; toggle = !toggle;
} }
//ui.layout_next_column()!!; //ui.layout_next_column()!!;
if (ui.button("button1", Rect{0,0,30,30})!!.mouse_press) { if (ui.button("button1", {0,0,30,30})!!.mouse_press) {
io::printn("press button1"); io::printn("press button1");
} }
//ui.layout_next_column()!!; //ui.layout_next_column()!!;
if (ui.button("button2", Rect{0,0,30,30})!!.mouse_release) { if (ui.button("button2", {0,0,30,30})!!.mouse_release) {
io::printn("release button2"); io::printn("release button2");
} }
ui.layout_set_row()!!; ui.layout_set_row()!!;
ui.layout_next_row()!!; ui.layout_next_row()!!;
static float rf, gf, bf, af; static float rf, gf, bf, af;
ui.slider_ver("slider_r", Rect{0,0,30,100}, &rf)!!; ui.slider_ver("slider_r", {0,0,30,100}, &rf)!!;
ui.slider_ver("slider_g", Rect{0,0,30,100}, &gf)!!; ui.slider_ver("slider_g", {0,0,30,100}, &gf)!!;
ui.slider_ver("slider_b", Rect{0,0,30,100}, &bf)!!; ui.slider_ver("slider_b", {0,0,30,100}, &bf)!!;
ui.slider_ver("slider_a", Rect{0,0,30,100}, &af)!!; ui.slider_ver("slider_a", {0,0,30,100}, &af)!!;
ui.layout_next_column()!!; ui.layout_next_column()!!;
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!; ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
@ -235,8 +237,8 @@ fn int main(String[] args)
ui.layout_next_row()!!; ui.layout_next_row()!!;
static bool check; static bool check;
ui.checkbox("check1", "", Point{}, &check, "tick")!!; ui.checkbox("check1", "", {}, &check, "tick")!!;
ui.toggle("toggle1", "", Point{}, &toggle)!!; ui.toggle("toggle1", "", {}, &toggle)!!;
/* /*
ui.layout_set_column()!!; ui.layout_set_column()!!;
@ -248,33 +250,33 @@ fn int main(String[] args)
ui.layout_next_row()!!; ui.layout_next_row()!!;
ui.button_label(" E ")!!; ui.button_label(" E ")!!;
*/ */
|}; };
ui.draw_sprite("sprite1", "tux")!!; ui.draw_sprite("sprite1", "tux")!!;
ui.div_end()!!; ui.div_end()!!;
ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!; ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
{| {
ui.layout_set_column()!!; ui.layout_set_column()!!;
static float slider2 = 0.5; static float slider2 = 0.5;
if (ui.slider_ver("slider", Rect{0,0,30,100}, &slider2)!!.update) { if (ui.slider_ver("slider", {0,0,30,100}, &slider2)!!.update) {
io::printfn("other slider: %f", slider2); io::printfn("other slider: %f", slider2);
} }
ui.button("button0", Rect{0,0,50,50})!!; ui.button("button0", {0,0,50,50})!!;
ui.button("button1", Rect{0,0,50,50})!!; ui.button("button1", {0,0,50,50})!!;
ui.button("button2", Rect{0,0,50,50})!!; ui.button("button2", {0,0,50,50})!!;
ui.button("button3", Rect{0,0,50,50})!!; ui.button("button3", {0,0,50,50})!!;
if (toggle) { if (toggle) {
ui.button("button4", Rect{0,0,50,50})!!; ui.button("button4", {0,0,50,50})!!;
ui.button("button5", Rect{0,0,50,50})!!; ui.button("button5", {0,0,50,50})!!;
ui.button("button6", Rect{0,0,50,50})!!; ui.button("button6", {0,0,50,50})!!;
ui.button("button7", Rect{0,0,50,50})!!; ui.button("button7", {0,0,50,50})!!;
} }
ui.layout_next_column()!!; ui.layout_next_column()!!;
ui.layout_set_row()!!; ui.layout_set_row()!!;
static float f1, f2; static float f1, f2;
ui.slider_hor("hs1", Rect{0,0,100,30}, &f1)!!; ui.slider_hor("hs1", {0,0,100,30}, &f1)!!;
ui.slider_hor("hs2", Rect{0,0,100,30}, &f2)!!; ui.slider_hor("hs2", {0,0,100,30}, &f2)!!;
|}; };
ui.div_end()!!; ui.div_end()!!;
// Timings counter // Timings counter
@ -282,12 +284,12 @@ fn int main(String[] args)
TimeStats uts = ui_times.get_stats(); TimeStats uts = ui_times.get_stats();
ui.layout_set_floating()!!; ui.layout_set_floating()!!;
ui.div_begin("fps", Rect{0, ui.height-100, -300, 100})!!; ui.div_begin("fps", {0, ui.height-100, -300, 100})!!;
{| {
ui.layout_set_column()!!; ui.layout_set_column()!!;
ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!; ui.text_unbounded("draw times", string::tformat("ui avg: %s\ndraw avg: %s\nTOT: %s", uts.avg, dts.avg, uts.avg+dts.avg))!!;
ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!; ui.text_unbounded("ui text input", (String)ui.input.keyboard.text[..])!!;
|}; };
ui.div_end()!!; ui.div_end()!!;
ui.frame_end()!!; ui.frame_end()!!;
@ -341,15 +343,15 @@ fn int main(String[] args)
rl::endShaderMode(); rl::endShaderMode();
} else if (cmd.sprite.texture_id == sprite_id) { } else if (cmd.sprite.texture_id == sprite_id) {
// FIXME: THIS CODE IS SHIT, REAL DOO DOO // FIXME: THIS CODE IS SHIT, REAL DOO DOO
{| {
if (cmd.sprite.type == SpriteType.SPRITE_MSDF) { if (cmd.sprite.type == SpriteType.SPRITE_MSDF) {
rl::beginShaderMode(msdf_shader); rl::beginShaderMode(msdf_shader);
defer rl::endShaderMode(); defer rl::endShaderMode();
rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv());
} else { } else {
rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), rl::Vector2{0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv()); rl::drawTexturePro(sprite_texture, cmd.sprite.texture_rect.conv(), cmd.sprite.rect.conv(), {0.0f, 0.0f}, 0.0f, cmd.sprite.hue.conv());
} }
|}; };
} else { } else {
io::printfn("unknown texture id: %d", cmd.sprite.texture_id); io::printfn("unknown texture id: %d", cmd.sprite.texture_id);
} }

View File

@ -2,10 +2,7 @@ module ugui;
import std::io; import std::io;
fault UgAtlasError { faultdef CANNOT_PLACE, INVALID_TYPE;
CANNOT_PLACE,
INVALID_TYPE,
}
enum AtlasType { enum AtlasType {
ATLAS_GRAYSCALE, ATLAS_GRAYSCALE,
@ -33,7 +30,39 @@ macro usz AtlasType.bpp(type)
} }
} }
fn void! Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height) macro typeid AtlasType.underlying(type)
{
switch (type) {
case ATLAS_GRAYSCALE: return char;
case ATLAS_R8G8B8A8: return uint;
}
}
/*
// FIXME: in and out types are not always known at compile time
macro @pixel_convert(p, AtlasType $in, AtlasType $out)
{
$if $in == $out:
return p;
$else
$switch
$case $in == ATLAS_R8G8B8A8 && $out == ATLAS_GRAYSCALE:
var r = ((p >> 0) & 0xff);
var g = ((p >> 8) & 0xff);
var b = ((p >> 16) & 0xff);
var a = ((p >> 24) & 0xff);
if (a == 0) return (char)0;
return (ATLAS_GRAYSCALE.underlying())(((float)r+g+b) / 3.0f);
$case $in == ATLAS_GRAYSCALE && $out == ATLAS_R8G8B8A8:
var x = (char)(p/3.0);
return (ATLAS_R8G8B8A8.underlying())(x|(x<<8)|(x<<16)|(255<<24));
$default: $error "Unimplemented pixel format conversion";
$endswitch
$endif
}
*/
fn void? Atlas.new(&atlas, Id id, AtlasType type, ushort width, ushort height)
{ {
atlas.id = id; atlas.id = id;
atlas.type = type; atlas.type = type;
@ -66,7 +95,7 @@ fn void Atlas.free(&atlas)
// place a rect inside the atlas // place a rect inside the atlas
// uses a row first algorithm // uses a row first algorithm
// TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/ // TODO: use a skyline algorithm https://jvernay.fr/en/blog/skyline-2d-packer/implementation/
fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride) fn Point? Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride)
{ {
Point p; Point p;
@ -79,7 +108,7 @@ fn Point! Atlas.place(&atlas, char[] pixels, ushort w, ushort h, ushort stride)
if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) { if (atlas.row.x + w <= atlas.width && atlas.row.y + h <= atlas.height) {
p = atlas.row; p = atlas.row;
} else { } else {
return UgAtlasError.CANNOT_PLACE?; return CANNOT_PLACE?;
} }
} }

View File

@ -10,7 +10,7 @@ struct ElemButton {
// draw a button, return the events on that button // draw a button, return the events on that button
// FIXME: "state" should be renamed "active" to toggle between an usable button and // FIXME: "state" should be renamed "active" to toggle between an usable button and
// an inactive (greyed-out) button // an inactive (greyed-out) button
fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false) fn ElemEvents? Ctx.button(&ctx, String label, Rect size, bool state = false)
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -22,14 +22,14 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false)
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_BUTTON; elem.type = ETYPE_BUTTON;
} else if (elem.type != ETYPE_BUTTON) { } else if (elem.type != ETYPE_BUTTON) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
elem.bounds = ctx.position_element(parent, size, true); elem.bounds = ctx.position_element(parent, size, true);
// if the bounds are null the element is outside the div view, // if the bounds are null the element is outside the div view,
// no interaction should occur so just return // no interaction should occur so just return
if (elem.bounds.is_null()) { return ElemEvents{}; } if (elem.bounds.is_null()) { return {}; }
Color col = 0x0000ffffu.to_rgba(); Color col = 0x0000ffffu.to_rgba();
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);
@ -45,7 +45,7 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false)
return elem.events; return elem.events;
} }
fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.max,short.max}, bool state = false) fn ElemEvents? Ctx.button_label(&ctx, String label, Rect size = {0,0,short.max,short.max}, bool state = false)
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -60,11 +60,11 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
Rect text_size = ctx.get_text_bounds(label)!; Rect text_size = ctx.get_text_bounds(label)!;
Rect btn_size = text_size.add(Rect{0,0,10,10}); Rect btn_size = text_size.add({0,0,10,10});
// 2. Layout // 2. Layout
elem.bounds = ctx.position_element(parent, btn_size, true); elem.bounds = ctx.position_element(parent, btn_size, true);
if (elem.bounds.is_null()) { return ElemEvents{}; } if (elem.bounds.is_null()) { return {}; }
Color col = 0x0000ffffu.to_rgba(); Color col = 0x0000ffffu.to_rgba();
elem.events = ctx.get_elem_events(elem); elem.events = ctx.get_elem_events(elem);
@ -86,9 +86,50 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m
return elem.events; return elem.events;
} }
fn ElemEvents? Ctx.button_icon(&ctx, String label, String icon, String on_icon = "", bool state = false)
{
Id id = ctx.gen_id(label)!;
Elem *parent = ctx.get_parent()!;
Elem *elem = ctx.get_elem(id)!;
// add it to the tree
ctx.tree.add(id, ctx.active_div)!;
if (elem.flags.is_new) {
elem.type = ETYPE_BUTTON;
} else if (elem.type != ETYPE_BUTTON) {
return WRONG_ELEMENT_TYPE?;
}
Sprite* def_sprite = ctx.sprite_atlas.get(icon)!;
Sprite* on_sprite = ctx.sprite_atlas.get(on_icon) ?? &&(Sprite){};
Rect max_size = def_sprite.rect().max(on_sprite.rect());
elem.bounds = ctx.position_element(parent, max_size, true);
// if the bounds are null the element is outside the div view,
// no interaction should occur so just return
if (elem.bounds.is_null()) { return {}; }
Color col = 0x0000ffffu.to_rgba();
elem.events = ctx.get_elem_events(elem);
Id tex_id = ctx.sprite_atlas.id;
if (state && on_icon != "") {
ctx.push_sprite(elem.bounds, on_sprite.uv(), tex_id, type: on_sprite.type)!;
} else {
ctx.push_sprite(elem.bounds, def_sprite.uv(), tex_id, type: def_sprite.type)!;
}
// Draw the button
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
return elem.events;
}
// FIXME: this should be inside the style // FIXME: this should be inside the style
const ushort DEFAULT_CHECKBOX_SIZE = 16; const ushort DEFAULT_CHECKBOX_SIZE = 16;
fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) fn void? Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {})
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -102,7 +143,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_BUTTON; elem.type = ETYPE_BUTTON;
} else if (elem.type != ETYPE_BUTTON) { } else if (elem.type != ETYPE_BUTTON) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
Rect size = {off.x, off.y, DEFAULT_CHECKBOX_SIZE, DEFAULT_CHECKBOX_SIZE}; Rect size = {off.x, off.y, DEFAULT_CHECKBOX_SIZE, DEFAULT_CHECKBOX_SIZE};
@ -116,7 +157,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state); if (elem.events.mouse_hover && elem.events.mouse_release) *state = !(*state);
Color col; Color col;
if (tick_sprite != String{}) { if (tick_sprite != {}) {
col = ctx.style.bgcolor; col = ctx.style.bgcolor;
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
if (*state) { if (*state) {
@ -135,7 +176,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
// FIXME: this should be inside the style // FIXME: this should be inside the style
const short DEFAULT_SWITCH_SIZE = 16; const short DEFAULT_SWITCH_SIZE = 16;
fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* state) fn void? Ctx.toggle(&ctx, String label, String description, Point off, bool* state)
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -149,7 +190,7 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_BUTTON; elem.type = ETYPE_BUTTON;
} else if (elem.type != ETYPE_BUTTON) { } else if (elem.type != ETYPE_BUTTON) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
Rect size = {off.x, off.y, DEFAULT_SWITCH_SIZE*2, DEFAULT_SWITCH_SIZE}; Rect size = {off.x, off.y, DEFAULT_SWITCH_SIZE*2, DEFAULT_SWITCH_SIZE};
@ -171,6 +212,6 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta
// Draw the button // Draw the button
// FIXME: THIS IS SHIT // FIXME: THIS IS SHIT
ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!; ctx.push_rect(elem.bounds, ctx.style.bgcolor, do_border: true, do_radius: true)!;
Rect t = elem.bounds.add(Rect{*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6}); Rect t = elem.bounds.add({*state ? (DEFAULT_SWITCH_SIZE+3) : +3, +3, -DEFAULT_SWITCH_SIZE-6, -6});
ctx.push_rect(t, col, do_border: false, do_radius: true)!; ctx.push_rect(t, col, do_border: false, do_radius: true)!;
} }

View File

@ -68,7 +68,7 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd)
// FIXME: is this really the best solution? // FIXME: is this really the best solution?
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content) // "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false) fn void? Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
{ {
Rect border = ctx.style.border; Rect border = ctx.style.border;
Rect padding = ctx.style.padding; Rect padding = ctx.style.padding;
@ -101,7 +101,7 @@ fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, boo
} }
// TODO: add texture id // TODO: add texture id
fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL) fn void? Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color hue = 0xffffffffu.to_rgba(), SpriteType type = SPRITE_NORMAL)
{ {
Cmd cmd = { Cmd cmd = {
.type = CMD_SPRITE, .type = CMD_SPRITE,
@ -114,7 +114,7 @@ fn void! Ctx.push_sprite(&ctx, Rect bounds, Rect texture, Id texture_id, Color h
ctx.push_cmd(&cmd)!; ctx.push_cmd(&cmd)!;
} }
fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba()) fn void? Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu.to_rgba())
{ {
if (text.len == 0) { if (text.len == 0) {
return; return;
@ -163,10 +163,10 @@ fn void! Ctx.push_string(&ctx, Rect bounds, String text, Color hue = 0xffffffffu
} }
// FIXME: we never get here if an error was thrown before // FIXME: we never get here if an error was thrown before
ctx.push_scissor(Rect{})!; ctx.push_scissor({})!;
} }
fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas) fn void? Ctx.push_update_atlas(&ctx, Atlas* atlas)
{ {
Cmd up = { Cmd up = {
.type = CMD_UPDATE_ATLAS, .type = CMD_UPDATE_ATLAS,
@ -181,7 +181,7 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas)
ctx.push_cmd(&up)!; ctx.push_cmd(&up)!;
} }
fn void! Ctx.push_scissor(&ctx, Rect rect) fn void? Ctx.push_scissor(&ctx, Rect rect)
{ {
Cmd sc = { Cmd sc = {
.type = CMD_SCISSOR, .type = CMD_SCISSOR,

View File

@ -9,7 +9,7 @@ import std::core::string;
// element ids are just long ints // element ids are just long ints
def Id = usz; alias Id = usz;
enum ElemType { enum ElemType {
ETYPE_NONE, ETYPE_NONE,
@ -54,20 +54,15 @@ struct Elem {
// relationships between elements are stored in a tree, it stores just the ids // relationships between elements are stored in a tree, it stores just the ids
def IdTree = vtree::VTree(<Id>) @private; alias IdTree = vtree::VTree{Id};
// elements themselves are kept in a cache // elements themselves are kept in a cache
const uint MAX_ELEMENTS = 256; const uint MAX_ELEMENTS = 256;
def ElemCache = cache::Cache(<Id, Elem, MAX_ELEMENTS>) @private; alias ElemCache = cache::Cache{Id, Elem, MAX_ELEMENTS};
def CmdQueue = fifo::Fifo(<Cmd>); alias CmdQueue = fifo::Fifo{Cmd};
fault UgError { faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE;
INVALID_SIZE,
EVENT_UNSUPPORTED,
UNEXPECTED_ELEMENT,
WRONG_ELEMENT_TYPE,
}
const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 }; const Rect DIV_FILL = { .x = 0, .y = 0, .w = 0, .h = 0 };
@ -128,7 +123,7 @@ struct Ctx {
} }
// return a pointer to the parent of the current active div // return a pointer to the parent of the current active div
fn Elem*! Ctx.get_parent(&ctx) fn Elem*? Ctx.get_parent(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
return ctx.cache.search(parent_id); return ctx.cache.search(parent_id);
@ -140,7 +135,7 @@ const uint GOLDEN_RATIO = 0x9E3779B9;
// generate an id combining the hashes of the parent id and the label // generate an id combining the hashes of the parent id and the label
// with the Cantor pairing function // with the Cantor pairing function
macro Id! Ctx.gen_id(&ctx, String label) macro Id? Ctx.gen_id(&ctx, String label)
{ {
Id id1 = ctx.tree.get(ctx.active_div)!; Id id1 = ctx.tree.get(ctx.active_div)!;
Id id2 = label.hash(); Id id2 = label.hash();
@ -153,7 +148,7 @@ macro Id! Ctx.gen_id(&ctx, String label)
// get or push an element from the cache, return a pointer to it // get or push an element from the cache, return a pointer to it
// resets all flags except is_new which is set accordingly // resets all flags except is_new which is set accordingly
fn Elem*! Ctx.get_elem(&ctx, Id id) fn Elem*? Ctx.get_elem(&ctx, Id id)
{ {
Elem empty_elem; Elem empty_elem;
bool is_new; bool is_new;
@ -170,10 +165,10 @@ fn Elem*! Ctx.get_elem(&ctx, Id id)
// THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE // THIS HAS TO BE A MACRO SINCE IT RETURNS A POINTER TO A TEMPORARY VALUE
macro Elem* Ctx.find_elem(&ctx, Id id) macro Elem* Ctx.find_elem(&ctx, Id id)
{ {
Elem*! elem; Elem*? elem;
elem = ctx.cache.search(id); elem = ctx.cache.search(id);
if (catch elem) { if (catch elem) {
return &&Elem{}; return &&(Elem){};
} }
return elem; return elem;
} }
@ -194,7 +189,7 @@ macro Ctx.get_elem_by_tree_idx(&ctx, isz idx) @private
return ctx.cache.search(id); return ctx.cache.search(id);
} }
fn void! Ctx.init(&ctx) fn void? Ctx.init(&ctx)
{ {
ctx.tree.init(MAX_ELEMENTS)!; ctx.tree.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.tree.free(); } defer catch { (void)ctx.tree.free(); }
@ -208,9 +203,9 @@ fn void! Ctx.init(&ctx)
ctx.active_div = 0; ctx.active_div = 0;
// TODO: add style config // TODO: add style config
ctx.style.margin = Rect{2, 2, 2, 2}; ctx.style.margin = {2, 2, 2, 2};
ctx.style.border = Rect{2, 2, 2, 2}; ctx.style.border = {2, 2, 2, 2};
ctx.style.padding = Rect{1, 1, 1, 1}; ctx.style.padding = {1, 1, 1, 1};
ctx.style.radius = 5; ctx.style.radius = 5;
ctx.style.bgcolor = 0x282828ffu.to_rgba(); ctx.style.bgcolor = 0x282828ffu.to_rgba();
ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba(); ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba();
@ -226,7 +221,7 @@ fn void Ctx.free(&ctx)
(void)ctx.sprite_atlas.free(); (void)ctx.sprite_atlas.free();
} }
fn void! Ctx.frame_begin(&ctx) fn void? Ctx.frame_begin(&ctx)
{ {
// 2. Get the root element from the cache and update it // 2. Get the root element from the cache and update it
@ -268,7 +263,7 @@ fn void! Ctx.frame_begin(&ctx)
// TODO: add a background color taken from a theme or config // TODO: add a background color taken from a theme or config
} }
fn void! Ctx.frame_end(&ctx) fn void? Ctx.frame_end(&ctx)
{ {
Elem* root = ctx.get_elem_by_tree_idx(0)!; Elem* root = ctx.get_elem_by_tree_idx(0)!;
root.div.layout = LAYOUT_ROW; root.div.layout = LAYOUT_ROW;

View File

@ -28,7 +28,7 @@ struct ElemDiv {
// if the width or height are negative the width or height will be calculated based on the children size // if the width or height are negative the width or height will be calculated based on the children size
// sort similar to a flexbox, and the minimum size is set by the negative of the width or height // sort similar to a flexbox, and the minimum size is set by the negative of the width or height
// FIXME: there is a bug if the size.w or size.h == -0 // FIXME: there is a bug if the size.w or size.h == -0
fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false) fn void? Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, bool scroll_y = false)
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -42,7 +42,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_DIV; elem.type = ETYPE_DIV;
} else if (elem.type != ETYPE_DIV) { } else if (elem.type != ETYPE_DIV) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
elem.div.scroll_x.enabled = scroll_x; elem.div.scroll_x.enabled = scroll_x;
elem.div.scroll_y.enabled = scroll_y; elem.div.scroll_y.enabled = scroll_y;
@ -55,16 +55,16 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
.h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h, .h = size.h < 0 ? max(elem.div.pcb.h, (short)-size.h) : size.h,
}; };
elem.bounds = ctx.position_element(parent, wanted_size); elem.bounds = ctx.position_element(parent, wanted_size);
elem.div.children_bounds = Rect{}; elem.div.children_bounds = {};
// update the ctx scissor // update the ctx scissor
ctx.div_scissor = elem.bounds; ctx.div_scissor = elem.bounds;
ctx.push_scissor(elem.bounds)!; ctx.push_scissor(elem.bounds)!;
// 4. Fill the div fields // 4. Fill the div fields
elem.div.origin_c = Point{ elem.div.origin_c = {
.x = elem.bounds.x, .x = elem.bounds.x,
.y = elem.bounds.y, .y = elem.bounds.y
}; };
elem.div.origin_r = elem.div.origin_c; elem.div.origin_r = elem.div.origin_c;
elem.div.layout = parent.div.layout; elem.div.layout = parent.div.layout;
@ -79,7 +79,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
// TODO: check resizeable // TODO: check resizeable
} }
fn void! Ctx.div_end(&ctx) fn void? Ctx.div_end(&ctx)
{ {
// swap the children bounds // swap the children bounds
Elem* parent = ctx.get_parent()!; Elem* parent = ctx.get_parent()!;

View File

@ -4,13 +4,15 @@ import schrift;
import grapheme; import grapheme;
import std::collections::map; import std::collections::map;
import std::core::mem; import std::core::mem;
import std::core::mem::allocator;
import std::io; import std::io;
import std::ascii; import std::ascii;
// unicode code point, different type for a different hash // unicode code point, different type for a different hash
def Codepoint = uint; alias Codepoint = uint;
//macro uint Codepoint.hash(self) => ((uint)self).hash();
/* width and height of a glyph contain the kering advance /* width and height of a glyph contain the kering advance
* (u,v) * (u,v)
@ -40,14 +42,9 @@ struct Glyph {
} }
const uint FONT_CACHED = 255; const uint FONT_CACHED = 255;
def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private; alias GlyphTable = map::HashMap{Codepoint, Glyph};
fault UgFontError { faultdef TTF_LOAD_FAILED, MISSING_GLYPH, BAD_GLYPH_METRICS, RENDER_ERROR;
TTF_LOAD_FAILED,
MISSING_GLYPH,
BAD_GLYPH_METRICS,
RENDER_ERROR,
}
struct Font { struct Font {
schrift::Sft sft; schrift::Sft sft;
@ -61,14 +58,14 @@ struct Font {
bool should_update; // should send update_atlas command, resets at frame_end() bool should_update; // should send update_atlas command, resets at frame_end()
} }
fn void! Font.load(&font, String name, ZString path, uint height, float scale) fn void? Font.load(&font, String name, ZString path, uint height, float scale)
{ {
font.table.new_init(capacity: FONT_CACHED); font.table.init(allocator::heap(), capacity: FONT_CACHED);
font.id = name.hash(); font.id = name.hash();
font.size = height*scale; font.size = height*scale;
font.sft = schrift::Sft{ font.sft = {
.xScale = (double)font.size, .xScale = (double)font.size,
.yScale = (double)font.size, .yScale = (double)font.size,
.flags = schrift::SFT_DOWNWARD_Y, .flags = schrift::SFT_DOWNWARD_Y,
@ -77,7 +74,7 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale)
font.sft.font = schrift::loadfile(path); font.sft.font = schrift::loadfile(path);
if (font.sft.font == null) { if (font.sft.font == null) {
font.table.free(); font.table.free();
return UgFontError.TTF_LOAD_FAILED?; return TTF_LOAD_FAILED?;
} }
schrift::SftLMetrics lmetrics; schrift::SftLMetrics lmetrics;
@ -98,13 +95,13 @@ fn void! Font.load(&font, String name, ZString path, uint height, float scale)
} }
} }
fn Glyph*! Font.get_glyph(&font, Codepoint code) fn Glyph*? Font.get_glyph(&font, Codepoint code)
{ {
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 != NOT_FOUND) {
return excuse?; return excuse?;
} }
} else { } else {
@ -117,12 +114,12 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
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, (SftUChar)code, &gid) < 0) {
return UgFontError.MISSING_GLYPH?; return MISSING_GLYPH?;
} }
if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) { if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) {
return UgFontError.BAD_GLYPH_METRICS?; return BAD_GLYPH_METRICS?;
} }
schrift::SftImage img = { schrift::SftImage img = {
@ -132,7 +129,7 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
char[] pixels = mem::new_array(char, (usz)img.width * img.height); char[] pixels = mem::new_array(char, (usz)img.width * img.height);
img.pixels = pixels; img.pixels = pixels;
if (schrift::render(&font.sft, gid, img) < 0) { if (schrift::render(&font.sft, gid, img) < 0) {
return UgFontError.RENDER_ERROR?; return RENDER_ERROR?;
} }
glyph.code = code; glyph.code = code;
@ -163,7 +160,7 @@ fn void Font.free(&font)
schrift::freefont(font.sft.font); schrift::freefont(font.sft.font);
} }
fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0) fn void? Ctx.load_font(&ctx, String name, ZString path, uint height, float scale = 1.0)
{ {
return ctx.font.load(name, path, height, scale); return ctx.font.load(name, path, height, scale);
} }
@ -174,7 +171,7 @@ fn void! Ctx.load_font(&ctx, String name, ZString path, uint height, float scale
fn Codepoint str_to_codepoint(char[] str, usz* off) fn Codepoint str_to_codepoint(char[] str, usz* off)
{ {
Codepoint cp; Codepoint cp;
isz b = grapheme::decode_utf8(str, str.len, &cp); isz b = grapheme::decode_utf8(str, str.len, (uint*)&cp);
if (b == 0 || b > str.len) { if (b == 0 || b > str.len) {
return 0; return 0;
} }
@ -182,7 +179,7 @@ fn Codepoint str_to_codepoint(char[] str, usz* off)
return cp; return cp;
} }
fn Rect! Ctx.get_text_bounds(&ctx, String text) fn Rect? Ctx.get_text_bounds(&ctx, String text)
{ {
Rect text_bounds; Rect text_bounds;
short line_height = (short)ctx.font.ascender - (short)ctx.font.descender; short line_height = (short)ctx.font.ascender - (short)ctx.font.descender;
@ -219,7 +216,7 @@ fn Point Ctx.center_text(&ctx, Rect text_bounds, Rect bounds)
short dw = bounds.w - text_bounds.w; short dw = bounds.w - text_bounds.w;
short dh = bounds.h - text_bounds.h; short dh = bounds.h - text_bounds.h;
return Point{.x = dw/2, .y = dh/2}; return {.x = dw/2, .y = dh/2};
} }
// TODO: check if the font is present in the context // TODO: check if the font is present in the context

View File

@ -45,10 +45,10 @@ const ModKeys KMOD_CTRL = {.lctrl = true, .rctrl = true};
const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true}; const ModKeys KMOD_SHIFT = {.lshift = true, .rshift = true};
const ModKeys KMOD_ALT = {.lalt = true, .ralt = true}; const ModKeys KMOD_ALT = {.lalt = true, .ralt = true};
const ModKeys KMOD_GUI = {.lgui = true, .rgui = true}; const ModKeys KMOD_GUI = {.lgui = true, .rgui = true};
const ModKeys KMOD_NONE = ModKeys{}; const ModKeys KMOD_NONE = {};
const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max); const ModKeys KMOD_ANY = (ModKeys)(ModKeys.inner.max);
const MouseButtons BTN_NONE = MouseButtons{}; const MouseButtons BTN_NONE = {};
const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max); const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max);
const MouseButtons BTN_LEFT = {.btn_left = true}; const MouseButtons BTN_LEFT = {.btn_left = true};
const MouseButtons BTN_MIDDLE = {.btn_middle = true}; const MouseButtons BTN_MIDDLE = {.btn_middle = true};
@ -72,10 +72,10 @@ fn bool Ctx.check_key_combo(&ctx, ModKeys mod, String keys)
} }
// Window size was changed // Window size was changed
fn void! Ctx.input_window_size(&ctx, short width, short height) fn void? Ctx.input_window_size(&ctx, short width, short height)
{ {
if (width <= 0 || height <= 0) { if (width <= 0 || height <= 0) {
return UgError.INVALID_SIZE?; return INVALID_SIZE?;
} }
ctx.input.events.resize = ctx.width != width || ctx.height != height; ctx.input.events.resize = ctx.width != width || ctx.height != height;
ctx.width = width; ctx.width = width;

View File

@ -7,71 +7,71 @@ enum Layout {
LAYOUT_ABSOLUTE, LAYOUT_ABSOLUTE,
} }
fn void! Ctx.layout_set_row(&ctx) fn void? Ctx.layout_set_row(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!; Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) { if (parent.type != ETYPE_DIV) {
// what? // what?
return UgError.UNEXPECTED_ELEMENT?; return UNEXPECTED_ELEMENT?;
} }
parent.div.layout = LAYOUT_ROW; parent.div.layout = LAYOUT_ROW;
} }
fn void! Ctx.layout_set_column(&ctx) fn void? Ctx.layout_set_column(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!; Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) { if (parent.type != ETYPE_DIV) {
// what? // what?
return UgError.UNEXPECTED_ELEMENT?; return UNEXPECTED_ELEMENT?;
} }
parent.div.layout = LAYOUT_COLUMN; parent.div.layout = LAYOUT_COLUMN;
} }
fn void! Ctx.layout_set_floating(&ctx) fn void? Ctx.layout_set_floating(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!; Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) { if (parent.type != ETYPE_DIV) {
// what? // what?
return UgError.UNEXPECTED_ELEMENT?; return UNEXPECTED_ELEMENT?;
} }
parent.div.layout = LAYOUT_FLOATING; parent.div.layout = LAYOUT_FLOATING;
} }
fn void! Ctx.layout_next_row(&ctx) fn void? Ctx.layout_next_row(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!; Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) { if (parent.type != ETYPE_DIV) {
return UgError.UNEXPECTED_ELEMENT?; return UNEXPECTED_ELEMENT?;
} }
parent.div.origin_r = Point{ parent.div.origin_r = {
.x = parent.bounds.x, .x = parent.bounds.x,
.y = parent.div.children_bounds.bottom_right().y, .y = parent.div.children_bounds.bottom_right().y,
}; };
parent.div.origin_c = parent.div.origin_r; parent.div.origin_c = parent.div.origin_r;
} }
fn void! Ctx.layout_next_column(&ctx) fn void? Ctx.layout_next_column(&ctx)
{ {
Id parent_id = ctx.tree.get(ctx.active_div)!; Id parent_id = ctx.tree.get(ctx.active_div)!;
Elem *parent = ctx.cache.search(parent_id)!; Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) { if (parent.type != ETYPE_DIV) {
return UgError.UNEXPECTED_ELEMENT?; return UNEXPECTED_ELEMENT?;
} }
parent.div.origin_c = Point{ parent.div.origin_c = {
.x = parent.div.children_bounds.bottom_right().x, .x = parent.div.children_bounds.bottom_right().x,
.y = parent.bounds.y, .y = parent.bounds.y,
}; };
@ -124,7 +124,7 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect case LAYOUT_ABSOLUTE: // absolute position, this is a no-op, return the rect
return rect; return rect;
default: // error default: // error
return Rect{}; return {};
} }
// 2. Compute the parent's view // 2. Compute the parent's view
@ -166,11 +166,11 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
child_occupied = child_occupied.grow(rect.size()); child_occupied = child_occupied.grow(rect.size());
// 4. Update the parent's origin // 4. Update the parent's origin
div.origin_r = Point{ div.origin_r = {
.x = child_occupied.bottom_right().x, .x = child_occupied.bottom_right().x,
.y = origin.y, .y = origin.y,
}; };
div.origin_c = Point{ div.origin_c = {
.x = origin.x, .x = origin.x,
.y = child_occupied.bottom_right().y, .y = child_occupied.bottom_right().y,
}; };
@ -191,6 +191,6 @@ fn Rect Ctx.position_element(&ctx, Elem *parent, Rect rect, bool style = false)
if (child_placement.collides(parent_view)) { if (child_placement.collides(parent_view)) {
return child_placement.off(parent.get_view_off().neg()); return child_placement.off(parent.get_view_off().neg());
} else { } else {
return Rect{}; return {};
} }
} }

View File

@ -19,7 +19,7 @@ macro bool Rect.contains(Rect a, Rect b)
// returns the intersection of a and b // returns the intersection of a and b
macro Rect Rect.intersection(Rect a, Rect b) macro Rect Rect.intersection(Rect a, Rect b)
{ {
return Rect{ return {
.x = (short)max(a.x, b.x), .x = (short)max(a.x, b.x),
.y = (short)max(a.y, b.y), .y = (short)max(a.y, b.y),
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x), .w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
@ -39,7 +39,7 @@ macro bool Rect.is_null(Rect r) => r.x == 0 && r.y == 0 && r.x == 0 && r.w == 0;
// returns the element-wise addition of r1 and r2 // returns the element-wise addition of r1 and r2
macro Rect Rect.add(Rect r1, Rect r2) macro Rect Rect.add(Rect r1, Rect r2)
{ {
return Rect{ return {
.x = r1.x + r2.x, .x = r1.x + r2.x,
.y = r1.y + r2.y, .y = r1.y + r2.y,
.w = r1.w + r2.w, .w = r1.w + r2.w,
@ -50,7 +50,7 @@ macro Rect Rect.add(Rect r1, Rect r2)
// returns the element-wise subtraction of r1 and r2 // returns the element-wise subtraction of r1 and r2
macro Rect Rect.sub(Rect r1, Rect r2) macro Rect Rect.sub(Rect r1, Rect r2)
{ {
return Rect{ return {
.x = r1.x - r2.x, .x = r1.x - r2.x,
.y = r1.y - r2.y, .y = r1.y - r2.y,
.w = r1.w - r2.w, .w = r1.w - r2.w,
@ -61,7 +61,7 @@ macro Rect Rect.sub(Rect r1, Rect r2)
// returns the element-wise multiplication of r1 and r2 // returns the element-wise multiplication of r1 and r2
macro Rect Rect.mul(Rect r1, Rect r2) macro Rect Rect.mul(Rect r1, Rect r2)
{ {
return Rect{ return {
.x = r1.x * r2.x, .x = r1.x * r2.x,
.y = r1.y * r2.y, .y = r1.y * r2.y,
.w = r1.w * r2.w, .w = r1.w * r2.w,
@ -71,7 +71,7 @@ macro Rect Rect.mul(Rect r1, Rect r2)
macro Point Rect.position(Rect r) macro Point Rect.position(Rect r)
{ {
return Point{ return {
.x = r.x, .x = r.x,
.y = r.y, .y = r.y,
}; };
@ -79,7 +79,7 @@ macro Point Rect.position(Rect r)
macro Point Rect.size(Rect r) macro Point Rect.size(Rect r)
{ {
return Point{ return {
.x = r.w, .x = r.w,
.y = r.h, .y = r.h,
}; };
@ -87,7 +87,7 @@ macro Point Rect.size(Rect r)
macro Rect Rect.max(Rect a, Rect b) macro Rect Rect.max(Rect a, Rect b)
{ {
return Rect{ return {
.x = max(a.x, b.x), .x = max(a.x, b.x),
.y = max(a.y, b.y), .y = max(a.y, b.y),
.w = max(a.w, b.w), .w = max(a.w, b.w),
@ -97,7 +97,7 @@ macro Rect Rect.max(Rect a, Rect b)
macro Rect Rect.min(Rect a, Rect b) macro Rect Rect.min(Rect a, Rect b)
{ {
return Rect{ return {
.x = min(a.x, b.x), .x = min(a.x, b.x),
.y = min(a.y, b.y), .y = min(a.y, b.y),
.w = min(a.w, b.w), .w = min(a.w, b.w),
@ -108,7 +108,7 @@ macro Rect Rect.min(Rect a, Rect b)
// Offset a rect by a point // Offset a rect by a point
macro Rect Rect.off(Rect r, Point p) macro Rect Rect.off(Rect r, Point p)
{ {
return Rect{ return {
.x = r.x + p.x, .x = r.x + p.x,
.y = r.y + p.y, .y = r.y + p.y,
.w = r.w, .w = r.w,
@ -119,7 +119,7 @@ macro Rect Rect.off(Rect r, Point p)
// Resize a rect width and height // Resize a rect width and height
macro Rect Rect.grow(Rect r, Point p) macro Rect Rect.grow(Rect r, Point p)
{ {
return Rect{ return {
.x = r.x, .x = r.x,
.y = r.y, .y = r.y,
.w = r.w + p.x, .w = r.w + p.x,
@ -130,7 +130,7 @@ macro Rect Rect.grow(Rect r, Point p)
// Return the bottom-right corner of a rectangle // Return the bottom-right corner of a rectangle
macro Point Rect.bottom_right(Rect r) macro Point Rect.bottom_right(Rect r)
{ {
return Point{ return {
.x = r.x + r.w, .x = r.x + r.w,
.y = r.y + r.h, .y = r.y + r.h,
}; };
@ -153,7 +153,7 @@ macro bool Point.in_rect(Point p, Rect r)
macro Point Point.add(Point a, Point b) macro Point Point.add(Point a, Point b)
{ {
return Point{ return {
.x = a.x + b.x, .x = a.x + b.x,
.y = a.y + b.y, .y = a.y + b.y,
}; };
@ -161,17 +161,17 @@ macro Point Point.add(Point a, Point b)
macro Point Point.sub(Point a, Point b) macro Point Point.sub(Point a, Point b)
{ {
return Point{ return {
.x = a.x - b.x, .x = a.x - b.x,
.y = a.y - b.y, .y = a.y - b.y,
}; };
} }
macro Point Point.neg(Point p) => Point{-p.x, -p.y}; macro Point Point.neg(Point p) => {-p.x, -p.y};
macro Point Point.max(Point a, Point b) macro Point Point.max(Point a, Point b)
{ {
return Point{ return {
.x = max(a.x, b.x), .x = max(a.x, b.x),
.y = max(a.y, b.y), .y = max(a.y, b.y),
}; };
@ -179,7 +179,7 @@ macro Point Point.max(Point a, Point b)
macro Point Point.min(Point a, Point b) macro Point Point.min(Point a, Point b)
{ {
return Point{ return {
.x = min(a.x, b.x), .x = min(a.x, b.x),
.y = min(a.y, b.y), .y = min(a.y, b.y),
}; };
@ -195,7 +195,7 @@ struct Color{
macro Color uint.to_rgba(uint u) macro Color uint.to_rgba(uint u)
{ {
return Color{ return {
.r = (char)((u >> 24) & 0xff), .r = (char)((u >> 24) & 0xff),
.g = (char)((u >> 16) & 0xff), .g = (char)((u >> 16) & 0xff),
.b = (char)((u >> 8) & 0xff), .b = (char)((u >> 8) & 0xff),

View File

@ -16,7 +16,7 @@ struct ElemSlider {
<* <*
@require value != null @require value != null
*> *>
fn ElemEvents! Ctx.slider_hor(&ctx, fn ElemEvents? Ctx.slider_hor(&ctx,
String label, String label,
Rect size, Rect size,
float* value, float* value,
@ -35,7 +35,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx,
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_SLIDER; elem.type = ETYPE_SLIDER;
} else if (elem.type != ETYPE_SLIDER) { } else if (elem.type != ETYPE_SLIDER) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
// 2. Layout // 2. Layout
@ -79,7 +79,7 @@ fn ElemEvents! Ctx.slider_hor(&ctx,
* | | * | |
* +-+ * +-+
*/ */
fn ElemEvents! Ctx.slider_ver(&ctx, fn ElemEvents? Ctx.slider_ver(&ctx,
String label, String label,
Rect size, Rect size,
float* value, float* value,
@ -98,7 +98,7 @@ fn ElemEvents! Ctx.slider_ver(&ctx,
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_SLIDER; elem.type = ETYPE_SLIDER;
} else if (elem.type != ETYPE_SLIDER) { } else if (elem.type != ETYPE_SLIDER) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
// 2. Layout // 2. Layout

View File

@ -1,5 +1,6 @@
module ugui; module ugui;
import std::core::mem::allocator;
import std::collections::map; import std::collections::map;
import std::io; import std::io;
import mqoi; import mqoi;
@ -20,7 +21,7 @@ struct Sprite {
ushort w, h; ushort w, h;
} }
def SpriteMap = map::HashMap(<Id, Sprite>); alias SpriteMap = map::HashMap{Id, Sprite};
struct SpriteAtlas { struct SpriteAtlas {
Id id; Id id;
@ -34,20 +35,20 @@ struct ElemSprite {
} }
// name: some examples are "icons" or "images" // name: some examples are "icons" or "images"
fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) fn void? SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height)
{ {
// FIXME: for now only R8G8B8A8 format is supported // FIXME: for now only R8G8B8A8 format is supported
if (type != ATLAS_R8G8B8A8) { if (type != ATLAS_R8G8B8A8) {
return UgAtlasError.INVALID_TYPE?; return INVALID_TYPE?;
} }
this.id = name.hash(); this.id = name.hash();
this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!; this.atlas.new(this.id, AtlasType.ATLAS_R8G8B8A8, width, height)!;
this.sprites.new_init(capacity: SRITES_PER_ATLAS); this.sprites.init(allocator::heap(), capacity: SRITES_PER_ATLAS);
this.should_update = false; this.should_update = false;
} }
fn void! SpriteAtlas.free(&this) fn void? SpriteAtlas.free(&this)
{ {
this.atlas.free(); this.atlas.free();
this.sprites.free(); this.sprites.free();
@ -55,7 +56,7 @@ fn void! SpriteAtlas.free(&this)
// FIXME: this should throw an error when a different pixel format than the atlas' is used // FIXME: this should throw an error when a different pixel format than the atlas' is used
// or convert from the source's pixel format to the atlas' // or convert from the source's pixel format to the atlas'
fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) fn Sprite*? SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride)
{ {
Sprite s; Sprite s;
s.id = name.hash(); s.id = name.hash();
@ -70,18 +71,21 @@ fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixel
return this.sprites.get_ref(s.id); return this.sprites.get_ref(s.id);
} }
fn Sprite*! SpriteAtlas.get(&this, String name) fn Sprite*? SpriteAtlas.get(&this, String name)
{ {
Id id = name.hash(); Id id = name.hash();
return this.sprites.get_ref(id); return this.sprites.get_ref(id);
} }
fn Sprite*! SpriteAtlas.get_by_id(&this, Id id) fn Sprite*? SpriteAtlas.get_by_id(&this, Id id)
{ {
return this.sprites.get_ref(id); return this.sprites.get_ref(id);
} }
fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h) macro Rect Sprite.rect(s) => {0,0,s.w,s.h};
macro Rect Sprite.uv(s) => {s.u,s.v,s.w,s.h};
fn void? Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h)
{ {
ctx.sprite_atlas.init(name, type, w, h)!; ctx.sprite_atlas.init(name, type, w, h)!;
} }
@ -91,12 +95,12 @@ fn Id Ctx.get_sprite_atlas_id(&ctx, String name)
return name.hash(); return name.hash();
} }
fn void! Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL) fn void? Ctx.import_sprite_memory(&ctx, String name, char[] pixels, ushort w, ushort h, ushort stride, SpriteType type = SPRITE_NORMAL)
{ {
ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!; ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!;
} }
fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) fn void? Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL)
{ {
mqoi::Desc image_desc; mqoi::Desc image_desc;
uint w, h; uint w, h;
@ -108,7 +112,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
mqoi::desc_push(&image_desc, file.read_byte()!); mqoi::desc_push(&image_desc, file.read_byte()!);
} }
if (mqoi::desc_verify(&image_desc, &w, &h) != 0) { if (mqoi::desc_verify(&image_desc, &w, &h) != 0) {
return IoError.FILE_NOT_VALID?; return io::FILE_NOT_VALID?;
} }
mqoi::Dec dec; mqoi::Dec dec;
@ -123,7 +127,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
mqoi::dec_push(&dec, file.read_byte()!); mqoi::dec_push(&dec, file.read_byte()!);
while ((px = mqoi::dec_pop(&dec)) != null) { while ((px = mqoi::dec_pop(&dec)) != null) {
pixels[idx..idx+3] = px.value; pixels[idx..idx+3] = px.value[..];
idx += 4; idx += 4;
} }
} }
@ -131,7 +135,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType t
ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!; ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!;
} }
fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) fn void? Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;
@ -143,7 +147,7 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
if (elem.flags.is_new) { if (elem.flags.is_new) {
elem.type = ETYPE_SPRITE; elem.type = ETYPE_SPRITE;
} else if (elem.type != ETYPE_SPRITE) { } else if (elem.type != ETYPE_SPRITE) {
return UgError.WRONG_ELEMENT_TYPE?; return WRONG_ELEMENT_TYPE?;
} }
Sprite* sprite = ctx.sprite_atlas.get(name)!; Sprite* sprite = ctx.sprite_atlas.get(name)!;
@ -163,11 +167,9 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
return ctx.push_sprite(elem.bounds, uv, tex_id)!; return ctx.push_sprite(elem.bounds, uv, tex_id)!;
} }
fn void! Ctx.draw_sprite_raw(&ctx, String name, Rect bounds) fn void? Ctx.draw_sprite_raw(&ctx, String name, Rect bounds)
{ {
Sprite* sprite = ctx.sprite_atlas.get(name)!; Sprite* sprite = ctx.sprite_atlas.get(name)!;
Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h };
//Rect bounds = Rect{ 0, 0, sprite.w, sprite.h }.off(off);
Id tex_id = ctx.sprite_atlas.id; Id tex_id = ctx.sprite_atlas.id;
return ctx.push_sprite(bounds, uv, tex_id, type: sprite.type)!; return ctx.push_sprite(bounds, sprite.uv(), tex_id, type: sprite.type)!;
} }

View File

@ -6,7 +6,7 @@ struct ElemText {
char* str; char* str;
} }
fn void! Ctx.text_unbounded(&ctx, String label, String text) fn void? Ctx.text_unbounded(&ctx, String label, String text)
{ {
Id id = ctx.gen_id(label)!; Id id = ctx.gen_id(label)!;

View File

@ -1,4 +1,4 @@
module vtree(<ElemType>); module vtree{ElemType};
import std::core::mem; import std::core::mem;
import std::io; import std::io;
@ -9,13 +9,7 @@ struct VTree {
isz[] refs, ordered_refs; isz[] refs, ordered_refs;
} }
fault VTreeError { faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;
CANNOT_SHRINK,
INVALID_REFERENCE,
TREE_FULL,
REFERENCE_NOT_PRESENT,
INVALID_ARGUMENT,
}
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); } macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); }
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; } macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
@ -27,11 +21,11 @@ macro @zero()
$if $assignable(0, ElemType): $if $assignable(0, ElemType):
return 0; return 0;
$else $else
return ElemType{}; return {};
$endif $endif
} }
fn void! VTree.init(&tree, usz size) fn void? VTree.init(&tree, usz size)
{ {
tree.vector = mem::new_array(ElemType, size); tree.vector = mem::new_array(ElemType, size);
defer catch { (void)mem::free(tree.vector); } defer catch { (void)mem::free(tree.vector); }
@ -74,7 +68,7 @@ fn void VTree.pack(&tree)
tree.vector[free_spot] = tree.vector[i]; tree.vector[free_spot] = tree.vector[i];
tree.refs[free_spot] = tree.refs[i]; tree.refs[free_spot] = tree.refs[i];
tree.vector[i] = @zero(); tree.vector[i] = {};
tree.refs[i] = -1; tree.refs[i] = -1;
// and move all references // and move all references
@ -90,18 +84,18 @@ fn void VTree.pack(&tree)
} }
} }
fn void! VTree.resize(&tree, usz newsize) fn void? VTree.resize(&tree, usz newsize)
{ {
// return error when shrinking with too many elements // return error when shrinking with too many elements
if (newsize < tree.elements) { if (newsize < tree.elements) {
return VTreeError.CANNOT_SHRINK?; return CANNOT_SHRINK?;
} }
// pack the vector when shrinking to avoid data loss // pack the vector when shrinking to avoid data loss
if ((int)newsize < tree.size()) { if ((int)newsize < tree.size()) {
// FIXME: packing destroys all references to elements of vec // FIXME: packing destroys all references to elements of vec
// so shrinking may cause dangling pointers // so shrinking may cause dangling pointers
return VTreeError.CANNOT_SHRINK?; return CANNOT_SHRINK?;
} }
usz old_size = tree.size(); usz old_size = tree.size();
@ -122,22 +116,22 @@ fn void! VTree.resize(&tree, usz newsize)
} }
// add an element to the tree, return it's ref // add an element to the tree, return it's ref
fn isz! VTree.add(&tree, ElemType elem, isz parent) fn isz? VTree.add(&tree, ElemType elem, isz parent)
{ {
// invalid parent // invalid parent
if (!tree.ref_is_valid(parent)) { if (!tree.ref_is_valid(parent)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// no space left // no space left
if (tree.elements >= tree.size()) { if (tree.elements >= tree.size()) {
return VTreeError.TREE_FULL?; return TREE_FULL?;
} }
// check if the parent exists // check if the parent exists
// if there are no elements in the tree the first add will set the root // if there are no elements in the tree the first add will set the root
if (!tree.ref_is_present(parent) && tree.elements != 0) { if (!tree.ref_is_present(parent) && tree.elements != 0) {
return VTreeError.REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
// get the first free spot // get the first free spot
@ -149,7 +143,7 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent)
} }
} }
if (free_spot < 0) { if (free_spot < 0) {
return VTreeError.TREE_FULL?; return TREE_FULL?;
} }
// finally add the element // finally add the element
@ -162,10 +156,10 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent)
// prune the tree starting from the ref // prune the tree starting from the ref
// returns the number of pruned elements // returns the number of pruned elements
fn usz! VTree.prune(&tree, isz ref) fn usz? VTree.prune(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
@ -196,10 +190,10 @@ fn usz VTree.nuke(&tree)
} }
// find the size of the subtree starting from ref // find the size of the subtree starting from ref
fn usz! VTree.subtree_size(&tree, isz ref) fn usz? VTree.subtree_size(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
@ -218,20 +212,20 @@ fn usz! VTree.subtree_size(&tree, isz ref)
} }
// iterate through the first level children, use a cursor like strtok_r // iterate through the first level children, use a cursor like strtok_r
fn isz! VTree.children_it(&tree, isz parent, isz *cursor) fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
{ {
if (cursor == null) { if (cursor == null) {
return VTreeError.INVALID_ARGUMENT?; return INVALID_ARGUMENT?;
} }
// if the cursor is out of bounds then we are done for sure // if the cursor is out of bounds then we are done for sure
if (!tree.ref_is_valid(*cursor)) { if (!tree.ref_is_valid(*cursor)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// same for the parent, if it's invalid it can't have children // same for the parent, if it's invalid it can't have children
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) { if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
// find the first child, update the cursor and return the ref // find the first child, update the cursor and return the ref
@ -256,10 +250,10 @@ fn isz! VTree.children_it(&tree, isz parent, isz *cursor)
* / \ [6] * / \ [6]
* [4] [5] * [4] [5]
*/ */
fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor) fn isz? VTree.level_order_it(&tree, isz ref, isz *cursor)
{ {
if (cursor == null) { if (cursor == null) {
return VTreeError.INVALID_ARGUMENT?; return INVALID_ARGUMENT?;
} }
isz[] queue = tree.ordered_refs; isz[] queue = tree.ordered_refs;
@ -303,27 +297,27 @@ fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor)
return -1; return -1;
} }
fn isz! VTree.parentof(&tree, isz ref) fn isz? VTree.parentof(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
return tree.refs[ref]; return tree.refs[ref];
} }
fn ElemType! VTree.get(&tree, isz ref) fn ElemType? VTree.get(&tree, isz ref)
{ {
if (!tree.ref_is_valid(ref)) { if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?; return INVALID_REFERENCE?;
} }
if (!tree.ref_is_present(ref)) { if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?; return REFERENCE_NOT_PRESENT?;
} }
return tree.vector[ref]; return tree.vector[ref];