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
[ ] Resizeable divs
[ ] 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
[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
@ -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
internal use functions (used to create widgets)
[ ] 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
[ ] Text reflow
[x] Flexbox
[ ] 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
@ -51,15 +60,16 @@ to maintain focus until mouse release (fix scroll bars)
## Commands
[x] rect commads should have:
_ border width
_ border radius
- border width
- border radius
[x] add a command to update an atlas
[ ] New window command, useful for popups
## Atlases
## Atlas
[ ] Add an interface to create, destroy, update and get atlases based on their ids
[ ] Implement multiple font atlases
[ ] Pixel format conversion
## Fonts
@ -79,3 +89,21 @@ _ border radius
[ ] Icon Buttons
[x] Switch
[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;
def SftFont = void*;
def SftUChar = uint;
def SftGlyph = uint;
alias SftFont = void*;
alias SftUChar = uint;
alias SftGlyph = uint;
const int SFT_DOWNWARD_Y = 0x01;

View File

@ -1,4 +1,4 @@
module cache(<Key, Value, SIZE>);
module cache{Key, Value, SIZE};
/* LRU Cache
* 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
import std::core::mem;
import std::core::mem::allocator;
import std::collections::bitset;
import std::collections::map;
def BitArr = bitset::BitSet(<SIZE>) @private;
def IdTable = map::HashMap(<Key, usz>) @private;
def IdTableEntry = map::Entry(<Key, usz>) @private;
alias BitArr = bitset::BitSet{SIZE};
alias IdTable = map::HashMap{Key, usz};
alias IdTableEntry = map::Entry{Key, usz};
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
foreach (idx, bit : cache.used) { cache.used[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);
}
fn Value*! Cache.search(&cache, Key id)
fn Value*? Cache.search(&cache, Key id)
{
// get_entry() faults on miss
IdTableEntry* entry = cache.table.get_entry(id)!;
@ -65,14 +66,14 @@ fn Value*! Cache.search(&cache, Key id)
/* MISS, wrong key */
if (entry.key != id) {
cache.table.remove(id)!;
return SearchResult.MISSING?;
return NOT_FOUND?;
}
/* MISS, the data is not valid (not present) */
if (!cache.present[entry.value]) {
// if the data is not present but it is still in the table, remove it
cache.table.remove(id)!;
return SearchResult.MISSING?;
return NOT_FOUND?;
}
/* HIT, set as recently used */
@ -82,7 +83,7 @@ fn Value*! Cache.search(&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) {
return;
}
@ -105,7 +106,7 @@ fn usz Cache.get_free_spot(&cache) @private
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
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
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();
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 (e != SearchResult.MISSING) {
if (e != NOT_FOUND) {
return e?;
} else {
// 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;
fault FifoErr {
FULL,
EMPTY,
}
faultdef FULL, EMPTY;
// TODO: specify the allocator
@ -15,7 +12,7 @@ struct Fifo {
usz count;
}
fn void! Fifo.init(&fifo, usz size)
fn void? Fifo.init(&fifo, usz size)
{
fifo.arr = mem::new_array(Type, size);
fifo.out = 0;
@ -27,20 +24,20 @@ fn void Fifo.free(&fifo)
(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) {
return FifoErr.FULL?;
return FULL?;
}
usz in = (fifo.out + fifo.count) % fifo.arr.len;
fifo.arr[in] = *elem;
fifo.count++;
}
fn Type*! Fifo.dequeue(&fifo)
fn Type*? Fifo.dequeue(&fifo)
{
if (fifo.count == 0) {
return FifoErr.EMPTY?;
return EMPTY?;
}
Type *ret = &fifo.arr[fifo.out];
fifo.count--;

View File

@ -7,7 +7,7 @@ import std::time;
import std::collections::ringbuffer;
import std::core::string;
def Times = ringbuffer::RingBuffer(<time::NanoDuration, 128>);
alias Times = ringbuffer::RingBuffer{time::NanoDuration[128]};
fn void Times.print_stats(&times)
{
@ -40,7 +40,7 @@ fn TimeStats Times.get_stats(&times)
}
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)
{
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)
{
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)
@ -169,11 +169,13 @@ fn int main(String[] args)
mod.lctrl = rl::isKeyDown(rl::KEY_LEFT_CONTROL);
ui.input_mod_keys(mod);
/*
for (rl::KeyboardKey key; (key = (KeyboardKey)rl::getKeyPressed()) != 0;) {
ZString kname = rl::getKeyName(key);
if (kname == null) continue;
ui.input_text_unicode(kname.str_view());
}
*/
/* Start Input Handling */
@ -203,29 +205,29 @@ fn int main(String[] args)
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()!!;
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");
toggle = !toggle;
}
//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");
}
//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");
}
ui.layout_set_row()!!;
ui.layout_next_row()!!;
static float rf, gf, bf, af;
ui.slider_ver("slider_r", Rect{0,0,30,100}, &rf)!!;
ui.slider_ver("slider_g", Rect{0,0,30,100}, &gf)!!;
ui.slider_ver("slider_b", Rect{0,0,30,100}, &bf)!!;
ui.slider_ver("slider_a", Rect{0,0,30,100}, &af)!!;
ui.slider_ver("slider_r", {0,0,30,100}, &rf)!!;
ui.slider_ver("slider_g", {0,0,30,100}, &gf)!!;
ui.slider_ver("slider_b", {0,0,30,100}, &bf)!!;
ui.slider_ver("slider_a", {0,0,30,100}, &af)!!;
ui.layout_next_column()!!;
ui.text_unbounded("text1", "Ciao Mamma\nAbilità ⚡\n'\udb80\udd2c'")!!;
@ -235,8 +237,8 @@ fn int main(String[] args)
ui.layout_next_row()!!;
static bool check;
ui.checkbox("check1", "", Point{}, &check, "tick")!!;
ui.toggle("toggle1", "", Point{}, &toggle)!!;
ui.checkbox("check1", "", {}, &check, "tick")!!;
ui.toggle("toggle1", "", {}, &toggle)!!;
/*
ui.layout_set_column()!!;
@ -248,33 +250,33 @@ fn int main(String[] args)
ui.layout_next_row()!!;
ui.button_label(" E ")!!;
*/
|};
};
ui.draw_sprite("sprite1", "tux")!!;
ui.div_end()!!;
ui.div_begin("second", ugui::DIV_FILL, scroll_x: true, scroll_y: true)!!;
{|
{
ui.layout_set_column()!!;
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);
}
ui.button("button0", Rect{0,0,50,50})!!;
ui.button("button1", Rect{0,0,50,50})!!;
ui.button("button2", Rect{0,0,50,50})!!;
ui.button("button3", Rect{0,0,50,50})!!;
ui.button("button0", {0,0,50,50})!!;
ui.button("button1", {0,0,50,50})!!;
ui.button("button2", {0,0,50,50})!!;
ui.button("button3", {0,0,50,50})!!;
if (toggle) {
ui.button("button4", Rect{0,0,50,50})!!;
ui.button("button5", Rect{0,0,50,50})!!;
ui.button("button6", Rect{0,0,50,50})!!;
ui.button("button7", Rect{0,0,50,50})!!;
ui.button("button4", {0,0,50,50})!!;
ui.button("button5", {0,0,50,50})!!;
ui.button("button6", {0,0,50,50})!!;
ui.button("button7", {0,0,50,50})!!;
}
ui.layout_next_column()!!;
ui.layout_set_row()!!;
static float f1, f2;
ui.slider_hor("hs1", Rect{0,0,100,30}, &f1)!!;
ui.slider_hor("hs2", Rect{0,0,100,30}, &f2)!!;
|};
ui.slider_hor("hs1", {0,0,100,30}, &f1)!!;
ui.slider_hor("hs2", {0,0,100,30}, &f2)!!;
};
ui.div_end()!!;
// Timings counter
@ -282,12 +284,12 @@ fn int main(String[] args)
TimeStats uts = ui_times.get_stats();
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.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.div_end()!!;
ui.frame_end()!!;
@ -341,15 +343,15 @@ fn int main(String[] args)
rl::endShaderMode();
} else if (cmd.sprite.texture_id == sprite_id) {
// FIXME: THIS CODE IS SHIT, REAL DOO DOO
{|
{
if (cmd.sprite.type == SpriteType.SPRITE_MSDF) {
rl::beginShaderMode(msdf_shader);
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 {
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 {
io::printfn("unknown texture id: %d", cmd.sprite.texture_id);
}

View File

@ -2,10 +2,7 @@ module ugui;
import std::io;
fault UgAtlasError {
CANNOT_PLACE,
INVALID_TYPE,
}
faultdef CANNOT_PLACE, INVALID_TYPE;
enum AtlasType {
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.type = type;
@ -66,7 +95,7 @@ fn void Atlas.free(&atlas)
// 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! 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;
@ -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) {
p = atlas.row;
} 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
// FIXME: "state" should be renamed "active" to toggle between an usable button and
// 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)!;
@ -22,14 +22,14 @@ fn ElemEvents! Ctx.button(&ctx, String label, Rect size, bool state = false)
if (elem.flags.is_new) {
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);
// 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 ElemEvents{}; }
if (elem.bounds.is_null()) { return {}; }
Color col = 0x0000ffffu.to_rgba();
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;
}
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)!;
@ -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;
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
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();
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;
}
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
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)!;
@ -102,7 +143,7 @@ fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* s
if (elem.flags.is_new) {
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};
@ -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);
Color col;
if (tick_sprite != String{}) {
if (tick_sprite != {}) {
col = ctx.style.bgcolor;
ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!;
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
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)!;
@ -149,7 +190,7 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta
if (elem.flags.is_new) {
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};
@ -171,6 +212,6 @@ fn void! Ctx.toggle(&ctx, String label, String description, Point off, bool* sta
// Draw the button
// FIXME: THIS IS SHIT
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)!;
}

