module ugui; import std::collections::map; import std::io; import mqoi; const usz SRITES_PER_ATLAS = 64; struct Sprite { Id id; ushort u, v; ushort w, h; } def SpriteMap = map::HashMap(); struct SpriteAtlas { Id id; Atlas atlas; SpriteMap sprites; bool should_update; } struct ElemSprite { Id id; } // name: some examples are "icons" or "images" fn void! SpriteAtlas.init(&this, String name, AtlasType type, ushort width, ushort height) { // FIXME: for now only RGBA32 format is supported if (type != ATLAS_RGBA32) { return UgAtlasError.INVALID_TYPE?; } this.id = name.hash(); this.atlas.new(this.id, AtlasType.ATLAS_RGBA32, width, height)!; this.sprites.new_init(capacity: SRITES_PER_ATLAS); this.should_update = false; } fn void! SpriteAtlas.free(&this) { this.atlas.free(); this.sprites.free(); } // FIXME: this should throw an error when a different pixel format than the atlas' is used fn Sprite*! SpriteAtlas.insert(&this, String name, char[] pixels, ushort w, ushort h, ushort stride) { Sprite s; s.id = name.hash(); Point uv = this.atlas.place(pixels, w, h, stride)!; s.w = w; s.h = h; s.u = uv.x; s.v = uv.y; this.sprites.set(s.id, s); this.should_update = true; return this.sprites.get_ref(s.id); } 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) { return this.sprites.get_ref(id); } fn void! Ctx.sprite_atlas_create(&ctx, String name, AtlasType type, ushort w, ushort h) { ctx.sprite_atlas.init(name, type, w, h)!; } 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) { ctx.sprite_atlas.insert(name, pixels, w, h, stride)!; } fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) { mqoi::Desc image_desc; uint w, h; File file = file::open(path, "rb")!; defer (void) file.close(); while (!mqoi::desc_done(&image_desc)) { mqoi::desc_push(&image_desc, file.read_byte()!); } if (mqoi::desc_verify(&image_desc, &w, &h) != 0) { return IoError.FILE_NOT_VALID?; } mqoi::Dec dec; mqoi::Rgba* px; usz idx; char[] pixels = mem::new_array(char, (usz)w*h*4); defer mem::free(pixels); mqoi::dec_init(&dec, w*h); while (!mqoi::dec_done(&dec)) { mqoi::dec_push(&dec, file.read_byte()!); while ((px = mqoi::dec_pop(&dec)) != null) { pixels[idx..idx+3] = px.value; idx += 4; } } ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; } fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) { 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_SPRITE; } else if (elem.type != ETYPE_SPRITE) { return UgError.WRONG_ELEMENT_TYPE?; } Sprite* sprite = ctx.sprite_atlas.get(name)!; Rect uv = { sprite.u, sprite.v, sprite.w, sprite.h }; Rect bounds = { 0, 0, sprite.w, sprite.h }; elem.bounds = ctx.position_element(parent, bounds.off(off), true); elem.sprite.id = ctx.get_sprite_atlas_id(name); // 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; Id tex_id = ctx.sprite_atlas.id; return ctx.push_sprite(elem.bounds, uv, tex_id)!; }