update project to c3 0.7.1
This commit is contained in:
parent
34e75f8c06
commit
79a2d66880
34
TODO
34
TODO
@ -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.
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
31
src/cache.c3
31
src/cache.c3
@ -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
|
||||
|
17
src/fifo.c3
17
src/fifo.c3
@ -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--;
|
||||
|
74
src/main.c3
74
src/main.c3
@ -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(×)
|
||||
{
|
||||
@ -40,7 +40,7 @@ fn TimeStats Times.get_stats(×)
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
@ -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?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)!;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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()!;
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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 {};
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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
|
||||
|
@ -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)!;
|
||||
}
|
||||
|
@ -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)!;
|
||||
|
||||
|
64
src/vtree.c3
64
src/vtree.c3
@ -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];
|
||||
|
Loading…
Reference in New Issue
Block a user