colourpicker.c (42454B)
1 /* See LICENSE for copyright details */ 2 #include <raylib.h> 3 #include <rlgl.h> 4 5 #include "util.c" 6 7 global f32 dt_for_frame; 8 9 #ifdef _DEBUG 10 enum clock_counts { 11 CC_WHOLE_RUN, 12 CC_DO_PICKER, 13 CC_DO_SLIDER, 14 CC_UPPER, 15 CC_LOWER, 16 CC_TEMP, 17 CC_LAST 18 }; 19 global struct { 20 s64 cpu_cycles[CC_LAST]; 21 s64 total_cycles[CC_LAST]; 22 s64 hit_count[CC_LAST]; 23 } g_debug_clock_counts; 24 25 #define BEGIN_CYCLE_COUNT(cc_name) \ 26 g_debug_clock_counts.cpu_cycles[cc_name] = rdtsc(); \ 27 g_debug_clock_counts.hit_count[cc_name]++ 28 29 #define END_CYCLE_COUNT(cc_name) \ 30 g_debug_clock_counts.cpu_cycles[cc_name] = rdtsc() - g_debug_clock_counts.cpu_cycles[cc_name]; \ 31 g_debug_clock_counts.total_cycles[cc_name] += g_debug_clock_counts.cpu_cycles[cc_name] 32 33 #else 34 #define BEGIN_CYCLE_COUNT(a) 35 #define END_CYCLE_COUNT(a) 36 #endif 37 38 function void 39 mem_move(u8 *dest, u8 *src, sz n) 40 { 41 if (dest < src) while (n) { *dest++ = *src++; n--; } 42 else while (n) { n--; dest[n] = src[n]; } 43 } 44 45 function b32 46 point_in_rect(v2 p, Rect r) 47 { 48 v2 end = add_v2(r.pos, r.size); 49 b32 result = BETWEEN(p.x, r.pos.x, end.x) & BETWEEN(p.y, r.pos.y, end.y); 50 return result; 51 } 52 53 function f32 54 move_towards_f32(f32 current, f32 target, f32 delta) 55 { 56 if (target < current) { 57 current -= delta; 58 if (current < target) 59 current = target; 60 } else { 61 current += delta; 62 if (current > target) 63 current = target; 64 } 65 return current; 66 } 67 68 function Color 69 fade(Color a, f32 alpha) 70 { 71 a.a = (u8)((f32)a.a * alpha); 72 return a; 73 } 74 75 function f32 76 lerp(f32 a, f32 b, f32 t) 77 { 78 return a + t * (b - a); 79 } 80 81 function v4 82 lerp_v4(v4 a, v4 b, f32 t) 83 { 84 v4 result; 85 result.x = a.x + t * (b.x - a.x); 86 result.y = a.y + t * (b.y - a.y); 87 result.z = a.z + t * (b.z - a.z); 88 result.w = a.w + t * (b.w - a.w); 89 return result; 90 } 91 92 function v2 93 measure_text(Font font, str8 text) 94 { 95 v2 result = {.y = font.baseSize}; 96 97 for (sz i = 0; i < text.len; i++) { 98 /* NOTE: assumes font glyphs are ordered (they are in our embedded fonts) */ 99 s32 idx = (s32)text.data[i] - 32; 100 result.x += font.glyphs[idx].advanceX; 101 if (font.glyphs[idx].advanceX == 0) 102 result.x += (font.recs[idx].width + font.glyphs[idx].offsetX); 103 } 104 105 return result; 106 } 107 108 function void 109 draw_text(Font font, str8 text, v2 pos, Color colour) 110 { 111 for (sz i = 0; i < text.len; i++) { 112 /* NOTE: assumes font glyphs are ordered (they are in our embedded fonts) */ 113 s32 idx = text.data[i] - 32; 114 Rectangle dst = { 115 pos.x + font.glyphs[idx].offsetX - font.glyphPadding, 116 pos.y + font.glyphs[idx].offsetY - font.glyphPadding, 117 font.recs[idx].width + 2.0f * font.glyphPadding, 118 font.recs[idx].height + 2.0f * font.glyphPadding 119 }; 120 Rectangle src = { 121 font.recs[idx].x - font.glyphPadding, 122 font.recs[idx].y - font.glyphPadding, 123 font.recs[idx].width + 2.0f * font.glyphPadding, 124 font.recs[idx].height + 2.0f * font.glyphPadding 125 }; 126 DrawTexturePro(font.texture, src, dst, (Vector2){0}, 0, colour); 127 128 pos.x += font.glyphs[idx].advanceX; 129 if (font.glyphs[idx].advanceX == 0) 130 pos.x += font.recs[idx].width; 131 } 132 } 133 134 function v2 135 left_align_text_in_rect(Rect r, str8 text, Font font) 136 { 137 v2 ts = measure_text(font, text); 138 v2 delta = { .h = r.size.h - ts.h }; 139 return (v2) { .x = r.pos.x, .y = r.pos.y + 0.5 * delta.h, }; 140 } 141 142 function Rect 143 scale_rect_centered(Rect r, v2 scale) 144 { 145 Rect or = r; 146 r.size.w *= scale.x; 147 r.size.h *= scale.y; 148 r.pos.x += (or.size.w - r.size.w) / 2; 149 r.pos.y += (or.size.h - r.size.h) / 2; 150 return r; 151 } 152 153 function v2 154 center_align_text_in_rect(Rect r, str8 text, Font font) 155 { 156 v2 ts = measure_text(font, text); 157 v2 delta = { .w = r.size.w - ts.w, .h = r.size.h - ts.h }; 158 return (v2) { 159 .x = r.pos.x + 0.5 * delta.w, 160 .y = r.pos.y + 0.5 * delta.h, 161 }; 162 } 163 164 function Rect 165 cut_rect_middle(Rect r, f32 left, f32 right) 166 { 167 ASSERT(left <= right); 168 r.pos.x += r.size.w * left; 169 r.size.w = r.size.w * (right - left); 170 return r; 171 } 172 173 function Rect 174 cut_rect_left(Rect r, f32 fraction) 175 { 176 r.size.w *= fraction; 177 return r; 178 } 179 180 function Rect 181 cut_rect_right(Rect r, f32 fraction) 182 { 183 r.pos.x += fraction * r.size.w; 184 r.size.w *= (1 - fraction); 185 return r; 186 } 187 188 function b32 189 hover_rect(v2 mouse, Rect rect, f32 *hover_t) 190 { 191 b32 result = point_in_rect(mouse, rect); 192 if (result) *hover_t += HOVER_SPEED * dt_for_frame; 193 else *hover_t -= HOVER_SPEED * dt_for_frame; 194 CLAMP01(*hover_t); 195 return result; 196 } 197 198 function b32 199 hover_var(ColourPickerCtx *ctx, v2 mouse, Rect rect, Variable *var) 200 { 201 b32 result = 0; 202 if (ctx->interaction.kind != InteractionKind_Drag || var == ctx->interaction.active) { 203 result = hover_rect(mouse, rect, &var->parameter); 204 if (result) { 205 ctx->interaction.next_hot = var; 206 ctx->interaction.hot_rect = rect; 207 } 208 } 209 return result; 210 } 211 212 function void 213 draw_cardinal_triangle(v2 midpoint, v2 size, v2 scale, enum cardinal_direction direction, 214 Color colour) 215 { 216 v2 t1, t2; 217 switch (direction) { 218 case NORTH: 219 t1.x = midpoint.x - scale.x * size.x; 220 t1.y = midpoint.y + scale.y * size.y; 221 t2.x = midpoint.x + scale.x * size.x; 222 t2.y = midpoint.y + scale.y * size.y; 223 break; 224 case EAST: 225 t1.x = midpoint.x - scale.x * size.y; 226 t1.y = midpoint.y - scale.y * size.x; 227 t2.x = midpoint.x - scale.x * size.y; 228 t2.y = midpoint.y + scale.y * size.x; 229 break; 230 case SOUTH: 231 t1.x = midpoint.x + scale.x * size.x; 232 t1.y = midpoint.y - scale.y * size.y; 233 t2.x = midpoint.x - scale.x * size.x; 234 t2.y = midpoint.y - scale.y * size.y; 235 break; 236 case WEST: 237 t1.x = midpoint.x + scale.x * size.y; 238 t1.y = midpoint.y + scale.y * size.x; 239 t2.x = midpoint.x + scale.x * size.y; 240 t2.y = midpoint.y - scale.y * size.x; 241 break; 242 default: ASSERT(0); return; 243 } 244 DrawTriangle(midpoint.rv, t1.rv, t2.rv, colour); 245 246 #if 0 247 DrawCircleV(midpoint.rv, 6, RED); 248 DrawCircleV(t1.rv, 6, BLUE); 249 DrawCircleV(t2.rv, 6, GREEN); 250 #endif 251 } 252 253 function v4 254 convert_colour(v4 colour, ColourKind current, ColourKind target) 255 { 256 v4 result = colour; 257 switch (current) { 258 case ColourKind_RGB: { 259 switch (target) { 260 case ColourKind_RGB: break; 261 case ColourKind_HSV: result = rgb_to_hsv(colour); break; 262 InvalidDefaultCase; 263 } 264 } break; 265 case ColourKind_HSV: { 266 switch (target) { 267 case ColourKind_RGB: result = hsv_to_rgb(colour); break; 268 case ColourKind_HSV: break; 269 InvalidDefaultCase; 270 } 271 } break; 272 InvalidDefaultCase; 273 } 274 return result; 275 } 276 277 function v4 278 get_formatted_colour(ColourPickerCtx *ctx, ColourKind format) 279 { 280 v4 result = convert_colour(ctx->colour, ctx->stored_colour_kind, format); 281 return result; 282 } 283 284 function void 285 store_formatted_colour(ColourPickerCtx *ctx, v4 colour, ColourKind format) 286 { 287 ctx->colour = convert_colour(colour, format, ctx->stored_colour_kind); 288 289 /* TODO(rnp): what exactly was going on here? shouldn't we always redraw the texture ? */ 290 if (ctx->stored_colour_kind == ColourKind_HSV) 291 ctx->flags |= ColourPickerFlag_RefillTexture; 292 } 293 294 function void 295 get_slider_subrects(Rect r, Rect *label, Rect *slider, Rect *value) 296 { 297 if (label) *label = cut_rect_left(r, 0.08); 298 if (value) *value = cut_rect_right(r, 0.83); 299 if (slider) { 300 *slider = cut_rect_middle(r, 0.1, 0.79); 301 slider->size.h *= 0.7; 302 slider->pos.y += r.size.h * 0.15; 303 } 304 } 305 306 function void 307 parse_and_store_text_input(ColourPickerCtx *ctx) 308 { 309 str8 input = {.len = ctx->text_input_state.count, .data = ctx->text_input_state.buf}; 310 v4 new_colour = {0}; 311 ColourKind new_mode = ColourKind_Last; 312 if (ctx->text_input_state.idx == -1) { 313 return; 314 } else if (ctx->text_input_state.idx == INPUT_HEX) { 315 new_colour = normalize_colour(parse_hex_u32(input)); 316 new_mode = ColourKind_RGB; 317 } else { 318 new_mode = ctx->stored_colour_kind; 319 new_colour = ctx->colour; 320 f32 fv = parse_f64(input); 321 CLAMP01(fv); 322 switch(ctx->text_input_state.idx) { 323 case INPUT_R: new_colour.r = fv; break; 324 case INPUT_G: new_colour.g = fv; break; 325 case INPUT_B: new_colour.b = fv; break; 326 case INPUT_A: new_colour.a = fv; break; 327 default: break; 328 } 329 } 330 331 if (new_mode != ColourKind_Last) 332 store_formatted_colour(ctx, new_colour, new_mode); 333 } 334 335 function void 336 set_text_input_idx(ColourPickerCtx *ctx, enum input_indices idx, Rect r, v2 mouse) 337 { 338 if (ctx->text_input_state.idx != (s32)idx) 339 parse_and_store_text_input(ctx); 340 341 Stream in = {.data = ctx->text_input_state.buf, .cap = countof(ctx->text_input_state.buf)}; 342 if (idx == INPUT_HEX) { 343 stream_append_colour(&in, rl_colour_from_normalized(get_formatted_colour(ctx, ColourKind_RGB))); 344 } else { 345 f32 fv = 0; 346 switch (idx) { 347 case INPUT_R: fv = ctx->colour.r; break; 348 case INPUT_G: fv = ctx->colour.g; break; 349 case INPUT_B: fv = ctx->colour.b; break; 350 case INPUT_A: fv = ctx->colour.a; break; 351 default: break; 352 } 353 stream_append_f64(&in, fv, 100); 354 } 355 ctx->text_input_state.count = in.widx; 356 357 ctx->text_input_state.idx = idx; 358 ctx->text_input_state.cursor = -1; 359 CLAMP(ctx->text_input_state.idx, -1, INPUT_A); 360 if (ctx->text_input_state.idx == -1) 361 return; 362 363 ASSERT(CheckCollisionPointRec(mouse.rv, r.rr)); 364 ctx->text_input_state.cursor_hover_p = (mouse.x - r.pos.x) / r.size.w; 365 CLAMP01(ctx->text_input_state.cursor_hover_p); 366 } 367 368 function void 369 do_text_input(ColourPickerCtx *ctx, Rect r, Color colour, s32 max_disp_chars) 370 { 371 TextInputState *is = &ctx->text_input_state; 372 v2 ts = measure_text(ctx->font, (str8){.len = is->count, .data = is->buf}); 373 v2 pos = {.x = r.pos.x, .y = r.pos.y + (r.size.y - ts.y) / 2}; 374 375 s32 buf_delta = is->count - max_disp_chars; 376 if (buf_delta < 0) buf_delta = 0; 377 str8 buf = {.len = is->count - buf_delta, .data = is->buf + buf_delta}; 378 { 379 /* NOTE: drop a char if the subtext still doesn't fit */ 380 v2 nts = measure_text(ctx->font, buf); 381 if (nts.w > 0.96 * r.size.w) { 382 buf.data++; 383 buf.len--; 384 } 385 } 386 draw_text(ctx->font, buf, pos, colour); 387 388 is->cursor_t = move_towards_f32(is->cursor_t, is->cursor_t_target, 1.5 * dt_for_frame); 389 if (is->cursor_t == is->cursor_t_target) { 390 if (is->cursor_t_target == 0) is->cursor_t_target = 1; 391 else is->cursor_t_target = 0; 392 } 393 394 v4 bg = ctx->cursor_colour; 395 bg.a = 0; 396 Color cursor_colour = rl_colour_from_normalized(lerp_v4(bg, ctx->cursor_colour, is->cursor_t)); 397 398 /* NOTE: guess a cursor position */ 399 if (is->cursor == -1) { 400 /* NOTE: extra offset to help with putting a cursor at idx 0 */ 401 #define TEXT_HALF_CHAR_WIDTH 10 402 f32 x_off = TEXT_HALF_CHAR_WIDTH, x_bounds = r.size.w * is->cursor_hover_p; 403 s32 i; 404 for (i = 0; i < is->count && x_off < x_bounds; i++) { 405 /* NOTE: assumes font glyphs are ordered */ 406 s32 idx = is->buf[i] - 32; 407 x_off += ctx->font.glyphs[idx].advanceX; 408 if (ctx->font.glyphs[idx].advanceX == 0) 409 x_off += ctx->font.recs[idx].width; 410 } 411 is->cursor = i; 412 } 413 414 buf.len = is->cursor - buf_delta; 415 v2 sts = measure_text(ctx->font, buf); 416 f32 cursor_x = r.pos.x + sts.x; 417 f32 cursor_width; 418 if (is->cursor == is->count) cursor_width = MIN(ctx->window_size.w * 0.03, 20); 419 else cursor_width = MIN(ctx->window_size.w * 0.01, 6); 420 421 Rect cursor_r = { 422 .pos = {.x = cursor_x, .y = pos.y}, 423 .size = {.w = cursor_width, .h = ts.h}, 424 }; 425 426 DrawRectangleRec(cursor_r.rr, cursor_colour); 427 428 /* NOTE: handle multiple input keys on a single frame */ 429 for (s32 key = GetCharPressed(); 430 is->count < (s32)countof(is->buf) && key > 0; 431 key = GetCharPressed()) 432 { 433 mem_move(is->buf + is->cursor + 1, 434 is->buf + is->cursor, 435 is->count - is->cursor); 436 437 is->buf[is->cursor++] = key; 438 is->count++; 439 } 440 441 is->cursor -= (IsKeyPressed(KEY_LEFT) || IsKeyPressedRepeat(KEY_LEFT)) && is->cursor > 0; 442 is->cursor += (IsKeyPressed(KEY_RIGHT) || IsKeyPressedRepeat(KEY_RIGHT)) && is->cursor < is->count; 443 444 if ((IsKeyPressed(KEY_BACKSPACE) || IsKeyPressedRepeat(KEY_BACKSPACE)) && is->cursor > 0) { 445 is->cursor--; 446 if (is->cursor < countof(is->buf) - 1) { 447 mem_move(is->buf + is->cursor, 448 is->buf + is->cursor + 1, 449 is->count - is->cursor - 1); 450 } 451 is->count--; 452 } 453 454 if ((IsKeyPressed(KEY_DELETE) || IsKeyPressedRepeat(KEY_DELETE)) && is->cursor < is->count) { 455 mem_move(is->buf + is->cursor, 456 is->buf + is->cursor + 1, 457 is->count - is->cursor - 1); 458 is->count--; 459 } 460 461 if (IsKeyPressed(KEY_ENTER)) { 462 parse_and_store_text_input(ctx); 463 is->idx = -1; 464 } 465 } 466 467 function s32 468 do_button(ButtonState *btn, v2 mouse, Rect r, f32 hover_speed) 469 { 470 b32 hovered = CheckCollisionPointRec(mouse.rv, r.rr); 471 s32 pressed_mask = 0; 472 pressed_mask |= MOUSE_LEFT * (hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)); 473 pressed_mask |= MOUSE_RIGHT * (hovered && IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)); 474 475 if (hovered) btn->hover_t += hover_speed * dt_for_frame; 476 else btn->hover_t -= hover_speed * dt_for_frame; 477 CLAMP01(btn->hover_t); 478 479 return pressed_mask; 480 } 481 482 function s32 483 do_rect_button(ButtonState *btn, v2 mouse, Rect r, Color bg, f32 hover_speed, f32 scale_target, f32 fade_t) 484 { 485 s32 pressed_mask = do_button(btn, mouse, r, hover_speed); 486 487 f32 param = lerp(1, scale_target, btn->hover_t); 488 v2 bscale = (v2){ 489 .x = param + RECT_BTN_BORDER_WIDTH / r.size.w, 490 .y = param + RECT_BTN_BORDER_WIDTH / r.size.h, 491 }; 492 Rect sr = scale_rect_centered(r, (v2){.x = param, .y = param}); 493 Rect sb = scale_rect_centered(r, bscale); 494 DrawRectangleRounded(sb.rr, SELECTOR_ROUNDNESS, 0, fade(SELECTOR_BORDER_COLOUR, fade_t)); 495 DrawRectangleRounded(sr.rr, SELECTOR_ROUNDNESS, 0, fade(bg, fade_t)); 496 497 return pressed_mask; 498 } 499 500 function s32 501 do_text_button(ColourPickerCtx *ctx, ButtonState *btn, v2 mouse, Rect r, str8 text, v4 fg, Color bg) 502 { 503 s32 pressed_mask = do_rect_button(btn, mouse, r, bg, HOVER_SPEED, 1, 1); 504 505 v2 tpos = center_align_text_in_rect(r, text, ctx->font); 506 v2 spos = {.x = tpos.x + 1.75, .y = tpos.y + 2}; 507 v4 colour = lerp_v4(fg, ctx->hover_colour, btn->hover_t); 508 509 draw_text(ctx->font, text, spos, fade(BLACK, 0.8)); 510 draw_text(ctx->font, text, tpos, rl_colour_from_normalized(colour)); 511 512 return pressed_mask; 513 } 514 515 function void 516 do_slider(ColourPickerCtx *ctx, Rect r, s32 label_idx, v2 relative_mouse, str8 name) 517 { 518 Rect lr, sr, vr; 519 get_slider_subrects(r, &lr, &sr, &vr); 520 521 b32 hovering = CheckCollisionPointRec(relative_mouse.rv, sr.rr); 522 523 if (hovering && ctx->held_idx == -1) 524 ctx->held_idx = label_idx; 525 526 if (ctx->held_idx != -1) { 527 f32 current = ctx->colour.E[ctx->held_idx]; 528 f32 wheel = GetMouseWheelMove(); 529 if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) 530 current = (relative_mouse.x - sr.pos.x) / sr.size.w; 531 current += wheel / 255; 532 CLAMP01(current); 533 ctx->colour.E[ctx->held_idx] = current; 534 ctx->flags |= ColourPickerFlag_RefillTexture; 535 } 536 537 if (IsMouseButtonUp(MOUSE_BUTTON_LEFT)) 538 ctx->held_idx = -1; 539 540 f32 current = ctx->colour.E[label_idx]; 541 542 { 543 f32 scale_delta = (SLIDER_SCALE_TARGET - 1.0) * SLIDER_SCALE_SPEED * dt_for_frame; 544 b32 should_scale = (ctx->held_idx == -1 && hovering) || 545 (ctx->held_idx != -1 && label_idx == ctx->held_idx); 546 f32 scale = ctx->ss.scale_t[label_idx]; 547 scale = move_towards_f32(scale, should_scale? SLIDER_SCALE_TARGET: 1.0, 548 scale_delta); 549 ctx->ss.scale_t[label_idx] = scale; 550 551 v2 tri_scale = {.x = scale, .y = scale}; 552 v2 tri_mid = {.x = sr.pos.x + current * sr.size.w, .y = sr.pos.y}; 553 draw_cardinal_triangle(tri_mid, SLIDER_TRI_SIZE, tri_scale, SOUTH, ctx->fg); 554 tri_mid.y += sr.size.h; 555 draw_cardinal_triangle(tri_mid, SLIDER_TRI_SIZE, tri_scale, NORTH, ctx->fg); 556 } 557 558 { 559 SliderState *s = &ctx->ss; 560 b32 collides = CheckCollisionPointRec(relative_mouse.rv, vr.rr); 561 if (collides && ctx->text_input_state.idx != (label_idx + 1)) { 562 s->colour_t[label_idx] += HOVER_SPEED * dt_for_frame; 563 } else { 564 s->colour_t[label_idx] -= HOVER_SPEED * dt_for_frame; 565 } 566 CLAMP01(s->colour_t[label_idx]); 567 568 if (!collides && ctx->text_input_state.idx == (label_idx + 1) && 569 IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { 570 set_text_input_idx(ctx, -1, vr, relative_mouse); 571 current = ctx->colour.E[label_idx]; 572 } 573 574 v4 colour = lerp_v4(normalize_colour(pack_rl_colour(ctx->fg)), 575 ctx->hover_colour, s->colour_t[label_idx]); 576 Color colour_rl = rl_colour_from_normalized(colour); 577 578 if (collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 579 set_text_input_idx(ctx, label_idx + 1, vr, relative_mouse); 580 581 if (ctx->text_input_state.idx != (label_idx + 1)) { 582 u8 vbuf[4]; 583 Stream vstream = {.data = vbuf, .cap = countof(vbuf)}; 584 stream_append_f64(&vstream, current, 100); 585 str8 value = {.len = vstream.widx, .data = vbuf}; 586 draw_text(ctx->font, value, left_align_text_in_rect(vr, value, ctx->font), colour_rl); 587 } else { 588 do_text_input(ctx, vr, colour_rl, 4); 589 } 590 } 591 draw_text(ctx->font, name, center_align_text_in_rect(lr, name, ctx->font), ctx->fg); 592 } 593 594 function void 595 do_status_bar(ColourPickerCtx *ctx, Rect r, v2 relative_mouse) 596 { 597 Rect hex_r = cut_rect_left(r, 0.5); 598 Rect mode_r; 599 get_slider_subrects(r, 0, 0, &mode_r); 600 601 str8 mode_txt = str8(""); 602 switch (ctx->stored_colour_kind) { 603 case ColourKind_RGB: mode_txt = str8("RGB"); break; 604 case ColourKind_HSV: mode_txt = str8("HSV"); break; 605 InvalidDefaultCase; 606 } 607 608 v2 mode_ts = measure_text(ctx->font, mode_txt); 609 mode_r.pos.y += (mode_r.size.h - mode_ts.h) / 2; 610 mode_r.size.w = mode_ts.w; 611 612 hover_var(ctx, relative_mouse, mode_r, &ctx->slider_mode_state.colour_kind_cycler); 613 614 u8 hbuf[8]; 615 Stream hstream = {.data = hbuf, .cap = countof(hbuf)}; 616 stream_append_colour(&hstream, rl_colour_from_normalized(get_formatted_colour(ctx, ColourKind_RGB))); 617 str8 hex = {.len = hstream.widx, .data = hbuf}; 618 str8 label = str8("RGB: "); 619 620 v2 label_size = measure_text(ctx->font, label); 621 v2 hex_size = measure_text(ctx->font, hex); 622 623 f32 extra_input_scale = 1.07; 624 hex_r.size.w = extra_input_scale * (label_size.w + hex_size.w); 625 626 Rect label_r = cut_rect_left(hex_r, label_size.x / hex_r.size.w); 627 hex_r = cut_rect_right(hex_r, label_size.x / hex_r.size.w); 628 629 s32 hex_collides = CheckCollisionPointRec(relative_mouse.rv, hex_r.rr); 630 631 if (!hex_collides && ctx->text_input_state.idx == INPUT_HEX && 632 IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { 633 set_text_input_idx(ctx, -1, hex_r, relative_mouse); 634 hstream.widx = 0; 635 stream_append_colour(&hstream, rl_colour_from_normalized(get_formatted_colour(ctx, ColourKind_RGB))); 636 hex.len = hstream.widx; 637 } 638 639 if (hex_collides && IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 640 set_text_input_idx(ctx, INPUT_HEX, hex_r, relative_mouse); 641 642 if (hex_collides && ctx->text_input_state.idx != INPUT_HEX) 643 ctx->sbs.hex_hover_t += HOVER_SPEED * dt_for_frame; 644 else 645 ctx->sbs.hex_hover_t -= HOVER_SPEED * dt_for_frame; 646 CLAMP01(ctx->sbs.hex_hover_t); 647 648 v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); 649 v4 hex_colour = lerp_v4(fg, ctx->hover_colour, ctx->sbs.hex_hover_t); 650 v4 mode_colour = lerp_v4(fg, ctx->hover_colour, ctx->slider_mode_state.colour_kind_cycler.parameter); 651 652 draw_text(ctx->font, label, left_align_text_in_rect(label_r, label, ctx->font), ctx->fg); 653 654 Color hex_colour_rl = rl_colour_from_normalized(hex_colour); 655 if (ctx->text_input_state.idx != INPUT_HEX) { 656 draw_text(ctx->font, hex, left_align_text_in_rect(hex_r, hex, ctx->font), hex_colour_rl); 657 } else { 658 do_text_input(ctx, hex_r, hex_colour_rl, 8); 659 } 660 661 draw_text(ctx->font, mode_txt, mode_r.pos, rl_colour_from_normalized(mode_colour)); 662 } 663 664 function void 665 do_colour_stack(ColourPickerCtx *ctx, Rect sa) 666 { 667 ColourStackState *css = &ctx->colour_stack; 668 669 /* NOTE: Small adjusment to align with mode text. TODO: Cleanup? */ 670 sa = scale_rect_centered(sa, (v2){.x = 1, .y = 0.98}); 671 sa.pos.y += 0.02 * sa.size.h; 672 673 Rect r = sa; 674 r.size.h *= 1.0 / (countof(css->items) + 3); 675 r.size.w *= 0.75; 676 r.pos.x += (sa.size.w - r.size.w) * 0.5; 677 678 f32 y_pos_delta = r.size.h * 1.2; 679 r.pos.y -= y_pos_delta * css->y_off_t; 680 681 /* NOTE: Stack is moving up; draw last top item as it moves up and fades out */ 682 if (css->fade_param) { 683 ButtonState btn; 684 do_rect_button(&btn, ctx->mouse_pos, r, rl_colour_from_normalized(css->last), 685 0, 1, css->fade_param); 686 r.pos.y += y_pos_delta; 687 } 688 689 f32 stack_scale_target = (f32)(countof(css->items) + 1) / countof(css->items); 690 691 f32 fade_scale[countof(css->items)] = { [countof(css->items) - 1] = 1 }; 692 for (u32 i = 0; i < countof(css->items); i++) { 693 s32 cidx = (css->widx + i) % countof(css->items); 694 Color bg = rl_colour_from_normalized(css->items[cidx]); 695 b32 pressed = do_rect_button(css->buttons + cidx, ctx->mouse_pos, r, bg, 696 BUTTON_HOVER_SPEED, stack_scale_target, 697 1 - css->fade_param * fade_scale[i]); 698 if (pressed) { 699 v4 hsv = rgb_to_hsv(css->items[cidx]); 700 store_formatted_colour(ctx, hsv, ColourKind_HSV); 701 if (ctx->mode == CPM_PICKER) { 702 ctx->pms.base_hue = hsv.x; 703 ctx->pms.fractional_hue = 0; 704 } 705 } 706 r.pos.y += y_pos_delta; 707 } 708 709 css->fade_param -= BUTTON_HOVER_SPEED * dt_for_frame; 710 css->y_off_t += BUTTON_HOVER_SPEED * dt_for_frame; 711 if (css->fade_param < 0) { 712 css->fade_param = 0; 713 css->y_off_t = 0; 714 } 715 716 r.pos.y = sa.pos.y + sa.size.h - r.size.h; 717 r.pos.x += r.size.w * 0.1; 718 r.size.w *= 0.8; 719 720 b32 push_pressed = do_button(&css->tri_btn, ctx->mouse_pos, r, BUTTON_HOVER_SPEED); 721 f32 param = css->tri_btn.hover_t; 722 v2 tri_size = {.x = 0.25 * r.size.w, .y = 0.5 * r.size.h}; 723 v2 tri_scale = {.x = 1 - 0.5 * param, .y = 1 + 0.3 * param}; 724 v2 tri_mid = {.x = r.pos.x + 0.5 * r.size.w, .y = r.pos.y - 0.3 * r.size.h * param}; 725 draw_cardinal_triangle(tri_mid, tri_size, tri_scale, NORTH, ctx->fg); 726 727 if (push_pressed) { 728 css->fade_param = 1.0; 729 css->last = css->items[css->widx]; 730 css->items[css->widx++] = get_formatted_colour(ctx, ColourKind_RGB); 731 if (css->widx == countof(css->items)) 732 css->widx = 0; 733 } 734 } 735 736 function void 737 do_colour_selector(ColourPickerCtx *ctx, Rect r) 738 { 739 Color colour = rl_colour_from_normalized(get_formatted_colour(ctx, ColourKind_RGB)); 740 Color pcolour = rl_colour_from_normalized(ctx->previous_colour); 741 742 Rect cs[2] = {cut_rect_left(r, 0.5), cut_rect_right(r, 0.5)}; 743 DrawRectangleRec(cs[0].rr, pcolour); 744 DrawRectangleRec(cs[1].rr, colour); 745 746 v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); 747 str8 labels[2] = {str8("Revert"), str8("Apply")}; 748 749 s32 pressed_idx = -1; 750 for (u32 i = 0; i < countof(cs); i++) { 751 if (CheckCollisionPointRec(ctx->mouse_pos.rv, cs[i].rr) && ctx->held_idx == -1) { 752 ctx->selection_hover_t[i] += HOVER_SPEED * dt_for_frame; 753 754 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 755 pressed_idx = i; 756 } else { 757 ctx->selection_hover_t[i] -= HOVER_SPEED * dt_for_frame; 758 } 759 760 CLAMP01(ctx->selection_hover_t[i]); 761 762 v4 colour = lerp_v4(fg, ctx->hover_colour, ctx->selection_hover_t[i]); 763 764 v2 fpos = center_align_text_in_rect(cs[i], labels[i], ctx->font); 765 v2 pos = fpos; 766 pos.x += 1.75; 767 pos.y += 2; 768 draw_text(ctx->font, labels[i], pos, fade(BLACK, 0.8)); 769 draw_text(ctx->font, labels[i], fpos, rl_colour_from_normalized(colour)); 770 } 771 772 DrawRectangleRoundedLinesEx(r.rr, SELECTOR_ROUNDNESS, 0, 4 * SELECTOR_BORDER_WIDTH, ctx->bg); 773 DrawRectangleRoundedLinesEx(r.rr, SELECTOR_ROUNDNESS, 0, SELECTOR_BORDER_WIDTH, 774 SELECTOR_BORDER_COLOUR); 775 v2 start = cs[1].pos; 776 v2 end = cs[1].pos; 777 end.y += cs[1].size.h; 778 DrawLineEx(start.rv, end.rv, SELECTOR_BORDER_WIDTH, SELECTOR_BORDER_COLOUR); 779 780 if (pressed_idx == 0) store_formatted_colour(ctx, ctx->previous_colour, ColourKind_RGB); 781 else if (pressed_idx == 1) ctx->previous_colour = get_formatted_colour(ctx, ColourKind_RGB); 782 783 if (pressed_idx != -1) { 784 ctx->pms.base_hue = get_formatted_colour(ctx, ColourKind_HSV).x; 785 ctx->pms.fractional_hue = 0; 786 } 787 } 788 789 function void 790 do_slider_shader(ColourPickerCtx *ctx, Rect r, s32 colour_mode, f32 *regions, f32 *colours) 791 { 792 f32 border_thick = SLIDER_BORDER_WIDTH; 793 f32 radius = SLIDER_ROUNDNESS / 2; 794 /* NOTE: scale radius by rect width or height to adapt to window scaling */ 795 radius *= (r.size.w > r.size.h)? r.size.h : r.size.w; 796 797 BeginShaderMode(ctx->picker_shader); 798 rlEnableShader(ctx->picker_shader.id); 799 rlSetUniform(ctx->mode_id, &ctx->mode, RL_SHADER_UNIFORM_INT, 1); 800 rlSetUniform(ctx->radius_id, &radius, RL_SHADER_UNIFORM_FLOAT, 1); 801 rlSetUniform(ctx->border_thick_id, &border_thick, RL_SHADER_UNIFORM_FLOAT, 1); 802 rlSetUniform(ctx->colour_mode_id, &colour_mode, RL_SHADER_UNIFORM_INT, 1); 803 rlSetUniform(ctx->colours_id, colours, RL_SHADER_UNIFORM_VEC4, 3); 804 rlSetUniform(ctx->regions_id, regions, RL_SHADER_UNIFORM_VEC4, 4); 805 DrawRectanglePro(r.rr, (Vector2){0}, 0, BLACK); 806 EndShaderMode(); 807 } 808 809 function void 810 do_slider_mode(ColourPickerCtx *ctx, v2 relative_mouse) 811 { 812 BEGIN_CYCLE_COUNT(CC_DO_SLIDER); 813 814 Rect tr = { 815 .size = { 816 .w = ctx->picker_texture.texture.width, 817 .h = ctx->picker_texture.texture.height 818 } 819 }; 820 Rect sb = tr; 821 Rect ss = tr; 822 sb.size.h *= 0.1; 823 ss.size.h *= 0.15; 824 ss.pos.y += 1.2 * sb.size.h; 825 826 BeginTextureMode(ctx->slider_texture); 827 ClearBackground(ctx->bg); 828 829 do_status_bar(ctx, sb, relative_mouse); 830 831 Rect sr; 832 get_slider_subrects(ss, 0, &sr, 0); 833 f32 r_bound = sr.pos.x + sr.size.w; 834 f32 y_step = 1.525 * ss.size.h; 835 836 837 local_persist str8 colour_slider_labels[ColourKind_Last][4] = { 838 [ColourKind_RGB] = { str8("R"), str8("G"), str8("B"), str8("A") }, 839 [ColourKind_HSV] = { str8("H"), str8("S"), str8("V"), str8("A") }, 840 }; 841 for (s32 i = 0; i < 4; i++) { 842 str8 name = colour_slider_labels[ctx->stored_colour_kind][i]; 843 do_slider(ctx, ss, i, relative_mouse, name); 844 ss.pos.y += y_step; 845 } 846 847 f32 start_y = sr.pos.y - 0.5 * ss.size.h; 848 f32 end_y = start_y + sr.size.h; 849 f32 regions[16] = { 850 sr.pos.x, start_y + 3 * y_step, r_bound, end_y + 3 * y_step, 851 sr.pos.x, start_y + 2 * y_step, r_bound, end_y + 2 * y_step, 852 sr.pos.x, start_y + 1 * y_step, r_bound, end_y + 1 * y_step, 853 sr.pos.x, start_y + 0 * y_step, r_bound, end_y + 0 * y_step 854 }; 855 v4 colours[3] = {ctx->colour}; 856 do_slider_shader(ctx, tr, ctx->stored_colour_kind, regions, (f32 *)colours); 857 858 EndTextureMode(); 859 860 END_CYCLE_COUNT(CC_DO_SLIDER); 861 } 862 863 864 #define PM_LEFT 0 865 #define PM_MIDDLE 1 866 #define PM_RIGHT 2 867 868 function v4 869 do_vertical_slider(ColourPickerCtx *ctx, v2 test_pos, Rect r, s32 idx, 870 v4 bot_colour, v4 top_colour, v4 colour) 871 { 872 b32 hovering = CheckCollisionPointRec(test_pos.rv, r.rr); 873 874 if (hovering && ctx->held_idx == -1) { 875 colour.x -= GetMouseWheelMove() * (bot_colour.x - top_colour.x) / 36; 876 CLAMP(colour.x, top_colour.x, bot_colour.x); 877 } 878 879 if (hovering && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ctx->held_idx == -1) 880 ctx->held_idx = idx; 881 882 if (ctx->held_idx == idx) { 883 CLAMP(test_pos.y, r.pos.y, r.pos.y + r.size.h); 884 f32 new_t = (test_pos.y - r.pos.y) / r.size.h; 885 colour.x = top_colour.x + new_t * (bot_colour.x - top_colour.x); 886 } 887 888 f32 param = (colour.x - top_colour.x) / (bot_colour.x - top_colour.x); 889 { 890 b32 should_scale = (ctx->held_idx == -1 && hovering) || 891 (ctx->held_idx != -1 && ctx->held_idx == idx); 892 if (should_scale) ctx->pms.scale_t[idx] += SLIDER_SCALE_SPEED * dt_for_frame; 893 else ctx->pms.scale_t[idx] -= SLIDER_SCALE_SPEED * dt_for_frame; 894 CLAMP01(ctx->pms.scale_t[idx]); 895 896 f32 scale = lerp(1, SLIDER_SCALE_TARGET, ctx->pms.scale_t[idx]); 897 v2 tri_scale = {.x = scale, .y = scale}; 898 v2 tri_mid = {.x = r.pos.x, .y = r.pos.y + (param * r.size.h)}; 899 draw_cardinal_triangle(tri_mid, SLIDER_TRI_SIZE, tri_scale, EAST, ctx->fg); 900 tri_mid.x += r.size.w; 901 draw_cardinal_triangle(tri_mid, SLIDER_TRI_SIZE, tri_scale, WEST, ctx->fg); 902 } 903 904 return colour; 905 } 906 907 function void 908 do_picker_mode(ColourPickerCtx *ctx, v2 relative_mouse) 909 { 910 BEGIN_CYCLE_COUNT(CC_DO_PICKER); 911 912 v4 colour = get_formatted_colour(ctx, ColourKind_HSV); 913 colour.x = ctx->pms.base_hue + ctx->pms.fractional_hue; 914 915 Rect tr = { 916 .size = { 917 .w = ctx->picker_texture.texture.width, 918 .h = ctx->picker_texture.texture.height 919 } 920 }; 921 922 Rect hs1 = scale_rect_centered(cut_rect_left(tr, 0.2), (v2){.x = 0.5, .y = 0.95}); 923 Rect hs2 = scale_rect_centered(cut_rect_middle(tr, 0.2, 0.4), (v2){.x = 0.5, .y = 0.95}); 924 Rect sv = scale_rect_centered(cut_rect_right(tr, 0.4), (v2){.x = 1.0, .y = 0.95}); 925 926 BeginTextureMode(ctx->picker_texture); 927 ClearBackground(ctx->bg); 928 929 v4 hsv[3] = {colour, colour, colour}; 930 hsv[1].x = 0; 931 hsv[2].x = 1; 932 f32 last_hue = colour.x; 933 colour = do_vertical_slider(ctx, relative_mouse, hs1, PM_LEFT, hsv[2], hsv[1], colour); 934 if (colour.x != last_hue) 935 ctx->pms.base_hue = colour.x - ctx->pms.fractional_hue; 936 937 f32 fraction = 0.1; 938 if (ctx->pms.base_hue - 0.5 * fraction < 0) { 939 hsv[1].x = 0; 940 hsv[2].x = fraction; 941 } else if (ctx->pms.base_hue + 0.5 * fraction > 1) { 942 hsv[1].x = 1 - fraction; 943 hsv[2].x = 1; 944 } else { 945 hsv[1].x = ctx->pms.base_hue - 0.5 * fraction; 946 hsv[2].x = ctx->pms.base_hue + 0.5 * fraction; 947 } 948 949 colour = do_vertical_slider(ctx, relative_mouse, hs2, PM_MIDDLE, hsv[2], hsv[1], colour); 950 ctx->pms.fractional_hue = colour.x - ctx->pms.base_hue; 951 952 { 953 f32 regions[16] = { 954 hs1.pos.x, hs1.pos.y, hs1.pos.x + hs1.size.w, hs1.pos.y + hs1.size.h, 955 hs2.pos.x, hs2.pos.y, hs2.pos.x + hs2.size.w, hs2.pos.y + hs2.size.h, 956 sv.pos.x, sv.pos.y, sv.pos.x + sv.size.w, sv.pos.y + sv.size.h 957 }; 958 do_slider_shader(ctx, tr, ColourKind_HSV, regions, (f32 *)hsv); 959 } 960 961 b32 hovering = CheckCollisionPointRec(relative_mouse.rv, sv.rr); 962 if (hovering && IsMouseButtonDown(MOUSE_BUTTON_LEFT) && ctx->held_idx == -1) 963 ctx->held_idx = PM_RIGHT; 964 965 if (ctx->held_idx == PM_RIGHT) { 966 CLAMP(relative_mouse.x, sv.pos.x, sv.pos.x + sv.size.w); 967 CLAMP(relative_mouse.y, sv.pos.y, sv.pos.y + sv.size.h); 968 colour.y = (relative_mouse.x - sv.pos.x) / sv.size.w; 969 colour.z = (sv.pos.y + sv.size.h - relative_mouse.y) / sv.size.h; 970 } 971 972 f32 radius = 4; 973 v2 param = {.x = colour.y, .y = 1 - colour.z}; 974 { 975 b32 should_scale = (ctx->held_idx == -1 && hovering) || 976 (ctx->held_idx != -1 && ctx->held_idx == PM_RIGHT); 977 if (should_scale) ctx->pms.scale_t[PM_RIGHT] += SLIDER_SCALE_SPEED * dt_for_frame; 978 else ctx->pms.scale_t[PM_RIGHT] -= SLIDER_SCALE_SPEED * dt_for_frame; 979 CLAMP01(ctx->pms.scale_t[PM_RIGHT]); 980 981 f32 slider_scale = lerp(1, SLIDER_SCALE_TARGET, ctx->pms.scale_t[PM_RIGHT]); 982 f32 line_len = 8; 983 984 /* NOTE: North-East */ 985 v2 start = { 986 .x = sv.pos.x + (param.x * sv.size.w) + 0.5 * radius, 987 .y = sv.pos.y + (param.y * sv.size.h) + 0.5 * radius, 988 }; 989 v2 end = start; 990 end.x += line_len * slider_scale; 991 end.y += line_len * slider_scale; 992 DrawLineEx(start.rv, end.rv, 4, ctx->fg); 993 994 /* NOTE: North-West */ 995 start.x -= radius; 996 end = start; 997 end.x -= line_len * slider_scale; 998 end.y += line_len * slider_scale; 999 DrawLineEx(start.rv, end.rv, 4, ctx->fg); 1000 1001 /* NOTE: South-West */ 1002 start.y -= radius; 1003 end = start; 1004 end.x -= line_len * slider_scale; 1005 end.y -= line_len * slider_scale; 1006 DrawLineEx(start.rv, end.rv, 4, ctx->fg); 1007 1008 /* NOTE: South-East */ 1009 start.x += radius; 1010 end = start; 1011 end.x += line_len * slider_scale; 1012 end.y -= line_len * slider_scale; 1013 DrawLineEx(start.rv, end.rv, 4, ctx->fg); 1014 } 1015 1016 EndTextureMode(); 1017 1018 if (IsMouseButtonUp(MOUSE_BUTTON_LEFT)) 1019 ctx->held_idx = -1; 1020 1021 store_formatted_colour(ctx, colour, ColourKind_HSV); 1022 1023 END_CYCLE_COUNT(CC_DO_PICKER); 1024 } 1025 1026 1027 #ifdef _DEBUG 1028 #include <stdio.h> 1029 #endif 1030 function void 1031 debug_dump_info(ColourPickerCtx *ctx) 1032 { 1033 (void)ctx; 1034 #ifdef _DEBUG 1035 if (IsKeyPressed(KEY_F1)) 1036 ctx->flags ^= ColourPickerFlag_PrintDebug; 1037 1038 DrawFPS(20, 20); 1039 1040 local_persist char *fmts[CC_LAST] = { 1041 [CC_WHOLE_RUN] = "Whole Run: %7ld cyc | %2d h | %7d cyc/h\n", 1042 [CC_DO_PICKER] = "Picker Mode: %7ld cyc | %2d h | %7d cyc/h\n", 1043 [CC_DO_SLIDER] = "Slider Mode: %7ld cyc | %2d h | %7d cyc/h\n", 1044 [CC_UPPER] = "Upper: %7ld cyc | %2d h | %7d cyc/h\n", 1045 [CC_LOWER] = "Lower: %7ld cyc | %2d h | %7d cyc/h\n", 1046 [CC_TEMP] = "Temp: %7ld cyc | %2d h | %7d cyc/h\n", 1047 }; 1048 1049 s64 cycs[CC_LAST]; 1050 s64 hits[CC_LAST]; 1051 1052 for (u32 i = 0; i < CC_LAST; i++) { 1053 cycs[i] = g_debug_clock_counts.total_cycles[i]; 1054 hits[i] = g_debug_clock_counts.hit_count[i]; 1055 g_debug_clock_counts.hit_count[i] = 0; 1056 g_debug_clock_counts.total_cycles[i] = 0; 1057 } 1058 1059 if (!(ctx->flags & ColourPickerFlag_PrintDebug)) 1060 return; 1061 1062 local_persist u32 fcount; 1063 fcount++; 1064 if (fcount != 60) 1065 return; 1066 fcount = 0; 1067 1068 printf("Begin Cycle Dump\n"); 1069 for (u32 i = 0; i < CC_LAST; i++) { 1070 if (hits[i] == 0) 1071 continue; 1072 printf(fmts[i], cycs[i], hits[i], cycs[i]/hits[i]); 1073 } 1074 #endif 1075 } 1076 1077 function void 1078 colour_picker_begin_interact(ColourPickerCtx *ctx, b32 scroll) 1079 { 1080 InteractionState *is = &ctx->interaction; 1081 if (is->hot) { 1082 switch (is->hot->kind) { 1083 case VariableKind_Cycler: { 1084 if (scroll) is->kind = InteractionKind_Scroll; 1085 else is->kind = InteractionKind_Set; 1086 } break; 1087 InvalidDefaultCase; 1088 } 1089 } 1090 1091 if (is->kind != InteractionKind_None) { 1092 is->active = is->hot; 1093 is->rect = is->hot_rect; 1094 } 1095 } 1096 1097 function void 1098 colour_picker_end_interact(ColourPickerCtx *ctx, b32 mouse_left_pressed, b32 mouse_right_pressed) 1099 { 1100 InteractionState *is = &ctx->interaction; 1101 switch (is->kind) { 1102 case InteractionKind_Scroll: { 1103 f32 delta = GetMouseWheelMoveV().y; 1104 switch (is->active->kind) { 1105 case VariableKind_Cycler: { 1106 is->active->cycler.state += delta; 1107 is->active->cycler.state %= is->active->cycler.length; 1108 } break; 1109 InvalidDefaultCase; 1110 } 1111 } break; 1112 case InteractionKind_Set: { 1113 switch (is->active->kind) { 1114 case VariableKind_Cycler: { 1115 s32 delta = (s32)mouse_left_pressed - (s32)mouse_right_pressed; 1116 is->active->cycler.state += delta; 1117 is->active->cycler.state %= is->active->cycler.length; 1118 } break; 1119 InvalidDefaultCase; 1120 } 1121 } break; 1122 InvalidDefaultCase; 1123 } 1124 1125 if (is->active->flags & VariableFlag_UpdateStoredMode) { 1126 ASSERT(is->active->kind == VariableKind_Cycler); 1127 ctx->colour = convert_colour(ctx->colour, ctx->stored_colour_kind, 1128 is->active->cycler.state); 1129 ctx->stored_colour_kind = is->active->cycler.state; 1130 } 1131 1132 is->kind = InteractionKind_None; 1133 is->active = 0; 1134 } 1135 1136 function void 1137 colour_picker_interact(ColourPickerCtx *ctx, v2 mouse) 1138 { 1139 InteractionState *is = &ctx->interaction; 1140 if (!is->active) is->hot = is->next_hot; 1141 is->next_hot = 0; 1142 1143 b32 mouse_left_pressed = IsMouseButtonPressed(MOUSE_BUTTON_LEFT); 1144 b32 mouse_right_pressed = IsMouseButtonPressed(MOUSE_BUTTON_RIGHT); 1145 b32 wheel_moved = GetMouseWheelMoveV().y != 0; 1146 if (mouse_left_pressed || mouse_right_pressed || wheel_moved) { 1147 if (is->kind != InteractionKind_None) 1148 colour_picker_end_interact(ctx, mouse_left_pressed, mouse_right_pressed); 1149 colour_picker_begin_interact(ctx, wheel_moved); 1150 } 1151 1152 switch (is->kind) { 1153 case InteractionKind_None: break; 1154 case InteractionKind_Scroll: 1155 case InteractionKind_Set: { 1156 colour_picker_end_interact(ctx, mouse_left_pressed, mouse_right_pressed); 1157 } break; 1158 InvalidDefaultCase; 1159 } 1160 1161 ctx->last_mouse = mouse; 1162 } 1163 1164 function void 1165 colour_picker_init(ColourPickerCtx *ctx) 1166 { 1167 #ifdef _DEBUG 1168 ctx->picker_shader = LoadShader(0, HSV_LERP_SHADER_NAME); 1169 #else 1170 ctx->picker_shader = LoadShaderFromMemory(0, _binary_slider_lerp_glsl_start); 1171 #endif 1172 ctx->mode_id = GetShaderLocation(ctx->picker_shader, "u_mode"); 1173 ctx->colour_mode_id = GetShaderLocation(ctx->picker_shader, "u_colour_mode"); 1174 ctx->colours_id = GetShaderLocation(ctx->picker_shader, "u_colours"); 1175 ctx->regions_id = GetShaderLocation(ctx->picker_shader, "u_regions"); 1176 ctx->radius_id = GetShaderLocation(ctx->picker_shader, "u_radius"); 1177 ctx->border_thick_id = GetShaderLocation(ctx->picker_shader, "u_border_thick"); 1178 1179 local_persist str8 colour_kind_labels[ColourKind_Last] = { 1180 [ColourKind_RGB] = str8("RGB"), 1181 [ColourKind_HSV] = str8("HSV"), 1182 }; 1183 ctx->slider_mode_state.colour_kind_cycler.kind = VariableKind_Cycler; 1184 ctx->slider_mode_state.colour_kind_cycler.flags = VariableFlag_UpdateStoredMode; 1185 ctx->slider_mode_state.colour_kind_cycler.cycler.state = ctx->stored_colour_kind; 1186 ctx->slider_mode_state.colour_kind_cycler.cycler.length = countof(colour_kind_labels); 1187 ctx->slider_mode_state.colour_kind_cycler.cycler.labels = colour_kind_labels; 1188 1189 ctx->flags |= ColourPickerFlag_Ready; 1190 } 1191 1192 DEBUG_EXPORT void 1193 do_colour_picker(ColourPickerCtx *ctx, f32 dt, Vector2 window_pos, Vector2 mouse_pos) 1194 { 1195 BEGIN_CYCLE_COUNT(CC_WHOLE_RUN); 1196 1197 dt_for_frame = dt; 1198 1199 if (IsWindowResized()) { 1200 ctx->window_size.h = GetScreenHeight(); 1201 ctx->window_size.w = ctx->window_size.h / WINDOW_ASPECT_RATIO; 1202 SetWindowSize(ctx->window_size.w, ctx->window_size.h); 1203 1204 UnloadTexture(ctx->font.texture); 1205 if (ctx->window_size.w < 480) ctx->font = LoadFont_lora_sb_1_inc(); 1206 else ctx->font = LoadFont_lora_sb_0_inc(); 1207 } 1208 1209 if (!(ctx->flags & ColourPickerFlag_Ready)) 1210 colour_picker_init(ctx); 1211 1212 ctx->mouse_pos.rv = mouse_pos; 1213 ctx->window_pos.rv = window_pos; 1214 1215 colour_picker_interact(ctx, ctx->mouse_pos); 1216 1217 uv2 ws = ctx->window_size; 1218 1219 DrawRectangle(ctx->window_pos.x, ctx->window_pos.y, ws.w, ws.h, ctx->bg); 1220 1221 v2 pad = {.x = 0.05 * ws.w, .y = 0.05 * ws.h}; 1222 Rect upper = { 1223 .pos = {.x = ctx->window_pos.x + pad.x, .y = ctx->window_pos.y + pad.y}, 1224 .size = {.w = ws.w - 2 * pad.x, .h = ws.h * 0.6}, 1225 }; 1226 Rect lower = { 1227 .pos = {.x = upper.pos.x, .y = upper.pos.y + ws.h * 0.6}, 1228 .size = {.w = ws.w - 2 * pad.x, .h = ws.h * 0.4 - 1 * pad.y}, 1229 }; 1230 1231 BEGIN_CYCLE_COUNT(CC_UPPER); 1232 1233 Rect ma = cut_rect_left(upper, 0.84); 1234 Rect sa = cut_rect_right(upper, 0.84); 1235 do_colour_stack(ctx, sa); 1236 1237 v2 ma_relative_mouse = ctx->mouse_pos; 1238 ma_relative_mouse.x -= ma.pos.x; 1239 ma_relative_mouse.y -= ma.pos.y; 1240 1241 { 1242 /* TODO(rnp): move this into single resize function */ 1243 if (ctx->picker_texture.texture.width != (s32)(ma.size.w)) { 1244 s32 w = ma.size.w; 1245 s32 h = ma.size.h; 1246 UnloadRenderTexture(ctx->picker_texture); 1247 ctx->picker_texture = LoadRenderTexture(w, h); 1248 if (ctx->mode != CPM_PICKER) { 1249 s32 mode = ctx->mode; 1250 ctx->mode = CPM_PICKER; 1251 do_picker_mode(ctx, ma_relative_mouse); 1252 ctx->mode = mode; 1253 } 1254 } 1255 1256 if (ctx->slider_texture.texture.width != (s32)(ma.size.w)) { 1257 s32 w = ma.size.w; 1258 s32 h = ma.size.h; 1259 UnloadRenderTexture(ctx->slider_texture); 1260 ctx->slider_texture = LoadRenderTexture(w, h); 1261 if (ctx->mode != CPM_SLIDERS) { 1262 s32 mode = ctx->mode; 1263 ctx->mode = CPM_SLIDERS; 1264 do_slider_mode(ctx, ma_relative_mouse); 1265 ctx->mode = mode; 1266 } 1267 } 1268 } 1269 1270 { 1271 Rect tr = { 1272 .size = { 1273 .w = ctx->slider_texture.texture.width, 1274 .h = -ctx->slider_texture.texture.height 1275 } 1276 }; 1277 NPatchInfo tnp = {tr.rr, 0, 0, 0, 0, NPATCH_NINE_PATCH}; 1278 switch (ctx->mode) { 1279 case CPM_SLIDERS: 1280 do_slider_mode(ctx, ma_relative_mouse); 1281 DrawTextureNPatch(ctx->slider_texture.texture, tnp, ma.rr, (Vector2){0}, 1282 0, WHITE); 1283 break; 1284 case CPM_PICKER: 1285 do_picker_mode(ctx, ma_relative_mouse); 1286 DrawTextureNPatch(ctx->picker_texture.texture, tnp, ma.rr, (Vector2){0}, 1287 0, WHITE); 1288 break; 1289 case CPM_LAST: 1290 ASSERT(0); 1291 break; 1292 } 1293 DrawRectangleRec(ma.rr, fade(ctx->bg, 1 - ctx->mcs.mode_visible_t)); 1294 } 1295 1296 END_CYCLE_COUNT(CC_UPPER); 1297 1298 { 1299 BEGIN_CYCLE_COUNT(CC_LOWER); 1300 Rect cb = lower; 1301 cb.size.h *= 0.25; 1302 cb.pos.y += 0.04 * lower.size.h; 1303 do_colour_selector(ctx, cb); 1304 1305 f32 mode_x_pad = 0.04 * lower.size.w; 1306 1307 Rect mb = cb; 1308 mb.size.w *= (1.0 / (CPM_LAST + 1) - 0.1); 1309 mb.size.w -= 0.5 * mode_x_pad; 1310 mb.size.h = mb.size.w; 1311 1312 mb.pos.y += lower.size.h * 0.75 / 2; 1313 1314 f32 offset = lower.size.w - (CPM_LAST + 1) * (mb.size.w + 0.5 * mode_x_pad); 1315 mb.pos.x += 0.5 * offset; 1316 1317 Rect tr = {.size.w = ctx->slider_texture.texture.width, 1318 .size.h = -ctx->slider_texture.texture.height}; 1319 1320 NPatchInfo tnp = {tr.rr, 0, 0, 0, 0, NPATCH_NINE_PATCH }; 1321 for (u32 i = 0; i < CPM_LAST; i++) { 1322 if (do_button(ctx->mcs.buttons + i, ctx->mouse_pos, mb, 10)) { 1323 if (ctx->mode != i) 1324 ctx->mcs.next_mode = i; 1325 } 1326 1327 if (ctx->mcs.next_mode != -1) { 1328 ctx->mcs.mode_visible_t -= 2 * dt_for_frame; 1329 if (ctx->mcs.mode_visible_t < 0) { 1330 ctx->mode = ctx->mcs.next_mode; 1331 ctx->mcs.next_mode = -1; 1332 if (ctx->mode == CPM_PICKER) { 1333 v4 hsv = get_formatted_colour(ctx, ColourKind_HSV); 1334 ctx->pms.base_hue = hsv.x; 1335 ctx->pms.fractional_hue = 0; 1336 } 1337 ctx->flags |= ColourPickerFlag_RefillTexture; 1338 } 1339 } else { 1340 ctx->mcs.mode_visible_t += 2 * dt_for_frame; 1341 } 1342 CLAMP01(ctx->mcs.mode_visible_t); 1343 1344 Texture *texture = NULL; 1345 switch (i) { 1346 case CPM_PICKER: texture = &ctx->picker_texture.texture; break; 1347 case CPM_SLIDERS: texture = &ctx->slider_texture.texture; break; 1348 case CPM_LAST: break; 1349 } 1350 ASSERT(texture); 1351 1352 f32 scale = lerp(1, 1.1, ctx->mcs.buttons[i].hover_t); 1353 Rect txt_out = scale_rect_centered(mb, (v2){.x = 0.8 * scale, 1354 .y = 0.8 * scale}); 1355 Rect outline_r = scale_rect_centered(mb, (v2){.x = scale, .y = scale}); 1356 1357 DrawTextureNPatch(*texture, tnp, txt_out.rr, (Vector2){0}, 0, WHITE); 1358 DrawRectangleRoundedLinesEx(outline_r.rr, SELECTOR_ROUNDNESS, 0, 1359 SELECTOR_BORDER_WIDTH, SELECTOR_BORDER_COLOUR); 1360 1361 mb.pos.x += mb.size.w + mode_x_pad; 1362 txt_out.pos.x += mb.size.w + mode_x_pad; 1363 } 1364 1365 v4 fg = normalize_colour(pack_rl_colour(ctx->fg)); 1366 Color bg = rl_colour_from_normalized(get_formatted_colour(ctx, ColourKind_RGB)); 1367 Rect btn_r = mb; 1368 btn_r.size.h *= 0.46; 1369 1370 if (do_text_button(ctx, ctx->buttons + 0, ctx->mouse_pos, btn_r, str8("Copy"), fg, bg)) { 1371 /* NOTE: SetClipboardText needs a NUL terminated string */ 1372 u8 cbuf[9] = {0}; 1373 Stream cstream = {.data = cbuf, .cap = countof(cbuf) - 1}; 1374 stream_append_colour(&cstream, bg); 1375 SetClipboardText((char *)cbuf); 1376 } 1377 btn_r.pos.y += 0.54 * mb.size.h; 1378 1379 if (do_text_button(ctx, ctx->buttons + 1, ctx->mouse_pos, btn_r, str8("Paste"), fg, bg)) { 1380 str8 txt = str8_from_c_str((char *)GetClipboardText()); 1381 if (txt.len) { 1382 v4 new_colour = normalize_colour(parse_hex_u32(txt)); 1383 ctx->colour = convert_colour(new_colour, ColourKind_RGB, ctx->stored_colour_kind); 1384 if (ctx->mode == CPM_PICKER) { 1385 f32 hue = get_formatted_colour(ctx, ColourKind_HSV).x; 1386 ctx->pms.base_hue = hue; 1387 ctx->pms.fractional_hue = 0; 1388 } 1389 } 1390 } 1391 1392 END_CYCLE_COUNT(CC_LOWER); 1393 } 1394 1395 END_CYCLE_COUNT(CC_WHOLE_RUN); 1396 1397 debug_dump_info(ctx); 1398 }