View File

@ -68,7 +68,7 @@ macro Ctx.push_cmd(&ctx, Cmd *cmd)
// FIXME: is this really the best solution?
// "rect" is the bounding box of the element, which includes the border and the padding (so not just the content)
fn void! Ctx.push_rect(&ctx, Rect rect, Color color, bool do_border = false, bool do_padding = false, bool do_radius = false)
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 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
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 = {
.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)!;
}
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) {
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
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 = {
.type = CMD_UPDATE_ATLAS,
@ -181,7 +181,7 @@ fn void! Ctx.push_update_atlas(&ctx, Atlas* atlas)
ctx.push_cmd(&up)!;
}
fn void! Ctx.push_scissor(&ctx, Rect rect)
fn void? Ctx.push_scissor(&ctx, Rect rect)
{
Cmd sc = {
.type = CMD_SCISSOR,

View File

@ -9,7 +9,7 @@ import std::core::string;
// element ids are just long ints
def Id = usz;
alias Id = usz;
enum ElemType {
ETYPE_NONE,
@ -54,20 +54,15 @@ struct Elem {
// 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
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 {
INVALID_SIZE,
EVENT_UNSUPPORTED,
UNEXPECTED_ELEMENT,
WRONG_ELEMENT_TYPE,
}
faultdef INVALID_SIZE, EVENT_UNSUPPORTED, UNEXPECTED_ELEMENT, WRONG_ELEMENT_TYPE;
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
fn Elem*! Ctx.get_parent(&ctx)
fn Elem*? Ctx.get_parent(&ctx)
{
Id parent_id = ctx.tree.get(ctx.active_div)!;
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
// 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 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
// 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;
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
macro Elem* Ctx.find_elem(&ctx, Id id)
{
Elem*! elem;
Elem*? elem;
elem = ctx.cache.search(id);
if (catch 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);
}
fn void! Ctx.init(&ctx)
fn void? Ctx.init(&ctx)
{
ctx.tree.init(MAX_ELEMENTS)!;
defer catch { (void)ctx.tree.free(); }
@ -208,9 +203,9 @@ fn void! Ctx.init(&ctx)
ctx.active_div = 0;
// TODO: add style config
ctx.style.margin = Rect{2, 2, 2, 2};
ctx.style.border = Rect{2, 2, 2, 2};
ctx.style.padding = Rect{1, 1, 1, 1};
ctx.style.margin = {2, 2, 2, 2};
ctx.style.border = {2, 2, 2, 2};
ctx.style.padding = {1, 1, 1, 1};
ctx.style.radius = 5;
ctx.style.bgcolor = 0x282828ffu.to_rgba();
ctx.style.fgcolor = 0xfbf1c7ffu.to_rgba();
@ -226,7 +221,7 @@ fn void Ctx.free(&ctx)
(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
@ -268,7 +263,7 @@ fn void! Ctx.frame_begin(&ctx)
// 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)!;
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
// 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
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)!;
@ -42,7 +42,7 @@ fn void! Ctx.div_begin(&ctx, String label, Rect size, bool scroll_x = false, boo
if (elem.flags.is_new) {
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_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,
};
elem.bounds = ctx.position_element(parent, wanted_size);
elem.div.children_bounds = Rect{};
elem.div.children_bounds = {};
// update the ctx scissor
ctx.div_scissor = elem.bounds;
ctx.push_scissor(elem.bounds)!;
// 4. Fill the div fields
elem.div.origin_c = Point{
elem.div.origin_c = {
.x = elem.bounds.x,
.y = elem.bounds.y,
.y = elem.bounds.y
};
elem.div.origin_r = elem.div.origin_c;
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
}
fn void! Ctx.div_end(&ctx)
fn void? Ctx.div_end(&ctx)
{
// swap the children bounds
Elem* parent = ctx.get_parent()!;

View File

@ -4,13 +4,15 @@ import schrift;
import grapheme;
import std::collections::map;
import std::core::mem;
import std::core::mem::allocator;
import std::io;
import std::ascii;
// 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
* (u,v)
@ -40,14 +42,9 @@ struct Glyph {
}
const uint FONT_CACHED = 255;
def GlyphTable = map::HashMap(<Codepoint, Glyph>) @private;
alias GlyphTable = map::HashMap{Codepoint, Glyph};
fault UgFontError {
TTF_LOAD_FAILED,
MISSING_GLYPH,
BAD_GLYPH_METRICS,
RENDER_ERROR,
}
faultdef TTF_LOAD_FAILED, MISSING_GLYPH, BAD_GLYPH_METRICS, RENDER_ERROR;
struct Font {
schrift::Sft sft;
@ -61,14 +58,14 @@ struct Font {
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.size = height*scale;
font.sft = schrift::Sft{
font.sft = {
.xScale = (double)font.size,
.yScale = (double)font.size,
.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);
if (font.sft.font == null) {
font.table.free();
return UgFontError.TTF_LOAD_FAILED?;
return TTF_LOAD_FAILED?;
}
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);
if (catch excuse = gp) {
if (excuse != SearchResult.MISSING) {
if (excuse != NOT_FOUND) {
return excuse?;
}
} else {
@ -117,12 +114,12 @@ fn Glyph*! Font.get_glyph(&font, Codepoint code)
schrift::SftGlyph gid;
schrift::SftGMetrics gmtx;
if (schrift::lookup(&font.sft, code, &gid) < 0) {
return UgFontError.MISSING_GLYPH?;
if (schrift::lookup(&font.sft, (SftUChar)code, &gid) < 0) {
return MISSING_GLYPH?;
}
if (schrift::gmetrics(&font.sft, gid, &gmtx) < 0) {
return UgFontError.BAD_GLYPH_METRICS?;
return BAD_GLYPH_METRICS?;
}
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);
img.pixels = pixels;
if (schrift::render(&font.sft, gid, img) < 0) {
return UgFontError.RENDER_ERROR?;
return RENDER_ERROR?;
}
glyph.code = code;
@ -163,7 +160,7 @@ fn void Font.free(&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);
}
@ -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)
{
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) {
return 0;
}
@ -182,7 +179,7 @@ fn Codepoint str_to_codepoint(char[] str, usz* off)
return cp;
}
fn Rect! Ctx.get_text_bounds(&ctx, String text)
fn Rect? Ctx.get_text_bounds(&ctx, String text)
{
Rect text_bounds;
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 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

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_ALT = {.lalt = true, .ralt = 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 MouseButtons BTN_NONE = MouseButtons{};
const MouseButtons BTN_NONE = {};
const MouseButtons BTN_ANY = (MouseButtons)(MouseButtons.inner.max);
const MouseButtons BTN_LEFT = {.btn_left = 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
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) {
return UgError.INVALID_SIZE?;
return INVALID_SIZE?;
}
ctx.input.events.resize = ctx.width != width || ctx.height != height;
ctx.width = width;

View File

@ -7,71 +7,71 @@ enum Layout {
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)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
return UNEXPECTED_ELEMENT?;
}
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)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
return UNEXPECTED_ELEMENT?;
}
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)!;
Elem *parent = ctx.cache.search(parent_id)!;
if (parent.type != ETYPE_DIV) {
// what?
return UgError.UNEXPECTED_ELEMENT?;
return UNEXPECTED_ELEMENT?;
}
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)!;
Elem *parent = ctx.cache.search(parent_id)!;
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,
.y = parent.div.children_bounds.bottom_right().y,
};
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)!;
Elem *parent = ctx.cache.search(parent_id)!;
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,
.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
return rect;
default: // error
return Rect{};
return {};
}
// 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());
// 4. Update the parent's origin
div.origin_r = Point{
div.origin_r = {
.x = child_occupied.bottom_right().x,
.y = origin.y,
};
div.origin_c = Point{
div.origin_c = {
.x = origin.x,
.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)) {
return child_placement.off(parent.get_view_off().neg());
} 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
macro Rect Rect.intersection(Rect a, Rect b)
{
return Rect{
return {
.x = (short)max(a.x, b.x),
.y = (short)max(a.y, b.y),
.w = (short)min(a.x+a.w, b.x+b.w) - (short)max(a.x, b.x),
@ -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
macro Rect Rect.add(Rect r1, Rect r2)
{
return Rect{
return {
.x = r1.x + r2.x,
.y = r1.y + r2.y,
.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
macro Rect Rect.sub(Rect r1, Rect r2)
{
return Rect{
return {
.x = r1.x - r2.x,
.y = r1.y - r2.y,
.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
macro Rect Rect.mul(Rect r1, Rect r2)
{
return Rect{
return {
.x = r1.x * r2.x,
.y = r1.y * r2.y,
.w = r1.w * r2.w,
@ -71,7 +71,7 @@ macro Rect Rect.mul(Rect r1, Rect r2)
macro Point Rect.position(Rect r)
{
return Point{
return {
.x = r.x,
.y = r.y,
};
@ -79,7 +79,7 @@ macro Point Rect.position(Rect r)
macro Point Rect.size(Rect r)
{
return Point{
return {
.x = r.w,
.y = r.h,
};
@ -87,7 +87,7 @@ macro Point Rect.size(Rect r)
macro Rect Rect.max(Rect a, Rect b)
{
return Rect{
return {
.x = max(a.x, b.x),
.y = max(a.y, b.y),
.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)
{
return Rect{
return {
.x = min(a.x, b.x),
.y = min(a.y, b.y),
.w = min(a.w, b.w),
@ -108,7 +108,7 @@ macro Rect Rect.min(Rect a, Rect b)
// Offset a rect by a point
macro Rect Rect.off(Rect r, Point p)
{
return Rect{
return {
.x = r.x + p.x,
.y = r.y + p.y,
.w = r.w,
@ -119,7 +119,7 @@ macro Rect Rect.off(Rect r, Point p)
// Resize a rect width and height
macro Rect Rect.grow(Rect r, Point p)
{
return Rect{
return {
.x = r.x,
.y = r.y,
.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
macro Point Rect.bottom_right(Rect r)
{
return Point{
return {
.x = r.x + r.w,
.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)
{
return Point{
return {
.x = a.x + b.x,
.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)
{
return Point{
return {
.x = a.x - b.x,
.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)
{
return Point{
return {
.x = max(a.x, b.x),
.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)
{
return Point{
return {
.x = min(a.x, b.x),
.y = min(a.y, b.y),
};
@ -195,7 +195,7 @@ struct Color{
macro Color uint.to_rgba(uint u)
{
return Color{
return {
.r = (char)((u >> 24) & 0xff),
.g = (char)((u >> 16) & 0xff),
.b = (char)((u >> 8) & 0xff),

View File

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

View File

@ -1,5 +1,6 @@
module ugui;
import std::core::mem::allocator;
import std::collections::map;
import std::io;
import mqoi;
@ -20,7 +21,7 @@ struct Sprite {
ushort w, h;
}
def SpriteMap = map::HashMap(<Id, Sprite>);
alias SpriteMap = map::HashMap{Id, Sprite};
struct SpriteAtlas {
Id id;
@ -34,20 +35,20 @@ struct ElemSprite {
}
// 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
if (type != ATLAS_R8G8B8A8) {
return UgAtlasError.INVALID_TYPE?;
return INVALID_TYPE?;
}
this.id = name.hash();
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;
}
fn void! SpriteAtlas.free(&this)
fn void? SpriteAtlas.free(&this)
{
this.atlas.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
// 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;
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);
}
fn Sprite*! SpriteAtlas.get(&this, String name)
fn Sprite*? SpriteAtlas.get(&this, String name)
{
Id id = name.hash();
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);
}
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)!;
}
@ -91,12 +95,12 @@ fn Id Ctx.get_sprite_atlas_id(&ctx, String name)
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)!;
}
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;
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()!);
}
if (mqoi::desc_verify(&image_desc, &w, &h) != 0) {
return IoError.FILE_NOT_VALID?;
return io::FILE_NOT_VALID?;
}
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()!);
while ((px = mqoi::dec_pop(&dec)) != null) {
pixels[idx..idx+3] = px.value;
pixels[idx..idx+3] = px.value[..];
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)!;
}
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)!;
@ -143,7 +147,7 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0})
if (elem.flags.is_new) {
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)!;
@ -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)!;
}
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)!;
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;
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;
}
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)!;

View File

@ -1,4 +1,4 @@
module vtree(<ElemType>);
module vtree{ElemType};
import std::core::mem;
import std::io;
@ -9,13 +9,7 @@ struct VTree {
isz[] refs, ordered_refs;
}
fault VTreeError {
CANNOT_SHRINK,
INVALID_REFERENCE,
TREE_FULL,
REFERENCE_NOT_PRESENT,
INVALID_ARGUMENT,
}
faultdef CANNOT_SHRINK, INVALID_REFERENCE, TREE_FULL, REFERENCE_NOT_PRESENT, INVALID_ARGUMENT;
macro VTree.ref_is_valid(&tree, isz ref) { return (ref >= 0 && ref < tree.refs.len); }
macro VTree.ref_is_present(&tree, isz ref) { return tree.refs[ref] >= 0; }
@ -27,11 +21,11 @@ macro @zero()
$if $assignable(0, ElemType):
return 0;
$else
return ElemType{};
return {};
$endif
}
fn void! VTree.init(&tree, usz size)
fn void? VTree.init(&tree, usz size)
{
tree.vector = mem::new_array(ElemType, size);
defer catch { (void)mem::free(tree.vector); }
@ -74,7 +68,7 @@ fn void VTree.pack(&tree)
tree.vector[free_spot] = tree.vector[i];
tree.refs[free_spot] = tree.refs[i];
tree.vector[i] = @zero();
tree.vector[i] = {};
tree.refs[i] = -1;
// 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
if (newsize < tree.elements) {
return VTreeError.CANNOT_SHRINK?;
return CANNOT_SHRINK?;
}
// pack the vector when shrinking to avoid data loss
if ((int)newsize < tree.size()) {
// FIXME: packing destroys all references to elements of vec
// so shrinking may cause dangling pointers
return VTreeError.CANNOT_SHRINK?;
return CANNOT_SHRINK?;
}
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
fn isz! VTree.add(&tree, ElemType elem, isz parent)
fn isz? VTree.add(&tree, ElemType elem, isz parent)
{
// invalid parent
if (!tree.ref_is_valid(parent)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
// no space left
if (tree.elements >= tree.size()) {
return VTreeError.TREE_FULL?;
return TREE_FULL?;
}
// check if the parent exists
// if there are no elements in the tree the first add will set the root
if (!tree.ref_is_present(parent) && tree.elements != 0) {
return VTreeError.REFERENCE_NOT_PRESENT?;
return REFERENCE_NOT_PRESENT?;
}
// get the first free spot
@ -149,7 +143,7 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent)
}
}
if (free_spot < 0) {
return VTreeError.TREE_FULL?;
return TREE_FULL?;
}
// finally add the element
@ -162,10 +156,10 @@ fn isz! VTree.add(&tree, ElemType elem, isz parent)
// prune the tree starting from the ref
// 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)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
@ -196,10 +190,10 @@ fn usz VTree.nuke(&tree)
}
// 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)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
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
fn isz! VTree.children_it(&tree, isz parent, isz *cursor)
fn isz? VTree.children_it(&tree, isz parent, isz *cursor)
{
if (cursor == null) {
return VTreeError.INVALID_ARGUMENT?;
return INVALID_ARGUMENT?;
}
// if the cursor is out of bounds then we are done for sure
if (!tree.ref_is_valid(*cursor)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
// same for the parent, if it's invalid it can't have children
if (!tree.ref_is_valid(parent) || !tree.ref_is_present(parent)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
// 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]
* [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) {
return VTreeError.INVALID_ARGUMENT?;
return INVALID_ARGUMENT?;
}
isz[] queue = tree.ordered_refs;
@ -303,27 +297,27 @@ fn isz! VTree.level_order_it(&tree, isz ref, isz *cursor)
return -1;
}
fn isz! VTree.parentof(&tree, isz ref)
fn isz? VTree.parentof(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?;
return REFERENCE_NOT_PRESENT?;
}
return tree.refs[ref];
}
fn ElemType! VTree.get(&tree, isz ref)
fn ElemType? VTree.get(&tree, isz ref)
{
if (!tree.ref_is_valid(ref)) {
return VTreeError.INVALID_REFERENCE?;
return INVALID_REFERENCE?;
}
if (!tree.ref_is_present(ref)) {
return VTreeError.REFERENCE_NOT_PRESENT?;
return REFERENCE_NOT_PRESENT?;
}
return tree.vector[ref];