From 588a417413d72b9730dbb56aaa722268b597b562 Mon Sep 17 00:00:00 2001 From: Alessandro Mauri Date: Tue, 4 Feb 2025 22:40:44 +0100 Subject: [PATCH] checkbox and msdf sprite rendering --- TODO | 6 ++--- resources/tick_sdf.qoi | Bin 0 -> 1821 bytes src/main.c3 | 54 +++++++++++++++++++++++++++++++++++------ src/ugui_button.c3 | 21 ++++++++++------ src/ugui_cmd.c3 | 9 +++---- src/ugui_sprite.c3 | 30 ++++++++++++++++++----- 6 files changed, 91 insertions(+), 29 deletions(-) create mode 100644 resources/tick_sdf.qoi 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 0000000000000000000000000000000000000000..51a8f81a83fcd592409942a386816af9f9329275 GIT binary patch literal 1821 zcmX|?eMpsO7{;Gkjp@joeW7NFnPlbL$vTddlBqeZnOM`XNJ*PkT4pVqHj^o6rAwz} zTKSPO3w0SOCZ*}|<7g+>i5wNpR$El7MQsHY6xX%;J&hgso%6?Y-xtqwe_SU$ZA+3; z%3bFJWl0)@dZeKGglZv6h!GYF69lDT5WW&Bg{w*#FB^uSUd!2ta7H*NBnh7>xVyO_ zTH+1}mxMNnw-_!%-Iw!A!Z8KM3`Z=FT8>N~ADpqXL%+n8*y1cuFBQgI9Rp*d5nJ-N zpnBs*Twl5rwa(5+wZ&Y)Y|cjPZ?QOZ;ShSu%W*b69p6So;B#+pqufkQG#V_J!YK%6 zIQDTL&OJMao*oN|iW;zX?RS_pYlCsH_K<{rYV0u#)!xApF2QzgM;+^M?b;2TI6)*Q z-^QXv?Gp8aQHrEAAzqJ%H@y+ZIFzy!=6hx|H^0T6Jw$LYF>&G(i8C9`@;zIK5%x>g zAe`xpRa}K!=A!-Yc64_Bg@OWN%^G6nOiCPU^Na)XJ6(tna)h&zYla=|u#C&FlRMGC z1`H4Xilaw~#6)7j0$Z-h*e~Iegb*P^I3?V&rEAoXzyy@D99KrK;M6H%*DfM3kg&I> zW-|6kti2E*qzcu7Rg>fa%-{^H=4#|K9~Ylq#QF2jk(WnAMiMSA)OxW{8u*bkrzb_S ze1$~eh@cnYjjqxU2Ra~#LC9bRn%*^GaPTh5%82-QV(wgOeMk@$f8i5Rl00u=olqon z3D2}1qsh<*U-}}6NjT2qu->$yzMe=+BmDh|4?om}%0whZ2o_vi+$A?k$P?Ox2LeqZ z)e8@LAciq0WFfjAb)&WQC313z6)T9TQ*Bl4$AGAu5Wb!^4PS(X$?#-}LX$A6^@@xM zPIN*jL$Q~8(ek8Ik%vdFRmk}3->XH(eO(t}7Jh$C9Ir;rW z$kVl+6q5l{ITb6p5;@F4>&sSjcmIil2Z@*%!o!2wAf`u8M@T^kwsma7k;g~S^|%Wr z(?e8L5Ic4dVPXHzw$UUH3Sa=zn1*`R!)m>SW5Oc4>hs4 zS4w8jo$fY&N@qGMSqakv6S}&d;P7FE)YJt?3`st4lM6S>%j0kTvi&(9CA#q*8 f0pUx*U637AYL1}0L8fq0kanmS+GRP^KV$v{LxTmh literal 0 HcmV?d00001 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)!; +}