#include "ui.h" #include "SDL_events.h" #include "aliases.h" #include "math_utils.h" #include "window.h" #include #include #include #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 bool aabb(rect rec, i32 x, i32 y); internal line line_from_origin(point origin, f64 angle, i32 line_length); void init_ui_ctx(ui_ctx *ctx) { *ctx = (ui_ctx){0}; ctx->hovered = -1; ctx->active = -1; } void reset_ui_ctx(ui_ctx *ctx) { ctx->count = 0; ctx->mouse_down = false; ctx->mouse_up = false; ctx->rel_x = 0; ctx->rel_y = 0; } void handle_ui_events(const window *wnd, ui_ctx *ctx, const SDL_Event *event) { switch (event->type) { case SDL_MOUSEMOTION: if (wnd->id == event->motion.windowID) { ctx->mouse_x = event->motion.x; ctx->mouse_y = event->motion.y; ctx->rel_x += event->motion.xrel; ctx->rel_y += event->motion.yrel; ctx->wnd = wnd; break; } case SDL_MOUSEBUTTONDOWN: if (wnd->id == event->button.windowID) { ctx->mouse_x = event->button.x; ctx->mouse_y = event->button.y; ctx->mouse_down = true; ctx->wnd = wnd; break; } case SDL_MOUSEBUTTONUP: if (wnd->id == event->button.windowID) { ctx->mouse_x = event->button.x; ctx->mouse_y = event->button.y; ctx->mouse_up = true; ctx->wnd = wnd; break; } } } bool ui_button(const window *wnd, ui_ctx *ctx, rect rec, ui_elem_colours colours) { if (ctx->count + 1 >= MAX_UI_ELEMENTS) { return false; } u64 id = (ctx->count)++; fill_rect(wnd, rec, colours.fill); draw_rect(wnd, rec, colours.border); if (wnd != ctx->wnd || (ctx->active >= 0 && ctx->active != id)) { return false; } if (!aabb(rec, ctx->mouse_x, ctx->mouse_y)) { return false; } ctx->hovered = id; if (ctx->mouse_down) { ctx->active = id; } if (ctx->mouse_up && ctx->hovered == id && ctx->active == id) { ctx->hovered = ctx->active = -1; return true; } return false; } ui_node_elem ui_node(const window *wnd, ui_ctx *ctx, ui_node_elem node, ui_elem_colours colours) { if (ctx->count + 1 >= MAX_UI_ELEMENTS) { return (ui_node_elem){0}; } u64 id = (ctx->count)++; line ln = {0}; f64 angle = 90.0; f64 angle_delta = 25.0; i64 delta_multiplier = node.inputs % 2 == 0 ? -1 : 0; for (u64 i = 0; i < node.inputs; ++i) { if (node.noodles[i].p0.x == node.noodles[i].p1.x && node.noodles[i].p0.y == node.noodles[i].p1.y) { point origin = {node.rec.topleft.x + node.rec.w / 2, node.rec.topleft.y + node.rec.h / 2}; f64 new_angle = angle + angle_delta * delta_multiplier; ln = line_from_origin(origin, new_angle, DEFAULT_NOODLE_LENGTH); if (delta_multiplier > 0) { angle = new_angle; } if (delta_multiplier == 0) { delta_multiplier = -1; } else { delta_multiplier *= -1; } } else { ln = node.noodles[i]; } node.noodles[i] = ui_noodle(wnd, ctx, ln, colours, node.rec); } fill_rect(wnd, node.rec, colours.fill); draw_rect(wnd, node.rec, colours.border); if (wnd != ctx->wnd || (ctx->active >= 0 && ctx->active != id)) { return node; } if (ctx->mouse_up) { ctx->hovered = ctx->active = -1; ctx->rel_x = ctx->rel_y = 0; return node; } if (ctx->hovered == id && ctx->active == id) { node.rec.topleft.x += ctx->rel_x; 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; } return node; } if (!aabb(node.rec, ctx->mouse_x, ctx->mouse_y)) { return node; } ctx->hovered = id; if (ctx->mouse_down) { ctx->active = id; } return node; } internal line ui_noodle(const window *wnd, ui_ctx *ctx, line ln, ui_elem_colours colours, rect node) { if (ctx->count + 1 >= MAX_UI_ELEMENTS) { return (line){0}; } u64 id = (ctx->count)++; bool horizontal = ln.p0.y == ln.p1.y; rect bounding_box = (rect){0}; if (horizontal) { i32 x = min(ln.p0.x, ln.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.h = NOODLE_HALF_WIDTH * 2; fill_rect(wnd, bounding_box, colours.fill); } else { vec2 direction = line_direction(&ln); 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}, }; } else { f32 slope = (f32)(direction.y) / direction.x; f32 perpendicular_dy = sqrtf(square(NOODLE_HALF_WIDTH) / (square((f32)slope) + 1.0f)); 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}, }; } fill_quad(wnd, qd, colours.fill); i32 _min_x_1 = min(qd.p0.x, qd.p1.x); i32 _min_x_2 = min(qd.p2.x, qd.p3.x); i32 min_x = min(_min_x_1, _min_x_2); i32 _max_x_1 = max(qd.p0.x, qd.p1.x); i32 _max_x_2 = max(qd.p2.x, qd.p3.x); i32 max_x = max(_max_x_1, _max_x_2); i32 _min_y_1 = min(qd.p0.y, qd.p1.y); i32 _min_y_2 = min(qd.p2.y, qd.p3.y); i32 min_y = min(_min_y_1, _min_y_2); i32 _max_y_1 = max(qd.p0.y, qd.p1.y); i32 _max_y_2 = max(qd.p2.y, qd.p3.y); i32 max_y = max(_max_y_1, _max_y_2); bounding_box = (rect){ .topleft.x = min_x, .topleft.y = min_y, .w = max_x - min_x, .h = max_y - min_y, }; } if (wnd != ctx->wnd || (ctx->active >= 0 && ctx->active != id)) { return ln; } if (ctx->mouse_up) { ctx->hovered = ctx->active = -1; ctx->rel_x = ctx->rel_y = 0; return ln; } if (ctx->hovered == id && ctx->active == id) { ln.p0.x += ctx->rel_x; ln.p0.y += ctx->rel_y; return ln; } if (!aabb(bounding_box, ctx->mouse_x, ctx->mouse_y) || aabb(node, ctx->mouse_x, ctx->mouse_y)) { return ln; } ctx->hovered = id; if (ctx->mouse_down) { ctx->active = id; } return ln; } internal bool aabb(rect rec, i32 x, i32 y) { return x > rec.topleft.x && x <= rec.topleft.x + rec.w && y > rec.topleft.y && y <= rec.topleft.y + rec.h; } internal line line_from_origin(point origin, f64 angle, i32 line_length) { f64 rad = radians(angle); f64 direction = angle / absolute(angle) * -1; i32 adjacent = line_length * cos(rad) * direction; // dx i32 opposite = line_length * sin(rad) * direction; // dy return (line){ (point){origin.x + adjacent, origin.y + opposite}, origin, }; }