checkbox and msdf sprite rendering

This commit is contained in:
Alessandro Mauri 2025-02-04 22:40:44 +01:00
parent 14359a9b7e
commit 588a417413
6 changed files with 91 additions and 29 deletions

6
TODO
View File

@ -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

BIN
resources/tick_sdf.qoi Normal file

Binary file not shown.

View File

@ -44,6 +44,35 @@ fn TimeStats Times.get_stats(&times)
}
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);
}

View File

@ -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)!;
}

View File

@ -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,

View File

@ -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)!;
}