src/2d.c

changeset 159
da7ebfcdd159
parent 158
f650994ec543
--- 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;

mercurial