ui.c (38379B)
1 /* See LICENSE for license details. */ 2 static Color 3 colour_from_normalized(v4 rgba) 4 { 5 return (Color){.r = rgba.r * 255.0f, .g = rgba.g * 255.0f, 6 .b = rgba.b * 255.0f, .a = rgba.a * 255.0f}; 7 } 8 9 static Color 10 fade(Color a, f32 alpha) 11 { 12 a.a = (u8)((f32)a.a * alpha); 13 return a; 14 } 15 16 static f32 17 lerp(f32 a, f32 b, f32 t) 18 { 19 return a + t * (b - a); 20 } 21 22 static v4 23 lerp_v4(v4 a, v4 b, f32 t) 24 { 25 return (v4){ 26 .x = a.x + t * (b.x - a.x), 27 .y = a.y + t * (b.y - a.y), 28 .z = a.z + t * (b.z - a.z), 29 .w = a.w + t * (b.w - a.w), 30 }; 31 } 32 33 static v2 34 measure_text(Font font, s8 text) 35 { 36 v2 result = {.y = font.baseSize}; 37 for (size i = 0; i < text.len; i++) { 38 /* NOTE: assumes font glyphs are ordered ASCII */ 39 i32 idx = (i32)text.data[i] - 0x20; 40 result.x += font.glyphs[idx].advanceX; 41 if (font.glyphs[idx].advanceX == 0) 42 result.x += (font.recs[idx].width + font.glyphs[idx].offsetX); 43 } 44 return result; 45 } 46 47 static v2 48 draw_text(Font font, s8 text, v2 pos, f32 rotation, Color colour) 49 { 50 rlPushMatrix(); 51 52 rlTranslatef(pos.x, pos.y, 0); 53 rlRotatef(rotation, 0, 0, 1); 54 55 v2 off = {0}; 56 for (size i = 0; i < text.len; i++) { 57 /* NOTE: assumes font glyphs are ordered ASCII */ 58 i32 idx = text.data[i] - 0x20; 59 Rectangle dst = { 60 off.x + font.glyphs[idx].offsetX - font.glyphPadding, 61 off.y + font.glyphs[idx].offsetY - font.glyphPadding, 62 font.recs[idx].width + 2.0f * font.glyphPadding, 63 font.recs[idx].height + 2.0f * font.glyphPadding 64 }; 65 Rectangle src = { 66 font.recs[idx].x - font.glyphPadding, 67 font.recs[idx].y - font.glyphPadding, 68 font.recs[idx].width + 2.0f * font.glyphPadding, 69 font.recs[idx].height + 2.0f * font.glyphPadding 70 }; 71 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 72 73 off.x += font.glyphs[idx].advanceX; 74 if (font.glyphs[idx].advanceX == 0) 75 off.x += font.recs[idx].width; 76 } 77 rlPopMatrix(); 78 v2 result = {.x = off.x, .y = font.baseSize}; 79 return result; 80 } 81 82 static Rect 83 scale_rect_centered(Rect r, v2 scale) 84 { 85 Rect or = r; 86 r.size.w *= scale.x; 87 r.size.h *= scale.y; 88 r.pos.x += (or.size.w - r.size.w) / 2; 89 r.pos.y += (or.size.h - r.size.h) / 2; 90 return r; 91 } 92 93 static v2 94 center_align_text_in_rect(Rect r, s8 text, Font font) 95 { 96 v2 ts = measure_text(font, text); 97 v2 delta = { .w = r.size.w - ts.w, .h = r.size.h - ts.h }; 98 return (v2) { 99 .x = r.pos.x + 0.5 * delta.w, 100 .y = r.pos.y + 0.5 * delta.h, 101 }; 102 } 103 104 static b32 105 hover_text(v2 mouse, Rect text_rect, f32 *hover_t, b32 can_advance) 106 { 107 b32 hovering = CheckCollisionPointRec(mouse.rl, text_rect.rl); 108 if (hovering && can_advance) *hover_t += TEXT_HOVER_SPEED * dt_for_frame; 109 else *hover_t -= TEXT_HOVER_SPEED * dt_for_frame; 110 *hover_t = CLAMP01(*hover_t); 111 return hovering; 112 } 113 114 /* TODO(rnp): once this has more callers decide if it would be better for this to take 115 * an orientation rather than force CCW/right-handed */ 116 static void 117 draw_ruler(BeamformerUI *ui, Stream *buf, v2 start_point, v2 end_point, 118 f32 start_value, f32 end_value, f32 *markers, u32 marker_count, 119 u32 segments, s8 suffix, Color ruler_colour, Color txt_colour) 120 { 121 b32 draw_plus = SIGN(start_value) != SIGN(end_value); 122 123 end_point = sub_v2(end_point, start_point); 124 f32 rotation = atan2_f32(end_point.y, end_point.x) * 180 / PI; 125 126 rlPushMatrix(); 127 rlTranslatef(start_point.x, start_point.y, 0); 128 rlRotatef(rotation, 0, 0, 1); 129 130 f32 inc = magnitude_v2(end_point) / segments; 131 f32 value_inc = (end_value - start_value) / segments; 132 f32 value = start_value; 133 134 v2 sp = {0}, ep = {.y = RULER_TICK_LENGTH}; 135 v2 tp = {.x = ui->small_font_height / 2, .y = ep.y + RULER_TEXT_PAD}; 136 for (u32 j = 0; j <= segments; j++) { 137 DrawLineEx(sp.rl, ep.rl, 3, ruler_colour); 138 139 buf->widx = 0; 140 if (draw_plus && value > 0) stream_append_byte(buf, '+'); 141 stream_append_f64(buf, value, 10); 142 stream_append_s8(buf, suffix); 143 draw_text(ui->small_font, stream_to_s8(buf), tp, 90, txt_colour); 144 145 value += value_inc; 146 sp.x += inc; 147 ep.x += inc; 148 tp.x += inc; 149 } 150 151 ep.y += RULER_TICK_LENGTH; 152 for (u32 i = 0; i < marker_count; i++) { 153 if (markers[i] < F32_INFINITY) { 154 ep.x = sp.x = markers[i]; 155 DrawLineEx(sp.rl, ep.rl, 3, colour_from_normalized(RULER_COLOUR)); 156 DrawCircleV(ep.rl, 3, colour_from_normalized(RULER_COLOUR)); 157 } 158 } 159 160 rlPopMatrix(); 161 } 162 163 static void 164 do_scale_bar(BeamformerUI *ui, Stream *buf, Variable var, v2 mouse, i32 direction, Rect draw_rect, 165 f32 start_value, f32 end_value, s8 suffix) 166 { 167 InteractionState *is = &ui->interaction; 168 ScaleBar *sb = var.store; 169 170 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 171 172 Rect tick_rect = draw_rect; 173 v2 start_pos = tick_rect.pos; 174 v2 end_pos = tick_rect.pos; 175 v2 relative_mouse = sub_v2(mouse, tick_rect.pos); 176 177 f32 markers[2]; 178 u32 marker_count = 1; 179 180 u32 tick_count; 181 if (direction == SB_AXIAL) { 182 tick_rect.size.x = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 183 tick_count = tick_rect.size.y / (1.5 * ui->small_font_height); 184 start_pos.y += tick_rect.size.y; 185 markers[0] = tick_rect.size.y - sb->zoom_starting_point.y; 186 markers[1] = tick_rect.size.y - relative_mouse.y; 187 sb->screen_offset = (v2){.y = tick_rect.pos.y}; 188 sb->screen_space_to_value = (v2){.y = (*sb->max_value - *sb->min_value) / tick_rect.size.y}; 189 } else { 190 tick_rect.size.y = RULER_TEXT_PAD + RULER_TICK_LENGTH + txt_s.x; 191 tick_count = tick_rect.size.x / (1.5 * ui->small_font_height); 192 end_pos.x += tick_rect.size.x; 193 markers[0] = sb->zoom_starting_point.x; 194 markers[1] = relative_mouse.x; 195 /* TODO(rnp): screen space to value space transformation helper */ 196 sb->screen_offset = (v2){.x = tick_rect.pos.x}; 197 sb->screen_space_to_value = (v2){.x = (*sb->max_value - *sb->min_value) / tick_rect.size.x}; 198 } 199 200 if (hover_text(mouse, tick_rect, &sb->hover_t, 1)) { 201 is->hot_state = IS_SCALE_BAR; 202 is->hot = var; 203 marker_count = 2; 204 } 205 206 draw_ruler(ui, buf, start_pos, end_pos, start_value, end_value, markers, marker_count, 207 tick_count, suffix, colour_from_normalized(FG_COLOUR), 208 colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, sb->hover_t))); 209 } 210 211 static void 212 draw_display_overlay(BeamformerCtx *ctx, Arena a, v2 mouse, Rect display_rect, BeamformFrame *frame) 213 { 214 BeamformerUI *ui = ctx->ui; 215 BeamformerParameters *bp = &ctx->params->raw; 216 InteractionState *is = &ui->interaction; 217 218 Stream buf = arena_stream(&a); 219 Texture *output = &ctx->fsctx.output.texture; 220 221 v2 txt_s = measure_text(ui->small_font, s8("-288.8 mm")); 222 223 display_rect.pos.x += 0.02 * display_rect.size.w; 224 display_rect.pos.y += 0.02 * display_rect.size.h; 225 display_rect.size.w *= 0.96; 226 display_rect.size.h *= 0.96; 227 228 f32 pad = 1.2 * txt_s.x + RULER_TICK_LENGTH; 229 Rect vr = display_rect; 230 vr.pos.x += 0.5 * ui->small_font_height; 231 vr.pos.y += 0.5 * ui->small_font_height; 232 vr.size.h = display_rect.size.h - pad; 233 vr.size.w = display_rect.size.w - pad; 234 235 /* TODO(rnp): make this depend on the requested draw orientation (x-z or y-z or x-y) */ 236 v2 output_dim = { 237 .x = frame->max_coordinate.x - frame->min_coordinate.x, 238 .y = frame->max_coordinate.z - frame->min_coordinate.z, 239 }; 240 v2 requested_dim = { 241 .x = bp->output_max_coordinate.x - bp->output_min_coordinate.x, 242 .y = bp->output_max_coordinate.z - bp->output_min_coordinate.z, 243 }; 244 245 f32 aspect = requested_dim.h / requested_dim.w; 246 if (display_rect.size.h < (vr.size.w * aspect) + pad) { 247 vr.size.w = vr.size.h / aspect; 248 } else { 249 vr.size.h = vr.size.w * aspect; 250 } 251 vr.pos.x += (display_rect.size.w - (vr.size.w + pad)) / 2; 252 vr.pos.y += (display_rect.size.h - (vr.size.h + pad)) / 2; 253 254 v2 pixels_per_meter = { 255 .w = (f32)output->width / output_dim.w, 256 .h = (f32)output->height / output_dim.h, 257 }; 258 259 v2 texture_points = mul_v2(pixels_per_meter, requested_dim); 260 /* TODO(rnp): this also depends on x-y, y-z, x-z */ 261 v2 texture_start = { 262 .x = pixels_per_meter.x * 0.5 * (output_dim.x - requested_dim.x), 263 .y = pixels_per_meter.y * (frame->max_coordinate.z - bp->output_max_coordinate.z), 264 }; 265 266 Rectangle tex_r = {texture_start.x, texture_start.y, texture_points.x, -texture_points.y}; 267 NPatchInfo tex_np = { tex_r, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 268 DrawTextureNPatch(*output, tex_np, vr.rl, (Vector2){0}, 0, WHITE); 269 270 Variable var = {.display_scale = 1e3}; 271 var.store = ui->scale_bars[0] + SB_LATERAL; 272 var.f32_limits = (v2){.x = -1, .y = 1}; 273 var.scroll_scale = 0.5e-3; 274 275 v2 start_pos = vr.pos; 276 start_pos.y += vr.size.y; 277 278 do_scale_bar(ui, &buf, var, mouse, SB_LATERAL, (Rect){.pos = start_pos, .size = vr.size}, 279 bp->output_min_coordinate.x * 1e3, bp->output_max_coordinate.x * 1e3, s8(" mm")); 280 281 var.store = ui->scale_bars[0] + SB_AXIAL; 282 var.f32_limits = (v2){.x = 0, .y = 1}; 283 var.scroll_scale = 1e-3; 284 285 start_pos = vr.pos; 286 start_pos.x += vr.size.x; 287 288 do_scale_bar(ui, &buf, var, mouse, SB_AXIAL, (Rect){.pos = start_pos, .size = vr.size}, 289 bp->output_max_coordinate.z * 1e3, bp->output_min_coordinate.z * 1e3, s8(" mm")); 290 291 v2 pixels_to_mm = output_dim; 292 pixels_to_mm.x /= vr.size.x * 1e-3; 293 pixels_to_mm.y /= vr.size.y * 1e-3; 294 295 if (CheckCollisionPointRec(mouse.rl, vr.rl)) { 296 is->hot_state = IS_DISPLAY; 297 is->hot.store = &ctx->fsctx.threshold; 298 is->hot.type = VT_F32; 299 is->hot.f32_limits = (v2){.y = 240}; 300 is->hot.flags = V_GEN_MIPMAPS; 301 is->hot.display_scale = 1; 302 is->hot.scroll_scale = 1; 303 304 v2 relative_mouse = sub_v2(mouse, vr.pos); 305 v2 mm = mul_v2(relative_mouse, pixels_to_mm); 306 mm.x += 1e3 * bp->output_min_coordinate.x; 307 mm.y += 1e3 * bp->output_min_coordinate.z; 308 309 buf.widx = 0; 310 stream_append_v2(&buf, mm); 311 v2 txt_s = measure_text(ui->small_font, stream_to_s8(&buf)); 312 v2 txt_p = { 313 .x = vr.pos.x + vr.size.w - txt_s.w - 4, 314 .y = vr.pos.y + vr.size.h - txt_s.h - 4, 315 }; 316 draw_text(ui->small_font, stream_to_s8(&buf), txt_p, 0, 317 colour_from_normalized(RULER_COLOUR)); 318 } 319 320 /* TODO(rnp): store converted ruler points instead of screen points */ 321 if (ui->ruler_state != RS_NONE && CheckCollisionPointRec(ui->ruler_start_p.rl, vr.rl)) { 322 v2 end_p; 323 if (ui->ruler_state == RS_START) end_p = mouse; 324 else end_p = ui->ruler_stop_p; 325 326 Color colour = colour_from_normalized(RULER_COLOUR); 327 328 end_p = clamp_v2_rect(end_p, vr); 329 v2 pixel_delta = sub_v2(ui->ruler_start_p, end_p); 330 v2 mm_delta = mul_v2(pixels_to_mm, pixel_delta); 331 332 DrawCircleV(ui->ruler_start_p.rl, 3, colour); 333 DrawLineEx(end_p.rl, ui->ruler_start_p.rl, 2, colour); 334 DrawCircleV(end_p.rl, 3, colour); 335 336 buf.widx = 0; 337 stream_append_f64(&buf, magnitude_v2(mm_delta), 100); 338 stream_append_s8(&buf, s8(" mm")); 339 340 v2 txt_p = ui->ruler_start_p; 341 v2 txt_s = measure_text(ui->small_font, stream_to_s8(&buf)); 342 if (pixel_delta.y < 0) txt_p.y -= txt_s.y; 343 if (pixel_delta.x < 0) txt_p.x -= txt_s.x; 344 draw_text(ui->small_font, stream_to_s8(&buf), txt_p, 0, colour); 345 } 346 } 347 348 /* TODO(rnp): this is known after the first frame, we could unbind 349 * the texture for the first draw pass or just accept a slight glitch 350 * at start up (make a good default guess) */ 351 /* NOTE: This is kinda sucks no matter how you spin it. If we want the values to be 352 * left aligned in the center column we need to know the longest prefix length but 353 * without either hardcoding one of the prefixes as the longest one or measuring all 354 * of them we can't know this ahead of time. For now we hardcode this and manually 355 * adjust when needed */ 356 #define LISTING_LEFT_COLUMN_WIDTH 270.0f 357 #define LISTING_LINE_PAD 6.0f 358 359 static Rect 360 do_value_listing(s8 prefix, s8 suffix, Arena a, f32 value, Font font, Rect r) 361 { 362 v2 suffix_s = measure_text(font, suffix); 363 v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; 364 365 Stream buf = arena_stream(&a); 366 stream_append_f64(&buf, value, 100); 367 v2 txt_p = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}; 368 369 draw_text(font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 370 draw_text(font, stream_to_s8(&buf), txt_p, 0, colour_from_normalized(FG_COLOUR)); 371 draw_text(font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); 372 r.pos.y += suffix_s.h + LISTING_LINE_PAD; 373 r.size.y -= suffix_s.h + LISTING_LINE_PAD; 374 375 return r; 376 } 377 378 static Rect 379 do_text_input_listing(s8 prefix, s8 suffix, Variable var, BeamformerUI *ui, Rect r, 380 v2 mouse, f32 *hover_t) 381 { 382 InputState *is = &ui->text_input_state; 383 b32 text_input_active = (ui->interaction.state == IS_TEXT) && 384 (var.store == ui->interaction.active.store); 385 386 Arena arena = ui->arena_for_frame; 387 Stream buf = arena_stream(&arena); 388 v2 txt_s; 389 390 if (text_input_active) { 391 txt_s = measure_text(ui->font, (s8){.len = is->buf_len, .data = is->buf}); 392 } else { 393 stream_append_variable(&buf, &var); 394 txt_s = measure_text(ui->font, stream_to_s8(&buf)); 395 } 396 397 Rect edit_rect = { 398 .pos = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}, 399 .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} 400 }; 401 402 b32 hovering = hover_text(mouse, edit_rect, hover_t, !text_input_active); 403 if (hovering) 404 ui->interaction.hot = var; 405 406 /* TODO: where should this go? */ 407 if (text_input_active && is->cursor == -1) { 408 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 409 #define TEXT_HALF_CHAR_WIDTH 10 410 f32 hover_p = CLAMP01((mouse.x - edit_rect.pos.x) / edit_rect.size.w); 411 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = edit_rect.size.w * hover_p; 412 i32 i; 413 for (i = 0; i < is->buf_len && x_off < x_bounds; i++) { 414 /* NOTE: assumes font glyphs are ordered ASCII */ 415 i32 idx = is->buf[i] - 0x20; 416 x_off += ui->font.glyphs[idx].advanceX; 417 if (ui->font.glyphs[idx].advanceX == 0) 418 x_off += ui->font.recs[idx].width; 419 } 420 is->cursor = i; 421 } 422 423 Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); 424 425 if (text_input_active) { 426 s8 buf = {.len = is->buf_len, .data = is->buf}; 427 v2 ts = measure_text(ui->font, buf); 428 v2 pos = {.x = edit_rect.pos.x, .y = edit_rect.pos.y + (edit_rect.size.y - ts.y) / 2}; 429 430 #define MAX_DISP_CHARS 7 431 i32 buf_delta = is->buf_len - MAX_DISP_CHARS; 432 if (buf_delta < 0) buf_delta = 0; 433 buf.len -= buf_delta; 434 buf.data += buf_delta; 435 { 436 /* NOTE: drop a char if the subtext still doesn't fit */ 437 v2 nts = measure_text(ui->font, buf); 438 if (nts.w > 0.96 * edit_rect.size.w) { 439 buf.data++; 440 buf.len--; 441 } 442 } 443 draw_text(ui->font, buf, pos, 0, colour); 444 445 v4 bg = FOCUSED_COLOUR; 446 bg.a = 0; 447 Color cursor_colour = colour_from_normalized(lerp_v4(bg, FOCUSED_COLOUR, 448 CLAMP01(is->cursor_blink_t))); 449 buf.len = is->cursor - buf_delta; 450 v2 sts = measure_text(ui->font, buf); 451 f32 cursor_x = pos.x + sts.x; 452 f32 cursor_width; 453 if (is->cursor == is->buf_len) cursor_width = 20; 454 else cursor_width = 4; 455 Rect cursor_r = { 456 .pos = {.x = cursor_x, .y = pos.y}, 457 .size = {.w = cursor_width, .h = ts.h}, 458 }; 459 460 DrawRectanglePro(cursor_r.rl, (Vector2){0}, 0, cursor_colour); 461 } else { 462 draw_text(ui->font, stream_to_s8(&buf), edit_rect.pos, 0, colour); 463 } 464 465 v2 suffix_s = measure_text(ui->font, suffix); 466 v2 suffix_p = {.x = r.pos.x + r.size.w - suffix_s.w, .y = r.pos.y}; 467 draw_text(ui->font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 468 draw_text(ui->font, suffix, suffix_p, 0, colour_from_normalized(FG_COLOUR)); 469 470 r.pos.y += suffix_s.h + LISTING_LINE_PAD; 471 r.size.y -= suffix_s.h + LISTING_LINE_PAD; 472 473 return r; 474 } 475 476 static Rect 477 do_text_toggle_listing(s8 prefix, s8 text0, s8 text1, Variable var, 478 BeamformerUI *ui, Rect r, v2 mouse, f32 *hover_t) 479 { 480 b32 toggle = *(b32 *)var.store; 481 v2 txt_s; 482 if (toggle) txt_s = measure_text(ui->font, text1); 483 else txt_s = measure_text(ui->font, text0); 484 485 Rect edit_rect = { 486 .pos = {.x = r.pos.x + LISTING_LEFT_COLUMN_WIDTH, .y = r.pos.y}, 487 .size = {.x = txt_s.w + TEXT_BOX_EXTRA_X, .y = txt_s.h} 488 }; 489 490 if (hover_text(mouse, edit_rect, hover_t, 1)) 491 ui->interaction.hot = var; 492 493 Color colour = colour_from_normalized(lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t)); 494 draw_text(ui->font, prefix, r.pos, 0, colour_from_normalized(FG_COLOUR)); 495 draw_text(ui->font, toggle? text1: text0, edit_rect.pos, 0, colour); 496 497 r.pos.y += txt_s.h + LISTING_LINE_PAD; 498 r.size.y -= txt_s.h + LISTING_LINE_PAD; 499 500 return r; 501 } 502 503 static b32 504 do_text_button(BeamformerUI *ui, s8 text, Rect r, v2 mouse, f32 *hover_t) 505 { 506 b32 hovering = hover_text(mouse, r, hover_t, 1); 507 b32 pressed = 0; 508 pressed |= (hovering && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); 509 pressed |= (hovering && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)); 510 511 f32 param = lerp(1, 1.04, *hover_t); 512 v2 bscale = (v2){ 513 .x = param + RECT_BTN_BORDER_WIDTH / r.size.w, 514 .y = param + RECT_BTN_BORDER_WIDTH / r.size.h, 515 }; 516 Rect sr = scale_rect_centered(r, (v2){.x = param, .y = param}); 517 Rect sb = scale_rect_centered(r, bscale); 518 DrawRectangleRounded(sb.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_BORDER_COLOUR); 519 DrawRectangleRounded(sr.rl, RECT_BTN_ROUNDNESS, 0, RECT_BTN_COLOUR); 520 521 v2 tpos = center_align_text_in_rect(r, text, ui->font); 522 v2 spos = {.x = tpos.x + 1.75, .y = tpos.y + 2}; 523 v4 colour = lerp_v4(FG_COLOUR, HOVERED_COLOUR, *hover_t); 524 525 draw_text(ui->font, text, spos, 0, fade(BLACK, 0.8)); 526 draw_text(ui->font, text, tpos, 0, colour_from_normalized(colour)); 527 528 return pressed; 529 } 530 531 static void 532 draw_settings_ui(BeamformerCtx *ctx, Rect r, v2 mouse) 533 { 534 BeamformerUI *ui = ctx->ui; 535 BeamformerParameters *bp = &ctx->params->raw; 536 537 f32 minx = bp->output_min_coordinate.x + 1e-6, maxx = bp->output_max_coordinate.x - 1e-6; 538 f32 minz = bp->output_min_coordinate.z + 1e-6, maxz = bp->output_max_coordinate.z - 1e-6; 539 540 Rect draw_r = r; 541 draw_r.pos.y += 20; 542 draw_r.pos.x += 20; 543 draw_r.size.x -= 20; 544 draw_r.size.y -= 20; 545 546 draw_r = do_value_listing(s8("Sampling Frequency:"), s8("[MHz]"), ui->arena_for_frame, 547 bp->sampling_frequency * 1e-6, ui->font, draw_r); 548 549 static f32 hover_t[15]; 550 i32 idx = 0; 551 552 Variable var; 553 554 var.store = &bp->center_frequency; 555 var.type = VT_F32; 556 var.f32_limits = (v2){.y = 100e6}; 557 var.flags = V_CAUSES_COMPUTE; 558 var.display_scale = 1e-6; 559 var.scroll_scale = 1e5; 560 draw_r = do_text_input_listing(s8("Center Frequency:"), s8("[MHz]"), var, ui, draw_r, 561 mouse, hover_t + idx++); 562 563 var.store = &bp->speed_of_sound; 564 var.type = VT_F32; 565 var.f32_limits = (v2){.y = 1e6}; 566 var.flags = V_CAUSES_COMPUTE; 567 var.display_scale = 1; 568 var.scroll_scale = 10; 569 draw_r = do_text_input_listing(s8("Speed of Sound:"), s8("[m/s]"), var, ui, draw_r, 570 mouse, hover_t + idx++); 571 572 var.store = &bp->output_min_coordinate.x; 573 var.type = VT_F32; 574 var.f32_limits = (v2){.x = -1e3, .y = maxx}; 575 var.flags = V_CAUSES_COMPUTE; 576 var.display_scale = 1e3; 577 var.scroll_scale = 0.5e-3; 578 draw_r = do_text_input_listing(s8("Min Lateral Point:"), s8("[mm]"), var, ui, draw_r, 579 mouse, hover_t + idx++); 580 581 var.store = &bp->output_max_coordinate.x; 582 var.type = VT_F32; 583 var.f32_limits = (v2){.x = minx, .y = 1e3}; 584 var.flags = V_CAUSES_COMPUTE; 585 var.display_scale = 1e3; 586 var.scroll_scale = 0.5e-3; 587 draw_r = do_text_input_listing(s8("Max Lateral Point:"), s8("[mm]"), var, ui, draw_r, 588 mouse, hover_t + idx++); 589 590 var.store = &bp->output_min_coordinate.z; 591 var.type = VT_F32; 592 var.f32_limits = (v2){.y = maxz}; 593 var.flags = V_CAUSES_COMPUTE; 594 var.display_scale = 1e3; 595 var.scroll_scale = 0.5e-3; 596 draw_r = do_text_input_listing(s8("Min Axial Point:"), s8("[mm]"), var, ui, draw_r, 597 mouse, hover_t + idx++); 598 599 var.store = &bp->output_max_coordinate.z; 600 var.type = VT_F32; 601 var.f32_limits = (v2){.x = minz, .y = 1e3}; 602 var.flags = V_CAUSES_COMPUTE; 603 var.display_scale = 1e3; 604 var.scroll_scale = 0.5e-3; 605 draw_r = do_text_input_listing(s8("Max Axial Point:"), s8("[mm]"), var, ui, draw_r, 606 mouse, hover_t + idx++); 607 608 var.store = &bp->off_axis_pos; 609 var.type = VT_F32; 610 var.f32_limits = (v2){.x = minx, .y = maxx}; 611 var.flags = V_CAUSES_COMPUTE; 612 var.display_scale = 1e3; 613 var.scroll_scale = 0.5e-3; 614 draw_r = do_text_input_listing(s8("Off Axis Position:"), s8("[mm]"), var, ui, draw_r, 615 mouse, hover_t + idx++); 616 617 var = (Variable){0}; 618 var.store = &bp->beamform_plane; 619 var.type = VT_B32; 620 var.flags = V_CAUSES_COMPUTE; 621 draw_r = do_text_toggle_listing(s8("Beamform Plane:"), s8("XZ"), s8("YZ"), var, ui, 622 draw_r, mouse, hover_t + idx++); 623 624 var.store = &bp->f_number; 625 var.type = VT_F32; 626 var.f32_limits = (v2){.y = 1e3}; 627 var.flags = V_CAUSES_COMPUTE; 628 var.display_scale = 1; 629 var.scroll_scale = 0.1; 630 draw_r = do_text_input_listing(s8("F#:"), s8(""), var, ui, draw_r, mouse, hover_t + idx++); 631 632 var.store = &ctx->fsctx.db; 633 var.type = VT_F32; 634 var.f32_limits = (v2){.x = -120}; 635 var.flags = V_GEN_MIPMAPS; 636 var.display_scale = 1; 637 var.scroll_scale = 1; 638 draw_r = do_text_input_listing(s8("Dynamic Range:"), s8("[dB]"), var, ui, draw_r, 639 mouse, hover_t + idx++); 640 641 var.store = &ctx->fsctx.threshold; 642 var.type = VT_F32; 643 var.f32_limits = (v2){.y = 240}; 644 var.flags = V_GEN_MIPMAPS; 645 var.display_scale = 1; 646 var.scroll_scale = 1; 647 draw_r = do_text_input_listing(s8("Threshold:"), s8(""), var, ui, draw_r, 648 mouse, hover_t + idx++); 649 650 draw_r.pos.y += 2 * LISTING_LINE_PAD; 651 draw_r.size.y -= 2 * LISTING_LINE_PAD; 652 653 #if 0 654 /* TODO: work this into the work queue */ 655 bmv = (BPModifiableValue){&ctx->partial_compute_ctx.volume_dim.x, bmv_store_power_of_two, 656 .ilimits = (iv2){.x = 1, .y = ctx->gl.max_3d_texture_dim}, 657 MV_INT, 1, 1}; 658 draw_r = do_text_input_listing(s8("Export Dimension X:"), s8(""), bmv, ctx, arena, 659 draw_r, mouse, hover_t + idx++); 660 661 bmv = (BPModifiableValue){&ctx->partial_compute_ctx.volume_dim.y, bmv_store_power_of_two, 662 .ilimits = (iv2){.x = 1, .y = ctx->gl.max_3d_texture_dim}, 663 MV_INT, 1, 1}; 664 draw_r = do_text_input_listing(s8("Export Dimension Y:"), s8(""), bmv, ctx, arena, 665 draw_r, mouse, hover_t + idx++); 666 667 bmv = (BPModifiableValue){&ctx->partial_compute_ctx.volume_dim.z, bmv_store_power_of_two, 668 .ilimits = (iv2){.x = 1, .y = ctx->gl.max_3d_texture_dim}, 669 MV_INT, 1, 1}; 670 draw_r = do_text_input_listing(s8("Export Dimension Z:"), s8(""), bmv, ctx, arena, 671 draw_r, mouse, hover_t + idx++); 672 673 Rect btn_r = draw_r; 674 btn_r.size.h = ctx->font.baseSize * 1.3; 675 btn_r.size.w *= 0.6; 676 if (do_text_button(ctx, s8("Dump Raw Volume"), btn_r, mouse, hover_t + idx++)) { 677 if (!ctx->partial_compute_ctx.state) { 678 } 679 } 680 #endif 681 682 /* NOTE: if C compilers didn't suck this would be a static assert */ 683 ASSERT(idx <= ARRAY_COUNT(hover_t)); 684 } 685 686 static void 687 draw_debug_overlay(BeamformerCtx *ctx, Arena arena, Rect r) 688 { 689 static s8 labels[CS_LAST] = { 690 #define X(e, n, s, h, pn) [CS_##e] = s8(pn ":"), 691 COMPUTE_SHADERS 692 #undef X 693 }; 694 695 BeamformerUI *ui = ctx->ui; 696 ComputeShaderCtx *cs = &ctx->csctx; 697 uv2 ws = ctx->window_size; 698 699 Stream buf = stream_alloc(&arena, 64); 700 v2 pos = {.x = 20, .y = ws.h - 10}; 701 702 f32 compute_time_sum = 0; 703 u32 stages = ctx->params->compute_stages_count; 704 for (u32 i = 0; i < stages; i++) { 705 u32 index = ctx->params->compute_stages[i]; 706 pos.y -= measure_text(ui->font, labels[index]).y; 707 draw_text(ui->font, labels[index], pos, 0, colour_from_normalized(FG_COLOUR)); 708 709 buf.widx = 0; 710 stream_append_f64_e(&buf, cs->last_frame_time[index]); 711 stream_append_s8(&buf, s8(" [s]")); 712 v2 txt_fs = measure_text(ui->font, stream_to_s8(&buf)); 713 v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; 714 draw_text(ui->font, stream_to_s8(&buf), rpos, 0, colour_from_normalized(FG_COLOUR)); 715 716 compute_time_sum += cs->last_frame_time[index]; 717 } 718 719 static s8 totals[2] = {s8("Compute Total:"), s8("Volume Total:")}; 720 f32 times[2] = {compute_time_sum, ctx->partial_compute_ctx.runtime}; 721 for (u32 i = 0; i < ARRAY_COUNT(totals); i++) { 722 pos.y -= measure_text(ui->font, totals[i]).y; 723 draw_text(ui->font, totals[i], pos, 0, colour_from_normalized(FG_COLOUR)); 724 725 buf.widx = 0; 726 stream_append_f64_e(&buf, times[i]); 727 stream_append_s8(&buf, s8(" [s]")); 728 v2 txt_fs = measure_text(ui->font, stream_to_s8(&buf)); 729 v2 rpos = {.x = r.pos.x + r.size.w - txt_fs.w, .y = pos.y}; 730 draw_text(ui->font, stream_to_s8(&buf), rpos, 0, colour_from_normalized(FG_COLOUR)); 731 } 732 733 { 734 static v2 pos = {.x = 32, .y = 128}; 735 static v2 scale = {.x = 1.0, .y = 1.0}; 736 static u32 txt_idx = 0; 737 static s8 txt[2] = { s8("-_-"), s8("^_^") }; 738 static v2 ts[2]; 739 if (ts[0].x == 0) { 740 ts[0] = measure_text(ui->font, txt[0]); 741 ts[1] = measure_text(ui->font, txt[1]); 742 } 743 744 pos.x += 130 * dt_for_frame * scale.x; 745 pos.y += 120 * dt_for_frame * scale.y; 746 747 if (pos.x > (ws.w - ts[txt_idx].x) || pos.x < 0) { 748 txt_idx = !txt_idx; 749 pos.x = CLAMP(pos.x, 0, ws.w - ts[txt_idx].x); 750 scale.x *= -1.0; 751 } 752 753 if (pos.y > (ws.h - ts[txt_idx].y) || pos.y < 0) { 754 txt_idx = !txt_idx; 755 pos.y = CLAMP(pos.y, 0, ws.h - ts[txt_idx].y); 756 scale.y *= -1.0; 757 } 758 759 draw_text(ui->font, txt[txt_idx], pos, 0, RED); 760 } 761 } 762 763 static void 764 ui_store_variable(Variable *var, void *new_value) 765 { 766 /* TODO: special cases (eg. power of 2) */ 767 switch (var->type) { 768 case VT_F32: { 769 f32 f32_val = *(f32 *)new_value; 770 f32 *f32_var = var->store; 771 *f32_var = CLAMP(f32_val, var->f32_limits.x, var->f32_limits.y); 772 } break; 773 case VT_I32: { 774 i32 i32_val = *(i32 *)new_value; 775 i32 *i32_var = var->store; 776 *i32_var = CLAMP(i32_val, var->i32_limits.x, var->i32_limits.y); 777 } break; 778 default: INVALID_CODE_PATH; 779 } 780 } 781 782 static void 783 begin_text_input(InputState *is, Variable *var) 784 { 785 ASSERT(var->store != NULL); 786 787 Stream s = {.cap = ARRAY_COUNT(is->buf), .data = is->buf}; 788 stream_append_variable(&s, var); 789 ASSERT(!s.errors); 790 is->buf_len = s.widx; 791 is->cursor = -1; 792 } 793 794 static void 795 end_text_input(InputState *is, Variable *var) 796 { 797 f32 value = parse_f64((s8){.len = is->buf_len, .data = is->buf}) / var->display_scale; 798 ui_store_variable(var, &value); 799 } 800 801 static void 802 update_text_input(InputState *is) 803 { 804 if (is->cursor == -1) 805 return; 806 807 is->cursor_blink_t += is->cursor_blink_scale * dt_for_frame; 808 if (is->cursor_blink_t >= 1) is->cursor_blink_scale = -1.5f; 809 if (is->cursor_blink_t <= 0) is->cursor_blink_scale = 1.5f; 810 811 /* NOTE: handle multiple input keys on a single frame */ 812 i32 key = GetCharPressed(); 813 while (key > 0) { 814 if (is->buf_len == ARRAY_COUNT(is->buf)) 815 break; 816 817 b32 allow_key = ((key >= '0' && key <= '9') || (key == '.') || 818 (key == '-' && is->cursor == 0)); 819 if (allow_key) { 820 mem_move(is->buf + is->cursor, 821 is->buf + is->cursor + 1, 822 is->buf_len - is->cursor + 1); 823 824 is->buf[is->cursor++] = key; 825 is->buf_len++; 826 } 827 key = GetCharPressed(); 828 } 829 830 if ((IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0) 831 is->cursor--; 832 833 if ((IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->buf_len) 834 is->cursor++; 835 836 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) { 837 is->cursor--; 838 mem_move(is->buf + is->cursor + 1, 839 is->buf + is->cursor, 840 is->buf_len - is->cursor); 841 is->buf_len--; 842 } 843 844 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->buf_len) { 845 mem_move(is->buf + is->cursor + 1, 846 is->buf + is->cursor, 847 is->buf_len - is->cursor); 848 is->buf_len--; 849 } 850 } 851 852 static b32 853 ui_can_start_compute(BeamformerCtx *ctx) 854 { 855 BeamformFrame *displayed = ctx->beamform_frames + ctx->displayed_frame_index; 856 b32 result = ctx->beamform_work_queue.compute_in_flight == 0; 857 result &= (displayed->dim.x != 0 || displayed->dim.y != 0); 858 result &= displayed->dim.z != 0; 859 return result; 860 } 861 862 static void 863 ui_start_compute(BeamformerCtx *ctx) 864 { 865 /* NOTE: we do not allow ui to start a work if no work was previously completed */ 866 Arena a = {0}; 867 if (ui_can_start_compute(ctx)) { 868 beamform_work_queue_push(ctx, &a, BW_RECOMPUTE); 869 BeamformFrameIterator bfi = beamform_frame_iterator(ctx); 870 for (BeamformFrame *frame = frame_next(&bfi); frame; frame = frame_next(&bfi)) 871 glClearTexImage(frame->texture, 0, GL_RED, GL_FLOAT, 0); 872 } 873 ctx->params->upload = 1; 874 } 875 876 static void 877 ui_gen_mipmaps(BeamformerCtx *ctx) 878 { 879 if (ctx->fsctx.output.texture.id) 880 ctx->fsctx.gen_mipmaps = 1; 881 } 882 883 static void 884 display_interaction_end(BeamformerUI *ui) 885 { 886 b32 is_hot = ui->interaction.hot_state == IS_DISPLAY; 887 b32 is_active = ui->interaction.state == IS_DISPLAY; 888 if ((is_active && is_hot) || ui->ruler_state == RS_HOLD) 889 return; 890 ui->ruler_state = RS_NONE; 891 } 892 893 static void 894 display_interaction(BeamformerUI *ui, v2 mouse) 895 { 896 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 897 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 898 b32 is_hot = ui->interaction.hot_state == IS_DISPLAY; 899 b32 is_active = ui->interaction.state == IS_DISPLAY; 900 901 if (mouse_left_pressed && is_active) { 902 ui->ruler_state++; 903 switch (ui->ruler_state) { 904 case RS_START: ui->ruler_start_p = mouse; break; 905 case RS_HOLD: ui->ruler_stop_p = mouse; break; 906 default: 907 ui->ruler_state = RS_NONE; 908 break; 909 } 910 } else if ((mouse_left_pressed && !is_hot) || (mouse_right_pressed && is_hot)) { 911 ui->ruler_state = RS_NONE; 912 } 913 } 914 915 static void 916 scale_bar_interaction(BeamformerCtx *ctx, v2 mouse) 917 { 918 BeamformerUI *ui = ctx->ui; 919 InteractionState *is = &ui->interaction; 920 ScaleBar *sb = is->active.store; 921 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 922 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 923 f32 mouse_wheel = GetMouseWheelMoveV().y * is->active.scroll_scale; 924 925 if (mouse_left_pressed) { 926 if (sb->zoom_starting_point.x == F32_INFINITY) { 927 sb->zoom_starting_point = sub_v2(mouse, sb->screen_offset); 928 } else { 929 v2 relative_mouse = sub_v2(mouse, sb->screen_offset); 930 f32 min = magnitude_v2(mul_v2(sb->zoom_starting_point, sb->screen_space_to_value)); 931 f32 max = magnitude_v2(mul_v2(relative_mouse, sb->screen_space_to_value)); 932 if (min > max) { f32 tmp = min; min = max; max = tmp; } 933 934 min += *sb->min_value; 935 max += *sb->min_value; 936 937 /* TODO(rnp): SLL_* macros */ 938 v2_sll *savepoint = ui->scale_bar_savepoint_freelist; 939 if (!savepoint) savepoint = push_struct(&ui->arena_for_frame, v2_sll); 940 ui->scale_bar_savepoint_freelist = savepoint->next; 941 942 savepoint->v.x = *sb->min_value; 943 savepoint->v.y = *sb->max_value; 944 savepoint->next = sb->savepoint_stack; 945 sb->savepoint_stack = savepoint; 946 947 *sb->min_value = MAX(min, is->active.f32_limits.x); 948 *sb->max_value = MIN(max, is->active.f32_limits.y); 949 950 sb->zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 951 ui_start_compute(ctx); 952 } 953 } 954 955 if (mouse_right_pressed) { 956 v2_sll *savepoint = sb->savepoint_stack; 957 if (savepoint) { 958 *sb->min_value = savepoint->v.x; 959 *sb->max_value = savepoint->v.y; 960 ui_start_compute(ctx); 961 962 sb->savepoint_stack = savepoint->next; 963 savepoint->next = ui->scale_bar_savepoint_freelist; 964 ui->scale_bar_savepoint_freelist = savepoint; 965 } 966 sb->zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 967 } 968 969 if (mouse_wheel) { 970 v2 limits = is->active.f32_limits; 971 *sb->min_value -= mouse_wheel * sb->scroll_both; 972 *sb->max_value += mouse_wheel; 973 *sb->min_value = MAX(limits.x, *sb->min_value); 974 *sb->max_value = MIN(limits.y, *sb->max_value); 975 ui_start_compute(ctx); 976 } 977 } 978 979 static void 980 ui_begin_interact(BeamformerUI *ui, BeamformerInput *input, b32 scroll, b32 mouse_left_pressed) 981 { 982 InteractionState *is = &ui->interaction; 983 if (is->hot_state != IS_NONE) { 984 is->state = is->hot_state; 985 } else { 986 switch (is->hot.type) { 987 case VT_NULL: is->state = IS_NOP; break; 988 case VT_B32: is->state = IS_SET; break; 989 case VT_GROUP: is->state = IS_SET; break; 990 case VT_F32: { 991 if (scroll) { 992 is->state = IS_SCROLL; 993 } else if (mouse_left_pressed) { 994 is->state = IS_TEXT; 995 begin_text_input(&ui->text_input_state, &is->hot); 996 } 997 } break; 998 } 999 } 1000 if (is->state != IS_NONE) { 1001 is->active = is->hot; 1002 } 1003 } 1004 1005 static void 1006 ui_end_interact(BeamformerCtx *ctx, v2 mouse) 1007 { 1008 BeamformerUI *ui = ctx->ui; 1009 InteractionState *is = &ui->interaction; 1010 switch (is->state) { 1011 case IS_NONE: break; 1012 case IS_NOP: break; 1013 case IS_SET: { 1014 switch (is->active.type) { 1015 case VT_B32: { 1016 b32 *val = is->active.store; 1017 *val = !(*val); 1018 } break; 1019 } 1020 } break; 1021 case IS_DISPLAY: display_interaction_end(ui); /* FALLTHROUGH */ 1022 case IS_SCROLL: { 1023 f32 delta = GetMouseWheelMoveV().y * is->active.scroll_scale; 1024 switch (is->active.type) { 1025 case VT_B32: { 1026 b32 *old_val = is->active.store; 1027 b32 new_val = !(*old_val); 1028 ui_store_variable(&is->active, &new_val); 1029 } break; 1030 case VT_F32: { 1031 f32 *old_val = is->active.store; 1032 f32 new_val = *old_val + delta; 1033 ui_store_variable(&is->active, &new_val); 1034 } break; 1035 case VT_I32: { 1036 i32 *old_val = is->active.store; 1037 i32 new_val = *old_val + delta; 1038 ui_store_variable(&is->active, &new_val); 1039 } break; 1040 } 1041 } break; 1042 case IS_SCALE_BAR: break; 1043 case IS_TEXT: end_text_input(&ui->text_input_state, &is->active); break; 1044 } 1045 1046 if (is->active.flags & V_CAUSES_COMPUTE) 1047 ui_start_compute(ctx); 1048 1049 if (is->active.flags & V_GEN_MIPMAPS) 1050 ui_gen_mipmaps(ctx); 1051 1052 is->state = IS_NONE; 1053 is->active = NULL_VARIABLE; 1054 } 1055 1056 static void 1057 ui_interact(BeamformerCtx *ctx, BeamformerInput *input) 1058 { 1059 BeamformerUI *ui = ctx->ui; 1060 InteractionState *is = &ui->interaction; 1061 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 1062 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 1063 b32 wheel_moved = GetMouseWheelMoveV().y != 0; 1064 if (mouse_right_pressed || mouse_left_pressed || wheel_moved) { 1065 if (is->state != IS_NONE) 1066 ui_end_interact(ctx, input->mouse); 1067 ui_begin_interact(ui, input, wheel_moved, mouse_left_pressed); 1068 } 1069 1070 if (IsKeyPressed(KEY_ENTER) && is->state == IS_TEXT) 1071 ui_end_interact(ctx, input->mouse); 1072 1073 switch (is->state) { 1074 case IS_DISPLAY: display_interaction(ui, input->mouse); break; 1075 case IS_SCROLL: ui_end_interact(ctx, input->mouse); break; 1076 case IS_SET: ui_end_interact(ctx, input->mouse); break; 1077 case IS_TEXT: update_text_input(&ui->text_input_state); break; 1078 case IS_DRAG: { 1079 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { 1080 ui_end_interact(ctx, input->mouse); 1081 } else { 1082 switch (is->active.type) { 1083 } 1084 } 1085 } break; 1086 case IS_SCALE_BAR: scale_bar_interaction(ctx, input->mouse); break; 1087 } 1088 1089 is->hot_state = IS_NONE; 1090 is->hot = NULL_VARIABLE; 1091 } 1092 1093 static void 1094 ui_init(BeamformerCtx *ctx, Arena store) 1095 { 1096 /* NOTE(rnp): store the ui at the base of the passed in arena and use the rest for 1097 * temporary allocations within the ui. If needed we can recall this function to 1098 * completely clear the ui state. The is that if we store pointers to static data 1099 * such as embedded font data we will need to reset them when the executable reloads. 1100 * We could also build some sort of ui structure here and store it then iterate over 1101 * it to actually draw the ui. If we reload we may have changed it so we should 1102 * rebuild it */ 1103 1104 /* NOTE: unload old fonts from the GPU */ 1105 if (ctx->ui) { 1106 UnloadFont(ctx->ui->font); 1107 UnloadFont(ctx->ui->small_font); 1108 } 1109 1110 BeamformerUI *ui = ctx->ui = alloc(&store, typeof(*ctx->ui), 1); 1111 ui->arena_for_frame = store; 1112 ui->frame_temporary_arena = begin_temp_arena(&ui->arena_for_frame); 1113 1114 /* TODO: build these into the binary */ 1115 ui->font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 28, 0, 0); 1116 ui->small_font = LoadFontEx("assets/IBMPlexSans-Bold.ttf", 22, 0, 0); 1117 1118 ui->font_height = measure_text(ui->font, s8("8\\W")).h; 1119 ui->small_font_height = measure_text(ui->small_font, s8("8\\W")).h; 1120 1121 /* TODO: multiple views */ 1122 ui->scale_bars[0][SB_LATERAL].min_value = &ctx->params->raw.output_min_coordinate.x; 1123 ui->scale_bars[0][SB_LATERAL].max_value = &ctx->params->raw.output_max_coordinate.x; 1124 ui->scale_bars[0][SB_AXIAL].min_value = &ctx->params->raw.output_min_coordinate.z; 1125 ui->scale_bars[0][SB_AXIAL].max_value = &ctx->params->raw.output_max_coordinate.z; 1126 1127 ui->scale_bars[0][SB_LATERAL].scroll_both = 1; 1128 ui->scale_bars[0][SB_AXIAL].scroll_both = 0; 1129 1130 ui->scale_bars[0][SB_LATERAL].zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 1131 ui->scale_bars[0][SB_AXIAL].zoom_starting_point = (v2){.x = F32_INFINITY, .y = F32_INFINITY}; 1132 1133 } 1134 1135 static void 1136 draw_ui(BeamformerCtx *ctx, BeamformerInput *input, BeamformFrame *frame_to_draw) 1137 { 1138 BeamformerUI *ui = ctx->ui; 1139 1140 /* TODO(rnp): we need an ALLOC_END flag so that we can have permanent storage 1141 * or we need a sub arena for the save point stack */ 1142 //end_temp_arena(ui->frame_temporary_arena); 1143 //ui->frame_temporary_arena = begin_temp_arena(&ui->arena_for_frame); 1144 1145 /* NOTE: process interactions first because the user interacted with 1146 * the ui that was presented last frame */ 1147 ui_interact(ctx, input); 1148 1149 BeginDrawing(); 1150 ClearBackground(colour_from_normalized(BG_COLOUR)); 1151 1152 v2 mouse = input->mouse; 1153 Rect wr = {.size = {.w = (f32)ctx->window_size.w, .h = (f32)ctx->window_size.h}}; 1154 Rect lr = wr, rr = wr; 1155 lr.size.w = INFO_COLUMN_WIDTH; 1156 rr.size.w = wr.size.w - lr.size.w; 1157 rr.pos.x = lr.pos.x + lr.size.w; 1158 1159 draw_settings_ui(ctx, lr, mouse); 1160 if (frame_to_draw->dim.w) 1161 draw_display_overlay(ctx, ui->arena_for_frame, mouse, rr, frame_to_draw); 1162 draw_debug_overlay(ctx, ui->arena_for_frame, lr); 1163 EndDrawing(); 1164 }