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