--- a/src/2d.c Tue Jun 17 20:11:53 2025 +0200 +++ b/src/2d.c Wed Jun 18 23:55:08 2025 +0200 @@ -37,21 +37,28 @@ typedef struct asc_rectangle_shader_s { AscShaderProgram program; GLint color; + GLint border_color; GLint size; GLint thickness; GLint radius; } AscRectangleShader; -#define ASC_RECTANGLE_SHADER_FLAG_FILL 1 -#define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 +#define ASC_RECTANGLE_SHADER_FLAG_FILL 1 +#define ASC_RECTANGLE_SHADER_FLAG_ROUND 2 +#define ASC_RECTANGLE_SHADER_FLAG_BORDER 4 static AscShaderProgram *asc_rectangle_shader_create(int flags) { AscShaderCodes codes; + // TODO: create a utility that rolls out those defines const char * const defines[] = { - "", + NULL, "#define FILL", - "#define ROUNDED_CORNERS", + "#define BORDER\n#define ROUNDED_CORNERS", "#define FILL\n#define ROUNDED_CORNERS", + "#define BORDER", + "#define BORDER\n#define FILL", + "#define BORDER\n#define ROUNDED_CORNERS", + "#define BORDER\n#define FILL\n#define ROUNDED_CORNERS", }; if (asc_shader_load_code_files((AscShaderCodeInfo){ .files.vtx = "sprite_vtx.glsl", @@ -66,12 +73,18 @@ asc_shader_free_codes(codes); return NULL; } - shader->color = glGetUniformLocation(shader->program.gl_id, "color"); shader->size = glGetUniformLocation(shader->program.gl_id, "size"); if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_FILL)) { - shader->thickness = -1; + shader->color = glGetUniformLocation(shader->program.gl_id, "color"); } else { + shader->color = -1; + } + if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_BORDER)) { shader->thickness = glGetUniformLocation(shader->program.gl_id, "thickness"); + shader->border_color = glGetUniformLocation(shader->program.gl_id, "border_color"); + } else { + shader->thickness = -1; + shader->border_color = -1; } if (asc_test_flag(flags, ASC_RECTANGLE_SHADER_FLAG_ROUND)) { shader->radius = glGetUniformLocation(shader->program.gl_id, "radius"); @@ -98,20 +111,26 @@ static void asc_rectangle_draw(const AscCamera *camera, const AscSceneNode *node) { asc_ptr_cast(AscRectangle, rectangle, node); - const bool filled = asc_test_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + const bool filled = rectangle->filled; const bool round = rectangle->radius > 0; + const bool border = rectangle->thickness > 0; // Compute shader flags int shader_flags = 0; if (filled) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_FILL; + if (border) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_BORDER; if (round) shader_flags |= ASC_RECTANGLE_SHADER_FLAG_ROUND; // Compute shader ID const int shader_ids[] = { - ASC_SHADER_RECTANGLE_DRAW, + -1, // unused ASC_SHADER_RECTANGLE_FILL, ASC_SHADER_RECTANGLE_DRAW_ROUND, ASC_SHADER_RECTANGLE_FILL_ROUND, + ASC_SHADER_RECTANGLE_DRAW, + ASC_SHADER_RECTANGLE_FILL_BORDER, + ASC_SHADER_RECTANGLE_DRAW_ROUND, + ASC_SHADER_RECTANGLE_FILL_BORDER_ROUND, }; // Look up and activate shader @@ -124,16 +143,24 @@ glUniformMatrix4fv(shader->program.model, 1, GL_FALSE, node->world_transform); - glUniform4f(shader->color, - rectangle->color.red, - rectangle->color.green, - rectangle->color.blue, - rectangle->color.alpha - ); + if (filled) { + glUniform4f(shader->color, + rectangle->color.red, + rectangle->color.green, + rectangle->color.blue, + rectangle->color.alpha + ); + } glUniform2f(shader->size, rectangle->width, rectangle->height); - if (!filled) { + if (border) { glUniform1f(shader->thickness, rectangle->thickness); + glUniform4f(shader->border_color, + rectangle->border_color.red, + rectangle->border_color.green, + rectangle->border_color.blue, + rectangle->border_color.alpha + ); } if (round) { glUniform1f(shader->radius, rectangle->radius); @@ -158,12 +185,21 @@ rectangle->height = (float) args.height; } - rectangle->thickness = ASC_NONZERO_OR(1.f, args.thickness); rectangle->radius = (float)args.radius; rectangle->color = asc_col_itof(asc_context.ink); - - if (args.filled) { - asc_set_flag(rectangle->flags, ASC_RECTANGLE_FILLED); + rectangle->border_color = asc_col_itof(args.border_color); + rectangle->filled = args.filled; + if (!args.filled && args.thickness == 0) { + // when we do not fill the rectangle, we need a border + rectangle->thickness = 1; + } else { + rectangle->thickness = args.thickness; + } + if (!args.filled && asc_col4_test_zero(args.border_color)) { + // convenience fallback: + // when we are drawing an outline but have no explicit border color, + // use the active ink + rectangle->border_color = rectangle->color; } AscSceneNode *node = &rectangle->node;