ui.c (25962B)
1 /* See LICENSE for license details. */ 2 #include "beamformer.h" 3 4 static Color 5 colour_from_normalized(v4 rgba) 6 { 7 return (Color){.r = rgba.r * 255.0f, .g = rgba.g * 255.0f, 8 .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; 9 } 10 11 static Color 12 fade(Color a, f32 alpha) 13 { 14 a.a = (u8)((f32)a.a * alpha); 15 return a; 16 } 17 18 static f32 19 move_towards_f32(f32 current, f32 target, f32 delta) 20 { 21 if (target < current) { 22 current -= delta; 23 if (current < target) 24 current = target; 25 } else { 26 current += delta; 27 if (current > target) 28 current = target; 29 } 30 return current; 31 } 32 33 static f32 34 lerp(f32 a, f32 b, f32 t) 35 { 36 return a + t * (b - a); 37 } 38 39 static v4 40 lerp_v4(v4 a, v4 b, f32 t) 41 { 42 return (v4){ 43 .x = a.x + t * (b.x - a.x), 44 .y = a.y + t * (b.y - a.y), 45 .z = a.z + t * (b.z - a.z), 46 .w = a.w + t * (b.w - a.w), 47 }; 48 } 49 50 static v2 51 measure_text(Font font, s8 text) 52 { 53 v2 result = {.y = font.baseSize}; 54 for (size i = 0; i < text.len; i++) { 55 /* NOTE: assumes font glyphs are ordered ASCII */ 56 i32 idx = (i32)text.data[i] - 0x20; 57 result.x += font.glyphs[idx].advanceX; 58 if (font.glyphs[idx].advanceX == 0) 59 result.x += (font.recs[idx].width + font.glyphs[idx].offsetX); 60 } 61 return result; 62 } 63 64 static v2 65 draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) 66 { 67 rlPushMatrix(); 68 69 rlTranslatef(pos.x, pos.y, 0); 70 rlRotatef(rotation, 0, 0, 1); 71 72 v2 off = {0}; 73 for (size i = 0; i < text.len; i++) { 74 /* NOTE: assumes font glyphs are ordered ASCII */ 75 i32 idx = text.data[i] - 0x20; 76 Rectangle dst = { 77 off.x + font.glyphs[idx].offsetX - font.glyphPadding, 78 off.y + font.glyphs[idx].offsetY - font.glyphPadding, 79 font.recs[idx].width + 2.0f * font.glyphPadding, 80 font.recs[idx].height + 2.0f * font.glyphPadding 81 }; 82 Rectangle src = { 83 font.recs[idx].x - font.glyphPadding, 84 font.recs[idx].y - font.glyphPadding, 85 font.recs[idx].width + 2.0f * font.glyphPadding, 86 font.recs[idx].height + 2.0f * font.glyphPadding 87 }; 88 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 89 90 off.x += font.glyphs[idx].advanceX; 91 if (font.glyphs[idx].advanceX == 0) 92 off.x += font.recs[idx].width; 93 } 94 rlPopMatrix(); 95 v2 result = {.x = off.x, .y = font.baseSize}; 96 return result; 97 } 98 99 static Rect 100 scale_rect_centered(Rect r, v2 scale) 101 { 102 Rect or = r; 103 r.size.w *= scale.x; 104 r.size.h *= scale.y; 105 r.pos.x += (or.size.w - r.size.w) / 2; 106 r.pos.y += (or.size.h - r.size.h) / 2; 107 return r; 108 } 109 110 static v2 111 center_align_text_in_rect(Rect r, s8 text, Font font) 112 { 113 v2 ts = measure_text(font, text); 114 v2 delta = { .w = r.size.w - ts.w, .h = r.size.h - ts.h }; 115 return (v2) { 116 .x = r.pos.x + 0.5 * delta.w, 117 .y = r.pos.y + 0.5 * delta.h, 118 }; 119 } 120 121 static b32 122 bmv_equal(BPModifiableValue *a, BPModifiableValue *b) 123 { 124 b32 result = (uintptr_t)a->value == (uintptr_t)b->value; 125 return result; 126 } 127 128 static f32 129 bmv_scaled_value(BPModifiableValue *a) 130 { 131 f32 result; 132 if (a->flags & MV_FLOAT) result = *(f32 *)a->value * a->scale; 133 else result = *(i32 *)a->value * a->scale; 134 return result; 135 } 136 137 static void 138 bmv_store_value(BeamformerCtx *ctx, BPModifiableValue *bmv, f32 new_val, b32 from_scroll) 139 { 140 if (bmv->flags & MV_FLOAT) { 141 f32 *value = bmv->value; 142 if (new_val / bmv->scale == *value) 143 return; 144 *value = new_val / bmv->scale; 145 CLAMP(*value, bmv->flimits.x, bmv->flimits.y); 146 } else if (bmv->flags & MV_INT && bmv->flags & MV_POWER_OF_TWO) { 147 i32 *value = bmv->value; 148 if (new_val == *value) 149 return; 150 if (from_scroll && new_val > *value) *value <<= 1; 151 else *value = round_down_power_of_2(new_val); 152 CLAMP(*value, bmv->ilimits.x, bmv->ilimits.y); 153 } else { 154 ASSERT(bmv->flags & MV_INT); 155 i32 *value = bmv->value; 156 if (new_val / bmv->scale == *value) 157 return; 158 *value = new_val / bmv->scale; 159 CLAMP(*value, bmv->ilimits.x, bmv->ilimits.y); 160 } 161 if (bmv->flags & MV_CAUSES_COMPUTE) { 162 ctx->flags |= DO_COMPUTE; 163 ctx->params->upload = 1; 164 } 165 if (bmv->flags & MV_GEN_MIPMAPS) 166 ctx->flags |= GEN_MIPMAPS; 167 } 168 169 static s8 170 bmv_sprint(BPModifiableValue *bmv, s8 buf) 171 { 172 s8 result = buf; 173 if (bmv->flags & MV_FLOAT) { 174 f32 *value = bmv->value; 175 size len = snprintf((char *)buf.data, buf.len, "%0.02f", *value * bmv->scale); 176 ASSERT(len <= buf.len); 177 result.len = len; 178 } else { 179 ASSERT(bmv->flags & MV_INT); 180 i32 *value = bmv->value; 181 size len = snprintf((char *)buf.data, buf.len, "%d", (i32)(*value * bmv->scale)); 182 ASSERT(len <= buf.len); 183 result.len = len; 184 } 185 return result; 186 } 187 188 static void 189 do_text_input(BeamformerCtx *ctx, i32 max_disp_chars, Rect r, Color colour) 190 { 191 v2 ts = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, .data = (u8 *)ctx->is.buf}); 192 v2 pos = {.x = r.pos.x, .y = r.pos.y + (r.size.y - ts.y) / 2}; 193 194 i32 buf_delta = ctx->is.buf_len - max_disp_chars; 195 if (buf_delta < 0) buf_delta = 0; 196 s8 buf = {.len = ctx->is.buf_len - buf_delta, .data = (u8 *)ctx->is.buf + buf_delta}; 197 { 198 /* NOTE: drop a char if the subtext still doesn't fit */ 199 v2 nts = measure_text(ctx->font, buf); 200 if (nts.w > 0.96 * r.size.w) { 201 buf.data++; 202 buf.len--; 203 } 204 } 205 draw_text(ctx->font, buf, pos, 0, colour); 206 207 ctx->is.cursor_blink_t = move_towards_f32(ctx->is.cursor_blink_t, 208 ctx->is.cursor_blink_target, 1.5 * ctx->dt); 209 if (ctx->is.cursor_blink_t == ctx->is.cursor_blink_target) { 210 if (ctx->is.cursor_blink_target == 0) ctx->is.cursor_blink_target = 1; 211 else ctx->is.cursor_blink_target = 0; 212 } 213 214 v4 bg = FOCUSED_COLOUR; 215 bg.a = 0; 216 Color cursor_colour = colour_from_normalized(lerp_v4(bg, FOCUSED_COLOUR, 217 ctx->is.cursor_blink_t)); 218 219 220 /* NOTE: guess a cursor position */ 221 if (ctx->is.cursor == -1) { 222 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 223 #define TEXT_HALF_CHAR_WIDTH 10 224 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * ctx->is.cursor_hover_p; 225 i32 i; 226 for (i = 0; i < ctx->is.buf_len && x_off < x_bounds; i++) { 227 /* NOTE: assumes font glyphs are ordered ASCII */ 228 i32 idx = ctx->is.buf[i] - 0x20; 229 x_off += ctx->font.glyphs[idx].advanceX; 230 if (ctx->font.glyphs[idx].advanceX == 0) 231 x_off += ctx->font.recs[idx].width; 232 } 233 ctx->is.cursor = i; 234 } 235 236 buf.len = ctx->is.cursor - buf_delta; 237 v2 sts = measure_text(ctx->font, buf); 238 f32 cursor_x = r.pos.x + sts.x; 239 f32 cursor_width; 240 if (ctx->is.cursor == ctx->is.buf_len) cursor_width = MIN(ctx->window_size.w * 0.03, 20); 241 else cursor_width = MIN(ctx->window_size.w * 0.01, 6); 242 243 Rect cursor_r = { 244 .pos = {.x = cursor_x, .y = pos.y}, 245 .size = {.w = cursor_width, .h = ts.h}, 246 }; 247 248 DrawRectanglePro(cursor_r.rl, (Vector2){0}, 0, cursor_colour); 249 250 /* NOTE: handle multiple input keys on a single frame */ 251 i32 key = GetCharPressed(); 252 while (key > 0) { 253 if (ctx->is.buf_len == (ARRAY_COUNT(ctx->is.buf) - 1)) { 254 ctx->is.buf[ARRAY_COUNT(ctx->is.buf) - 1] = 0; 255 break; 256 } 257 258 b32 allow_key = ((key >= '0' && key <= '9') || (key == '.') || 259 (key == '-' && ctx->is.cursor == 0)); 260 if (allow_key) { 261 mem_move(ctx->is.buf + ctx->is.cursor, 262 ctx->is.buf + ctx->is.cursor + 1, 263 ctx->is.buf_len - ctx->is.cursor + 1); 264 265 ctx->is.buf[ctx->is.cursor++] = key; 266 ctx->is.buf_len++; 267 } 268 key = GetCharPressed(); 269 } 270 271 if ((IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && ctx->is.cursor > 0) 272 ctx->is.cursor--; 273 274 if ((IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && 275 ctx->is.cursor < ctx->is.buf_len) 276 ctx->is.cursor++; 277 278 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && 279 ctx->is.cursor > 0) { 280 ctx->is.cursor--; 281 mem_move(ctx->is.buf + ctx->is.cursor + 1, 282 ctx->is.buf + ctx->is.cursor, 283 ctx->is.buf_len - ctx->is.cursor); 284 ctx->is.buf_len--; 285 } 286 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && 287 ctx->is.cursor < ctx->is.buf_len) { 288 mem_move(ctx->is.buf + ctx->is.cursor + 1, 289 ctx->is.buf + ctx->is.cursor, 290 ctx->is.buf_len - ctx->is.cursor); 291 ctx->is.buf_len--; 292 } 293 } 294 295 static void 296 set_text_input_idx(BeamformerCtx *ctx, BPModifiableValue bmv, Rect r, v2 mouse) 297 { 298 if (ctx->is.store.value && !bmv_equal(&ctx->is.store, &bmv)) { 299 f32 new_val = strtof(ctx->is.buf, NULL); 300 bmv_store_value(ctx, &ctx->is.store, new_val, 0); 301 } 302 303 ctx->is.store = bmv; 304 ctx->is.cursor = -1; 305 306 if (ctx->is.store.value == NULL) 307 return; 308 309 s8 ibuf = bmv_sprint(&bmv, (s8){.data = (u8 *)ctx->is.buf, .len = ARRAY_COUNT(ctx->is.buf)}); 310 ctx->is.buf_len = ibuf.len; 311 312 ASSERT(CheckCollisionPointRec(mouse.rl, r.rl)); 313 ctx->is.cursor_hover_p = (mouse.x - r.pos.x) / r.size.w; 314 CLAMP01(ctx->is.cursor_hover_p); 315 } 316 317 /* NOTE: This is kinda sucks no matter how you spin it. If we want the values to be 318 * left aligned in the center column we need to know the longest prefix length but 319 * without either hardcoding one of the prefixes as the longest one or measuring all 320 * of them we can't know this ahead of time. For now we hardcode this and manually 321 * adjust when needed */ 322 #define LISTING_LEFT_COLUMN_WIDTH 270.0f 323 #define LISTING_LINE_PAD 6.0f 324 325 static Rect 326 do_value_listing(s8 prefix, s8 suffix, f32 value, Font font, Arena a, Rect r) 327 { 328 v2 suffix_s = measure_text(font, suffix); 329 v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; 330 331 s8 txt = s8alloc(&a, 64); 332 txt.len = snprintf((char *)txt.data, txt.len, "%0.02f", value); 333 v2 txt_p = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}; 334 335 draw_text(font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 336 draw_text(font, txt, txt_p, 0, colour_from_normalized(FG_COLOUR)); 337 draw_text(font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); 338 r.pos.y += suffix_s.h + LISTING_LINE_PAD; 339 r.size.y -= suffix_s.h + LISTING_LINE_PAD; 340 341 return r; 342 } 343 344 static Rect 345 do_text_input_listing(s8 prefix, s8 suffix, BPModifiableValue bmv, BeamformerCtx *ctx, Arena a, 346 Rect r, v2 mouse, f32 *hover_t) 347 { 348 s8 buf = s8alloc(&a, 64); 349 s8 txt = buf; 350 v2 txt_s; 351 352 b32 bmv_active = bmv_equal(&bmv, &ctx->is.store); 353 if (bmv_active) { 354 txt_s = measure_text(ctx->font, (s8){.len = ctx->is.buf_len, 355 .data = (u8 *)ctx->is.buf}); 356 } else { 357 txt = bmv_sprint(&bmv, buf); 358 txt_s = measure_text(ctx->font, txt); 359 } 360 361 Rect edit_rect = { 362 .pos = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}, 363 .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} 364 }; 365 366 b32 collides = CheckCollisionPointRec(mouse.rl, edit_rect.rl); 367 if (collides && !bmv_active) *hover_t += TEXT_HOVER_SPEED * ctx->dt; 368 else *hover_t -= TEXT_HOVER_SPEED * ctx->dt; 369 CLAMP01(*hover_t); 370 371 if (collides) { 372 f32 mouse_scroll = GetMouseWheelMove(); 373 if (mouse_scroll) { 374 if (bmv_active) 375 set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); 376 f32 old_val = bmv_scaled_value(&bmv); 377 bmv_store_value(ctx, &bmv, old_val + mouse_scroll, 1); 378 txt = bmv_sprint(&bmv, buf); 379 } 380 } 381 382 if (!collides && bmv_equal(&bmv, &ctx->is.store) && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { 383 set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); 384 txt = bmv_sprint(&bmv, buf); 385 } 386 387 if (collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 388 set_text_input_idx(ctx, bmv, edit_rect, mouse); 389 390 Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); 391 392 if (!bmv_equal(&bmv, &ctx->is.store)) { 393 draw_text(ctx->font, txt, edit_rect.pos, 0, colour); 394 } else { 395 do_text_input(ctx, 7, edit_rect, colour); 396 } 397 398 v2 suffix_s = measure_text(ctx->font, suffix); 399 v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; 400 draw_text(ctx->font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 401 draw_text(ctx->font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); 402 403 r.pos.y += suffix_s.h + LISTING_LINE_PAD; 404 r.size.y -= suffix_s.h + LISTING_LINE_PAD; 405 406 return r; 407 } 408 409 static Rect 410 do_text_toggle_listing(s8 prefix, s8 text0, s8 text1, b32 toggle, BPModifiableValue bmv, 411 BeamformerCtx *ctx, Rect r, v2 mouse, f32 *hover_t) 412 { 413 v2 txt_s; 414 if (toggle) txt_s = measure_text(ctx->font, text1); 415 else txt_s = measure_text(ctx->font, text0); 416 417 Rect edit_rect = { 418 .pos = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}, 419 .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} 420 }; 421 422 b32 collides = CheckCollisionPointRec(mouse.rl, edit_rect.rl); 423 if (collides) *hover_t += TEXT_HOVER_SPEED * ctx->dt; 424 else *hover_t -= TEXT_HOVER_SPEED * ctx->dt; 425 CLAMP01(*hover_t); 426 427 b32 pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT) || IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 428 if (collides && (pressed || GetMouseWheelMove())) { 429 toggle = !toggle; 430 bmv_store_value(ctx, &bmv, toggle, 0); 431 } 432 433 Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); 434 draw_text(ctx->font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 435 draw_text(ctx->font, toggle? text1: text0, edit_rect.pos, 0, colour); 436 437 r.pos.y += txt_s.h + LISTING_LINE_PAD; 438 r.size.y -= txt_s.h + LISTING_LINE_PAD; 439 440 return r; 441 } 442 443 static b32 444 do_text_button(BeamformerCtx *ctx, s8 text, Rect r, v2 mouse, f32 *hover_t) 445 { 446 b32 hovered = CheckCollisionPointRec(mouse.rl, r.rl); 447 b32 pressed = 0; 448 pressed |= (hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); 449 pressed |= (hovered && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)); 450 451 if (hovered) *hover_t += TEXT_HOVER_SPEED * ctx->dt; 452 else *hover_t -= TEXT_HOVER_SPEED * ctx->dt; 453 CLAMP01(*hover_t); 454 455 f32 param = lerp(1, 1.04, *hover_t); 456 v2 bscale = (v2){ 457 .x = param + RECT_BTN_BORDER_WIDTH / r.size.w, 458 .y = param + RECT_BTN_BORDER_WIDTH / r.size.h, 459 }; 460 Rect sr = scale_rect_centered(r, (v2){.x = param, .y = param}); 461 Rect sb = scale_rect_centered(r, bscale); 462 DrawRectangleRounded(sb.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_BORDER_COLOUR); 463 DrawRectangleRounded(sr.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_COLOUR); 464 465 v2 tpos = center_align_text_in_rect(r, text, ctx->font); 466 v2 spos = {.x = tpos.x + 1.75, .y = tpos.y + 2}; 467 v4 colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t); 468 469 draw_text(ctx->font, text, spos, 0, fade(BLACK, 0.8)); 470 draw_text(ctx->font, text, tpos, 0, colour_from_normalized(colour)); 471 472 return pressed; 473 } 474 475 static void 476 draw_settings_ui(BeamformerCtx *ctx, Arena arena, Rect r, v2 mouse) 477 { 478 if (IsKeyPressed(KEY_ENTER) && ctx->is.store.value) 479 set_text_input_idx(ctx, (BPModifiableValue){0}, (Rect){0}, mouse); 480 481 BeamformerParameters *bp = &ctx->params->raw; 482 483 f32 minx = bp->output_min_coordinate.x + 1e-6, maxx = bp->output_max_coordinate.x - 1e-6; 484 f32 minz = bp->output_min_coordinate.z + 1e-6, maxz = bp->output_max_coordinate.z - 1e-6; 485 486 Rect draw_r = r; 487 draw_r.pos.y += 20; 488 draw_r.pos.x += 20; 489 draw_r.size.x -= 20; 490 draw_r.size.y -= 20; 491 492 draw_r = do_value_listing(s8("Sampling Frequency:"), s8("[MHz]"), 493 bp->sampling_frequency * 1e-6, ctx->font, arena, draw_r); 494 495 static f32 hover_t[13]; 496 i32 idx = 0; 497 498 BPModifiableValue bmv; 499 bmv = (BPModifiableValue){&bp->center_frequency, MV_FLOAT|MV_CAUSES_COMPUTE, 1e-6, 500 .flimits = (v2){.y = 100e6}}; 501 draw_r = do_text_input_listing(s8("Center Frequency:"), s8("[MHz]"), bmv, ctx, arena, 502 draw_r, mouse, hover_t + idx++); 503 504 bmv = (BPModifiableValue){&bp->speed_of_sound, MV_FLOAT|MV_CAUSES_COMPUTE, 1, 505 .flimits = (v2){.y = 1e6}}; 506 draw_r = do_text_input_listing(s8("Speed of Sound:"), s8("[m/s]"), bmv, ctx, arena, 507 draw_r, mouse, hover_t + idx++); 508 509 bmv = (BPModifiableValue){&bp->output_min_coordinate.x, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, 510 .flimits = (v2){.x = -1e3, .y = maxx}}; 511 draw_r = do_text_input_listing(s8("Min Lateral Point:"), s8("[mm]"), bmv, ctx, arena, 512 draw_r, mouse, hover_t + idx++); 513 514 bmv = (BPModifiableValue){&bp->output_max_coordinate.x, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, 515 .flimits = (v2){.x = minx, .y = 1e3}}; 516 draw_r = do_text_input_listing(s8("Max Lateral Point:"), s8("[mm]"), bmv, ctx, arena, 517 draw_r, mouse, hover_t + idx++); 518 519 bmv = (BPModifiableValue){&bp->output_min_coordinate.z, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, 520 .flimits = (v2){.y = maxz}}; 521 draw_r = do_text_input_listing(s8("Min Axial Point:"), s8("[mm]"), bmv, ctx, arena, 522 draw_r, mouse, hover_t + idx++); 523 524 bmv = (BPModifiableValue){&bp->output_max_coordinate.z, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, 525 .flimits = (v2){.x = minz, .y = 1e3}}; 526 draw_r = do_text_input_listing(s8("Max Axial Point:"), s8("[mm]"), bmv, ctx, arena, 527 draw_r, mouse, hover_t + idx++); 528 529 bmv = (BPModifiableValue){&bp->off_axis_pos, MV_FLOAT|MV_CAUSES_COMPUTE, 1e3, 530 .flimits = (v2){.x = -1, .y = 1}}; 531 draw_r = do_text_input_listing(s8("Off Axis Position:"), s8("[mm]"), bmv, ctx, arena, 532 draw_r, mouse, hover_t + idx++); 533 534 bmv = (BPModifiableValue){&bp->beamform_plane, MV_INT|MV_CAUSES_COMPUTE, 1, 535 .ilimits = (iv2){.y = 1}}; 536 draw_r = do_text_toggle_listing(s8("Beamform Plane:"), s8("XZ"), s8("YZ"), bp->beamform_plane, 537 bmv, ctx, draw_r, mouse, hover_t + idx++); 538 539 bmv = (BPModifiableValue){&ctx->fsctx.db, MV_FLOAT|MV_GEN_MIPMAPS, 1, 540 .flimits = (v2){.x = -120}}; 541 draw_r = do_text_input_listing(s8("Dynamic Range:"), s8("[dB]"), bmv, ctx, arena, 542 draw_r, mouse, hover_t + idx++); 543 544 draw_r.pos.y += 2 * LISTING_LINE_PAD; 545 draw_r.size.y -= 2 * LISTING_LINE_PAD; 546 547 bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.x, MV_INT|MV_POWER_OF_TWO, 1, 548 .ilimits = (iv2){.x = 1, .y = 2048}}; 549 draw_r = do_text_input_listing(s8("Export Dimension X:"), s8(""), bmv, ctx, arena, 550 draw_r, mouse, hover_t + idx++); 551 552 bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.y, MV_INT|MV_POWER_OF_TWO, 1, 553 .ilimits = (iv2){.x = 1, .y = 2048}}; 554 draw_r = do_text_input_listing(s8("Export Dimension Y:"), s8(""), bmv, ctx, arena, 555 draw_r, mouse, hover_t + idx++); 556 557 bmv = (BPModifiableValue){&ctx->export_ctx.volume_dim.z, MV_INT|MV_POWER_OF_TWO, 1, 558 .ilimits = (iv2){.x = 1, .y = 2048}}; 559 draw_r = do_text_input_listing(s8("Export Dimension Z:"), s8(""), bmv, ctx, arena, 560 draw_r, mouse, hover_t + idx++); 561 562 Rect btn_r = draw_r; 563 btn_r.size.h = ctx->font.baseSize * 1.3; 564 btn_r.size.w *= 0.6; 565 if (do_text_button(ctx, s8("Dump Raw Volume"), btn_r, mouse, hover_t + idx++)) { 566 if (!ctx->export_ctx.state) { 567 ctx->export_ctx.state = ES_START; 568 ctx->flags |= DO_COMPUTE; 569 } 570 } 571 572 /* NOTE: if C compilers didn't suck this would be a static assert */ 573 ASSERT(idx <= ARRAY_COUNT(hover_t)); 574 } 575 576 static void 577 draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) 578 { 579 uv2 ws = ctx->window_size; 580 581 static s8 labels[CS_LAST] = { 582 [CS_CUDA_DECODE] = s8("CUDA Decoding:"), 583 [CS_CUDA_HILBERT] = s8("CUDA Hilbert:"), 584 [CS_DEMOD] = s8("Demodulation:"), 585 [CS_HADAMARD] = s8("Decoding:"), 586 [CS_HERCULES] = s8("HERCULES:"), 587 [CS_MIN_MAX] = s8("Min/Max:"), 588 [CS_UFORCES] = s8("UFORCES:"), 589 }; 590 591 ComputeShaderCtx *cs = &ctx->csctx; 592 593 s8 txt_buf = s8alloc(&arena, 64); 594 v2 pos = {.x = 20, .y = ws.h - 10}; 595 596 f32 compute_time_sum = 0; 597 u32 stages = ctx->params->compute_stages_count; 598 for (u32 i = 0; i < stages; i++) { 599 u32 index = ctx->params->compute_stages[i]; 600 pos.y -= measure_text(ctx->font, labels[index]).y; 601 draw_text(ctx->font, labels[index], pos, 0, colour_from_normalized(FG_COLOUR)); 602 603 s8 tmp = txt_buf; 604 tmp.len = snprintf((char *)txt_buf.data, txt_buf.len, "%0.02e [s]", 605 cs->last_frame_time[index]); 606 v2 txt_fs = measure_text(ctx->font, tmp); 607 v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; 608 draw_text(ctx->font, tmp, rpos, 0, colour_from_normalized(FG_COLOUR)); 609 610 compute_time_sum += cs->last_frame_time[index]; 611 } 612 613 static s8 totals[2] = {s8("Compute Total:"), s8("Volume Total:")}; 614 f32 times[2] = {compute_time_sum, ctx->export_ctx.runtime}; 615 for (u32 i = 0; i < ARRAY_COUNT(totals); i++) { 616 pos.y -= measure_text(ctx->font, totals[i]).y; 617 draw_text(ctx->font, totals[i], pos, 0, colour_from_normalized(FG_COLOUR)); 618 619 s8 tmp = txt_buf; 620 tmp.len = snprintf((char *)txt_buf.data, txt_buf.len, "%0.02e [s]", times[i]); 621 v2 txt_fs = measure_text(ctx->font, tmp); 622 v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; 623 draw_text(ctx->font, tmp, rpos, 0, colour_from_normalized(FG_COLOUR)); 624 } 625 626 { 627 static v2 pos = {.x = 32, .y = 128}; 628 static v2 scale = {.x = 1.0, .y = 1.0}; 629 static u32 txt_idx = 0; 630 static s8 txt[2] = { s8("-_-"), s8("^_^") }; 631 static v2 ts[2]; 632 if (ts[0].x == 0) { 633 ts[0] = measure_text(ctx->font, txt[0]); 634 ts[1] = measure_text(ctx->font, txt[1]); 635 } 636 637 pos.x += 130 * ctx->dt * scale.x; 638 pos.y += 120 * ctx->dt * scale.y; 639 640 if (pos.x > (ws.w - ts[txt_idx].x) || pos.x < 0) { 641 txt_idx = !txt_idx; 642 CLAMP(pos.x, 0, ws.w - ts[txt_idx].x); 643 scale.x *= -1.0; 644 } 645 646 if (pos.y > (ws.h - ts[txt_idx].y) || pos.y < 0) { 647 txt_idx = !txt_idx; 648 CLAMP(pos.y, 0, ws.h - ts[txt_idx].y); 649 scale.y *= -1.0; 650 } 651 652 draw_text(ctx->font, txt[txt_idx], pos, 0, RED); 653 } 654 } 655 656 static void 657 draw_ui(BeamformerCtx *ctx, Arena arena) 658 { 659 BeamformerParameters *bp = &ctx->params->raw; 660 661 BeginDrawing(); 662 ClearBackground(colour_from_normalized(BG_COLOUR)); 663 664 Texture *output = &ctx->fsctx.output.texture; 665 666 /* TODO: this depends on the direction being rendered (x vs y) */ 667 v2 output_dim = { 668 .x = bp->output_max_coordinate.x - bp->output_min_coordinate.x, 669 .y = bp->output_max_coordinate.z - bp->output_min_coordinate.z, 670 }; 671 672 v2 mouse = { .rl = GetMousePosition() }; 673 Rect wr = {.size = {.w = (f32)ctx->window_size.w, .h = (f32)ctx->window_size.h}}; 674 Rect lr = wr, rr = wr; 675 lr.size.w = INFO_COLUMN_WIDTH; 676 rr.size.w = wr.size.w - lr.size.w; 677 rr.pos.x = lr.pos.x + lr.size.w; 678 679 if (output_dim.x > 1e-6 && output_dim.y > 1e-6) { 680 s8 txt = s8alloc(&arena, 64); 681 s8 tmp = txt; 682 tmp.len = snprintf((char *)txt.data, txt.len, "%+0.01f mm", -188.8f); 683 v2 txt_s = measure_text(ctx->small_font, tmp); 684 685 rr.pos.x += 0.02 * rr.size.w; 686 rr.pos.y += 0.02 * rr.size.h; 687 rr.size.w *= 0.96; 688 rr.size.h *= 0.96; 689 690 f32 tick_len = 20; 691 f32 pad = 1.2 * txt_s.x + tick_len; 692 693 Rect vr = rr; 694 vr.pos.x += 0.5 * txt_s.y; 695 vr.pos.y += 0.5 * txt_s.y; 696 vr.size.h = rr.size.h - pad; 697 vr.size.w = rr.size.w - pad; 698 699 f32 aspect = output_dim.h / output_dim.w; 700 if (rr.size.h < (vr.size.w * aspect) + pad) { 701 vr.size.w = vr.size.h / aspect; 702 } else { 703 vr.size.h = vr.size.w * aspect; 704 } 705 vr.pos.x += (rr.size.w - (vr.size.w + pad)) / 2; 706 vr.pos.y += (rr.size.h - (vr.size.h + pad)) / 2; 707 708 Rectangle tex_r = { 0.0f, 0.0f, (f32)output->width, -(f32)output->height }; 709 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 710 DrawTextureNPatch(*output, tex_np, vr.rl, (Vector2){0}, 0, WHITE); 711 712 /* NOTE: check mouse wheel for adjusting dynamic range of image */ 713 if (CheckCollisionPointRec(mouse.rl, vr.rl)) { 714 f32 delta = GetMouseWheelMove(); 715 ctx->fsctx.db += delta; 716 CLAMP(ctx->fsctx.db, -120, 0); 717 if (delta) ctx->flags |= GEN_MIPMAPS; 718 } 719 720 static f32 txt_colour_t[2]; 721 for (u32 i = 0; i < 2; i++) { 722 u32 line_count = vr.size.E[i] / (1.5 * txt_s.h); 723 f32 inc = vr.size.E[i] / line_count; 724 v2 start_pos = vr.pos; 725 start_pos.E[!i] += vr.size.E[!i]; 726 727 v2 end_pos = start_pos; 728 end_pos.E[!i] += tick_len; 729 730 /* NOTE: Center the Text with the Tick center */ 731 f32 txt_pos_scale[2] = {1, -1}; 732 v2 txt_pos = end_pos; 733 txt_pos.E[i] += txt_pos_scale[i] * txt_s.y/2; 734 txt_pos.E[!i] += 10; 735 736 Rect tick_rect = {.pos = start_pos, .size = vr.size}; 737 tick_rect.size.E[!i] = 10 + tick_len + txt_s.x; 738 739 /* TODO: don't do this nonsense; this code will need to get 740 * split into a seperate function */ 741 u32 coord_idx = i == 0? 0 : 2; 742 if (CheckCollisionPointRec(mouse.rl, tick_rect.rl)) { 743 f32 scale[2] = {0.5e-3, 1e-3}; 744 f32 size_delta = GetMouseWheelMove() * scale[i]; 745 /* TODO: smooth scroll this? */ 746 if (coord_idx== 0) 747 bp->output_min_coordinate.E[coord_idx] -= size_delta; 748 bp->output_max_coordinate.E[coord_idx] += size_delta; 749 if (size_delta) { 750 ctx->flags |= DO_COMPUTE; 751 ctx->params->upload = 1; 752 } 753 754 txt_colour_t[i] += TEXT_HOVER_SPEED * ctx->dt; 755 } else { 756 txt_colour_t[i] -= TEXT_HOVER_SPEED * ctx->dt; 757 } 758 CLAMP01(txt_colour_t[i]); 759 760 f32 mm = bp->output_min_coordinate.E[coord_idx] * 1e3; 761 f32 mm_inc = inc * output_dim.E[i] * 1e3 / vr.size.E[i]; 762 763 Color txt_colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, 764 txt_colour_t[i])); 765 766 char *fmt[2] = {"%+0.01f mm", "%0.01f mm"}; 767 f32 rot[2] = {90, 0}; 768 for (u32 j = 0; j <= line_count; j++) { 769 DrawLineEx(start_pos.rl, end_pos.rl, 3, colour_from_normalized(FG_COLOUR)); 770 s8 tmp = txt; 771 tmp.len = snprintf((char *)txt.data, txt.len, fmt[i], mm); 772 draw_text(ctx->small_font, tmp, txt_pos, rot[i], txt_colour); 773 start_pos.E[i] += inc; 774 end_pos.E[i] += inc; 775 txt_pos.E[i] += inc; 776 mm += mm_inc; 777 } 778 } 779 } 780 781 draw_settings_ui(ctx, arena, lr, mouse); 782 draw_debug_overlay(ctx, arena, lr); 783 EndDrawing(); 784 }