From 8501eb787ec39c8c0a5c614160582f3bc82c123a Mon Sep 17 00:00:00 2001
From: Abdelrahman <said.abdelrahman89@gmail.com>
Date: Sun, 25 Feb 2024 12:33:13 +0000
Subject: [PATCH] Support connecting noodle to another node and ensure it stays
 connected when parent node moves

---
 include/ui.h     |   8 ++-
 src/compositor.c |   3 +-
 src/ui.c         | 151 ++++++++++++++++++++++++++++++++---------------
 3 files changed, 114 insertions(+), 48 deletions(-)

diff --git a/include/ui.h b/include/ui.h
index b537e0d..75a3a1f 100644
--- a/include/ui.h
+++ b/include/ui.h
@@ -38,10 +38,16 @@ struct ui_elem_colours {
   colour border;
 };
 
+typedef struct ui_noodle_elem ui_noodle_elem;
+struct ui_noodle_elem {
+  line noodle;
+  u64 connected_node;
+};
+
 typedef struct ui_node_elem ui_node_elem;
 struct ui_node_elem {
   rect rec;
-  line *noodles;
+  ui_noodle_elem *noodles;
   u64 inputs;
 };
 
diff --git a/src/compositor.c b/src/compositor.c
index 1b68dd7..c2cfa02 100644
--- a/src/compositor.c
+++ b/src/compositor.c
@@ -181,7 +181,8 @@ void add_node(compositor *comp, node_type type, node_data data, u64 inputs,
     return;
   }
 
-  line *noodles = mem_arena_alloc(comp->arena, inputs * sizeof(line));
+  u64 alloc_size = inputs * sizeof(ui_noodle_elem);
+  ui_noodle_elem *noodles = mem_arena_alloc(comp->arena, alloc_size);
   if (!noodles) {
     return;
   }
diff --git a/src/ui.c b/src/ui.c
index 9390f65..e44c19d 100644
--- a/src/ui.c
+++ b/src/ui.c
@@ -10,8 +10,10 @@
 #define NOODLE_HALF_WIDTH 2
 #define DEFAULT_NOODLE_LENGTH 60
 
-internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln,
-                        ui_elem_colours colours, rect node);
+internal u64 get_id(ui_ctx *ctx);
+internal ui_noodle_elem ui_noodle(const window *wnd, ui_ctx *ctx,
+                                  ui_noodle_elem noodle,
+                                  ui_elem_colours colours, u64 parent_id);
 internal bool aabb(rect rec, i32 x, i32 y);
 internal line line_from_origin(point origin, f64 angle, i32 line_length);
 
@@ -68,7 +70,7 @@ bool ui_button(const window *wnd, ui_ctx *ctx, rect rec,
     return false;
   }
 
-  u64 id = (ctx->count)++;
+  u64 id = get_id(ctx);
   ctx->elements[id] = (ui_elem){
       .id = id,
       .rect = rec,
@@ -106,14 +108,13 @@ ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node,
     return (ui_node_elem){0};
   }
 
-  u64 id = (ctx->count)++;
+  u64 id = get_id(ctx);
   ctx->elements[id] = (ui_elem){
       .id = id,
       .rect = node.rec,
       .type = UI_ELEM_NODE,
   };
 
-  line ln = {0};
   f64 angle = 90.0;
   f64 angle_delta = 25.0;
   i64 delta_multiplier = node.inputs % 2 == 0 ? -1 : 0;
