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