ogl_beamforming

Ultrasound Beamforming Implemented with OpenGL
git clone anongit@rnpnr.xyz:ogl_beamforming.git
Log | Files | Refs | Feed | Submodules | LICENSE

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 }