@@ -121,17 +122,18 @@ ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node,
   for (u64 i = 0; i < node.inputs; ++i) {
     f64 new_angle = angle + angle_delta * delta_multiplier;
 
-    if (node.noodles[i].p0.x == node.noodles[i].p1.x &&
-        node.noodles[i].p0.y == node.noodles[i].p1.y) {
+    if (node.noodles[i].noodle.p0.x == node.noodles[i].noodle.p1.x &&
+        node.noodles[i].noodle.p0.y == node.noodles[i].noodle.p1.y) {
       point origin = {node.rec.topleft.x + node.rec.w / 2,
                       node.rec.topleft.y + node.rec.h / 2};
 
-      ln = line_from_origin(origin, new_angle, DEFAULT_NOODLE_LENGTH);
+      node.noodles[i].noodle =
+          line_from_origin(origin, new_angle, DEFAULT_NOODLE_LENGTH);
     } else {
-      ln = node.noodles[i];
+      node.noodles[i].noodle = node.noodles[i].noodle;
     }
 
-    node.noodles[i] = ui_noodle(wnd, ctx, ln, colours, node.rec);
+    node.noodles[i] = ui_noodle(wnd, ctx, node.noodles[i], colours, id);
 
     if (delta_multiplier > 0) {
       angle = new_angle;
@@ -152,8 +154,10 @@ ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node,
   }
 
   if (ctx->mouse_up) {
-    ctx->hovered = ctx->active = -1;
-    ctx->rel_x = ctx->rel_y = 0;
+    if (ctx->hovered == ctx->active && ctx->hovered == id) {
+      ctx->hovered = ctx->active = -1;
+    }
+
     return node;
   }
 
@@ -162,10 +166,13 @@ ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node,
     node.rec.topleft.y += ctx->rel_y;
 
     for (u64 i = 0; i < node.inputs; ++i) {
-      node.noodles[i].p0.x += ctx->rel_x;
-      node.noodles[i].p0.y += ctx->rel_y;
-      node.noodles[i].p1.x += ctx->rel_x;
-      node.noodles[i].p1.y += ctx->rel_y;
+      if (node.noodles[i].connected_node == 0) {
+        node.noodles[i].noodle.p0.x += ctx->rel_x;
+        node.noodles[i].noodle.p0.y += ctx->rel_y;
+      }
+
+      node.noodles[i].noodle.p1.x += ctx->rel_x;
+      node.noodles[i].noodle.p1.y += ctx->rel_y;
     }
 
     return node;
@@ -184,42 +191,52 @@ ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node,
   return node;
 }
 
-internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln,
-                        ui_elem_colours colours, rect node) {
+internal u64 get_id(ui_ctx *ctx) {
+  // This will always keep the 0 slot empty
+  return ++(ctx->count);
+}
+
+internal ui_noodle_elem ui_noodle(const window *wnd, ui_ctx *ctx,
+                                  ui_noodle_elem noodle,
+                                  ui_elem_colours colours, u64 parent_id) {
   if (ctx->count + 1 >= MAX_UI_ELEMENTS) {
-    return (line){0};
+    return (ui_noodle_elem){0};
   }
 
-  u64 id = (ctx->count)++;
+  u64 id = get_id(ctx);
   ctx->elements[id] = (ui_elem){
       .id = id,
       .rect = (rect){0},
       .type = UI_ELEM_NOODLE,
   };
 
-  bool horizontal = ln.p0.y == ln.p1.y;
+  bool horizontal = noodle.noodle.p0.y == noodle.noodle.p1.y;
 
   rect bounding_box = (rect){0};
 
   if (horizontal) {
-    i32 x = min(ln.p0.x, ln.p1.x);
+    i32 x = min(noodle.noodle.p0.x, noodle.noodle.p1.x);
 
-    bounding_box.topleft = (point){x, ln.p0.y - NOODLE_HALF_WIDTH};
-    bounding_box.w = abs(ln.p1.x - ln.p0.x);
+    bounding_box.topleft = (point){x, noodle.noodle.p0.y - NOODLE_HALF_WIDTH};
+    bounding_box.w = abs(noodle.noodle.p1.x - noodle.noodle.p0.x);
     bounding_box.h = NOODLE_HALF_WIDTH * 2;
 
     fill_rect(wnd, bounding_box, colours.fill);
   } else {
-    vec2 direction = line_direction(&ln);
+    vec2 direction = line_direction(&noodle.noodle);
 
     quad qd = (quad){0};
 
     if (direction.x == 0) {
       qd = (quad){
-          .p0 = (point){ln.p0.x - NOODLE_HALF_WIDTH, ln.p0.y},
-          .p1 = (point){ln.p0.x + NOODLE_HALF_WIDTH, ln.p0.y},
-          .p2 = (point){ln.p1.x - NOODLE_HALF_WIDTH, ln.p1.y},
-          .p3 = (point){ln.p1.x + NOODLE_HALF_WIDTH, ln.p1.y},
+          .p0 = (point){noodle.noodle.p0.x - NOODLE_HALF_WIDTH,
+                        noodle.noodle.p0.y},
+          .p1 = (point){noodle.noodle.p0.x + NOODLE_HALF_WIDTH,
+                        noodle.noodle.p0.y},
+          .p2 = (point){noodle.noodle.p1.x - NOODLE_HALF_WIDTH,
+                        noodle.noodle.p1.y},
+          .p3 = (point){noodle.noodle.p1.x + NOODLE_HALF_WIDTH,
+                        noodle.noodle.p1.y},
       };
     } else {
       f32 slope = (f32)(direction.y) / direction.x;
@@ -229,10 +246,14 @@ internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln,
       f32 perpendicular_dx = -1.0f * slope * perpendicular_dy;
 
       qd = (quad){
-          .p0 = (point){ln.p0.x - perpendicular_dx, ln.p0.y - perpendicular_dy},
-          .p1 = (point){ln.p0.x + perpendicular_dx, ln.p0.y + perpendicular_dy},
-          .p2 = (point){ln.p1.x - perpendicular_dx, ln.p1.y - perpendicular_dy},
-          .p3 = (point){ln.p1.x + perpendicular_dx, ln.p1.y + perpendicular_dy},
+          .p0 = (point){noodle.noodle.p0.x - perpendicular_dx,
+                        noodle.noodle.p0.y - perpendicular_dy},
+          .p1 = (point){noodle.noodle.p0.x + perpendicular_dx,
+                        noodle.noodle.p0.y + perpendicular_dy},
+          .p2 = (point){noodle.noodle.p1.x - perpendicular_dx,
+                        noodle.noodle.p1.y - perpendicular_dy},
+          .p3 = (point){noodle.noodle.p1.x + perpendicular_dx,
+                        noodle.noodle.p1.y + perpendicular_dy},
       };
     }
 
@@ -263,29 +284,67 @@ internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln,
   }
 
   if (wnd != ctx->wnd || (ctx->active >= 0 && ctx->active != id)) {
-    return ln;
+    return noodle;
   }
 
   if (ctx->mouse_up) {
-    ctx->hovered = ctx->active = -1;
-    ctx->rel_x = ctx->rel_y = 0;
-    return (line){
-        .p0 = ln.p0,
-        .p1 = ln.p0,
+    if (ctx->hovered == ctx->active && ctx->hovered == id) {
+      ctx->hovered = ctx->active = -1;
+
+      for (u64 i = 0; i < ctx->count; ++i) {
+        const ui_elem *elem = &(ctx->elements[i]);
+
+        if (elem->type != UI_ELEM_NODE || elem->id == parent_id) {
+          continue;
+        }
+
+        if (!aabb(elem->rect, ctx->mouse_x, ctx->mouse_y)) {
+          continue;
+        }
+
+        point p0 = (point){
+            .x = elem->rect.topleft.x + elem->rect.w / 2,
+            .y = elem->rect.topleft.y + elem->rect.h / 2,
+        };
+
+        line updated_noodle = (line){
+            .p0 = p0,
+            .p1 = noodle.noodle.p1,
+        };
+        return (ui_noodle_elem){
+            .noodle = updated_noodle,
+            .connected_node = i,
+        };
+      }
+
+      noodle.connected_node = 0;
+    }
+
+    if (noodle.connected_node > 0) {
+      return noodle;
+    }
+
+    line updated_noodle = (line){
+        .p0 = noodle.noodle.p1,
+        .p1 = noodle.noodle.p1,
+    };
+    return (ui_noodle_elem){
+        .noodle = updated_noodle,
+        .connected_node = noodle.connected_node,
     };
-    // return ln;
   }
 
   if (ctx->hovered == id && ctx->active == id) {
-    ln.p0.x += ctx->rel_x;
-    ln.p0.y += ctx->rel_y;
+    noodle.noodle.p0.x += ctx->rel_x;
+    noodle.noodle.p0.y += ctx->rel_y;
 
-    return ln;
+    return noodle;
   }
 
+  const rect *node = &(ctx->elements[parent_id].rect);
   if (!aabb(bounding_box, ctx->mouse_x, ctx->mouse_y) ||
-      aabb(node, ctx->mouse_x, ctx->mouse_y)) {
-    return ln;
+      aabb(*node, ctx->mouse_x, ctx->mouse_y)) {
+    return noodle;
   }
 
   ctx->hovered = id;
@@ -294,7 +353,7 @@ internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln,
     ctx->active = id;
   }
 
-  return ln;
+  return noodle;
 }
 
 internal bool aabb(rect rec, i32 x, i32 y) {