diff --git a/TODO b/TODO index 562326a..d570a17 100644 --- a/TODO +++ b/TODO @@ -27,10 +27,10 @@ to maintain focus until mouse release (fix scroll bars) [ ] .jpg [ ] gif support? [ ] layout_set_max_rows() and layout_set_max_columns() -[ ] Maybe SDF sprites?? +[x] Maybe SDF sprites?? [ ] Stylesheets and stylesheet import [ ] use SDF to draw anti-aliased rounded rectangles https://zed.dev/blog/videogame -[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for +[ ] Subdivide modules into ugui::ug for exported functions and ugui::core for internal use functions (used to create widgets) ## Layout @@ -77,4 +77,4 @@ _ border radius [ ] Text Input box [ ] Icon Buttons [ ] Switch -[ ] Checkbox +[x] Checkbox diff --git a/resources/tick_sdf.qoi b/resources/tick_sdf.qoi new file mode 100644 index 0000000..51a8f81 Binary files /dev/null and b/resources/tick_sdf.qoi differ diff --git a/src/main.c3 b/src/main.c3 index d2be50b..d012e4a 100644 --- a/src/main.c3 +++ b/src/main.c3 @@ -44,6 +44,35 @@ fn TimeStats Times.get_stats(×) } +const ZString MSDF_FS = ` +#version 330 + +in vec2 fragTexCoord; +out vec4 fragColor; +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +const float pxRange = 4.0f; + +float screenPxRange() { + vec2 unitRange = vec2(pxRange)/vec2(textureSize(texture0, 0)); + vec2 screenTexSize = vec2(1.0)/fwidth(fragTexCoord); + return max(0.5*dot(unitRange, screenTexSize), 1.0); +} + +float median(float r, float g, float b) { + return max(min(r, g), min(max(r, g), b)); +} + +void main() { + vec3 msd = texture(texture0, fragTexCoord).rgb; + float sd = median(msd.r, msd.g, msd.b); + float screenPxDistance = screenPxRange()*(sd - 0.5); + float opacity = clamp(screenPxDistance + 0.5, 0.0, 1.0); + fragColor = colDiffuse * opacity; +} +`; + const ZString FONT_FS = ` #version 330 @@ -85,6 +114,7 @@ fn int main(String[] args) ui.load_font("font1", "resources/hack-nerd.ttf", 16)!!; ui.sprite_atlas_create("icons", AtlasType.ATLAS_RGBA32, 512, 512)!!; ui.import_sprite_file_qoi("tux", "resources/tux.qoi")!!; + ui.import_sprite_file_qoi("tick", "resources/tick_sdf.qoi", SpriteType.SPRITE_MSDF)!!; short width = 800; short height = 450; @@ -103,10 +133,13 @@ fn int main(String[] args) // font stuff rl::Shader font_shader = rl::loadShaderFromMemory(null, FONT_FS); rl::Image font_atlas; - rl::Image sprite_atlas; rl::Texture2D font_texture; - rl::Texture2D sprite_texture; ugui::Id font_id = ui.get_font_id("font1"); + + // sprite stuff + rl::Shader msdf_shader = rl::loadShaderFromMemory(null, MSDF_FS); + rl::Image sprite_atlas; + rl::Texture2D sprite_texture; ugui::Id sprite_id = ui.get_sprite_atlas_id("icons"); // Main loop @@ -184,7 +217,7 @@ fn int main(String[] args) ui.layout_next_row()!!; static bool check; - ui.checkbox("check1", "", ugui::Point{}, &check)!!; + ui.checkbox("check1", "", ugui::Point{}, &check, "tick")!!; /* ui.layout_set_column()!!; @@ -288,11 +321,16 @@ fn int main(String[] args) rl::drawTextureRec(font_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); rl::endShaderMode(); } else if (cmd.sprite.texture_id == sprite_id) { - rl::Vector2 position = { - .x = cmd.sprite.rect.x, - .y = cmd.sprite.rect.y, - }; - rl::drawTextureRec(sprite_texture, cmd.sprite.texture_rect.conv(), position, cmd.sprite.hue.conv()); + // 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()); + } 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()); + } + |}; } else { io::printfn("unknown texture id: %d", cmd.sprite.texture_id); } diff --git a/src/ugui_button.c3 b/src/ugui_button.c3 index bbb0483..c7089f4 100644 --- a/src/ugui_button.c3 +++ b/src/ugui_button.c3 @@ -88,7 +88,7 @@ fn ElemEvents! Ctx.button_label(&ctx, String label, Rect size = Rect{0,0,short.m // 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) +fn void! Ctx.checkbox(&ctx, String label, String description, Point off, bool* state, String tick_sprite = {}) { Id id = ctx.gen_id(label)!; @@ -116,12 +116,19 @@ 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 (*state) { - col = 0xff0000ffu.to_rgba(); + if (tick_sprite != String{}) { + col = ctx.style.bgcolor; + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; + if (*state) { + ctx.draw_sprite_raw(tick_sprite, elem.bounds)!; + } } else { - col = 0xff00ffffu.to_rgba(); + if (*state) { + col = 0xff0000ffu.to_rgba(); + } else { + col = 0xff00ffffu.to_rgba(); + } + // Draw the button + ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; } - - // Draw the button - ctx.push_rect(elem.bounds, col, do_border: true, do_radius: true)!; } diff --git a/src/ugui_cmd.c3 b/src/ugui_cmd.c3 index 9ca04da..9524dc3 100644 --- a/src/ugui_cmd.c3 +++ b/src/ugui_cmd.c3 @@ -23,14 +23,12 @@ struct CmdUpdateAtlas { short width, height, bpp; } -// TODO: -// 1. Add atlases as a data type -// 2. Each atlas has an id struct CmdSprite { + Id texture_id; + SpriteType type; Rect rect; Rect texture_rect; Color hue; - Id texture_id; } // if rect is zero Rect{0} then reset the scissor @@ -103,10 +101,11 @@ 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()) +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, + .sprite.type = type, .sprite.rect = bounds, .sprite.texture_rect = texture, .sprite.texture_id = texture_id, diff --git a/src/ugui_sprite.c3 b/src/ugui_sprite.c3 index ce4cabb..7c1d368 100644 --- a/src/ugui_sprite.c3 +++ b/src/ugui_sprite.c3 @@ -6,8 +6,16 @@ import mqoi; const usz SRITES_PER_ATLAS = 64; +enum SpriteType { + SPRITE_NORMAL, + SPRITE_SDF, + SPRITE_MSDF, + SPRITE_ANIMATED, +} + struct Sprite { Id id; + SpriteType type; ushort u, v; ushort w, h; } @@ -46,10 +54,11 @@ fn void! SpriteAtlas.free(&this) } // 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) +fn Sprite*! SpriteAtlas.insert(&this, String name, SpriteType type, char[] pixels, ushort w, ushort h, ushort stride) { Sprite s; s.id = name.hash(); + s.type = type; Point uv = this.atlas.place(pixels, w, h, stride)!; s.w = w; s.h = h; @@ -81,12 +90,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) +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, pixels, w, h, stride)!; + ctx.sprite_atlas.insert(name, type, pixels, w, h, stride)!; } -fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) +fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path, SpriteType type = SPRITE_NORMAL) { mqoi::Desc image_desc; uint w, h; @@ -118,7 +127,7 @@ fn void! Ctx.import_sprite_file_qoi(&ctx, String name, String path) } } - ctx.sprite_atlas.insert(name, pixels, (ushort)w, (ushort)h, (ushort)w)!; + ctx.sprite_atlas.insert(name, type, pixels, (ushort)w, (ushort)h, (ushort)w)!; } fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) @@ -146,9 +155,18 @@ fn void! Ctx.draw_sprite(&ctx, String label, String name, Point off = {0,0}) // 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; + if (elem.bounds.is_null()) return; Id tex_id = ctx.sprite_atlas.id; return ctx.push_sprite(elem.bounds, uv, tex_id)!; } + +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)!; +}