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