comparison third_party/raylib/custom.c @ 173:827c6ac504cd hg-web

Merged in default here.
author MrJuneJune <me@mrjunejune.com>
date Mon, 19 Jan 2026 18:59:10 -0800
parents 058de208e640
children
comparison
equal deleted inserted replaced
151:c033667da5f9 173:827c6ac504cd
1 #include "third_party/raylib/include/raylib.h" 1 #include "third_party/raylib/include/raylib.h"
2 #define RAYGUI_IMPLEMENTATION 2 #define RAYGUI_IMPLEMENTATION
3 #include "third_party/raylib/include/raygui.h" 3 #include "third_party/raylib/include/raygui.h"
4 4 #include "third_party/raylib/custom.h"
5 // -- forward declarations --// 5 #include <string.h>
6 6
7 // --- Default Behaviour that should be part of all raylib gui ---/ 7 // ============================================================================
8 void DefaultBehaviours(); 8 // COLOR SCHEME IMPLEMENTATION
9 // --- Increase Font Sizes on key press --- // 9 // ============================================================================
10 void IncreaseFontSize(); 10
11 // --- Decrease Font Sizes on key press --- // 11 // Global color scheme instance
12 void DecreaseFontSize(); 12 ColorScheme g_colors = {0};
13 13
14 14 ColorScheme PostDog_DefaultColorScheme(void)
15 void DefaultBehaviours() 15 {
16 { 16 return (ColorScheme){
17 // Font sizes 17 .primary = (Color){255, 140, 0, 255}, // Orange
18 IncreaseFontSize(); 18 .secondary = (Color){255, 248, 231, 255}, // Beige/Egg white
19 DecreaseFontSize(); 19 .background = (Color){255, 255, 255, 255}, // White
20 } 20 .text = (Color){30, 30, 30, 255}, // Near black
21 21 .text_light = (Color){255, 255, 255, 255}, // White
22 void IncreaseFontSize() 22 .border = (Color){200, 200, 200, 255}, // Light gray
23 { 23 .highlight = (Color){255, 180, 100, 255}, // Light orange
24 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL)) 24 .success = (Color){76, 175, 80, 255}, // Green
25 GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1); 25 .error = (Color){244, 67, 54, 255}, // Red
26 } 26 .muted = (Color){158, 158, 158, 255}, // Gray
27 27 };
28 void DecreaseFontSize() 28 }
29 { 29
30 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS)) 30 ColorScheme PostDog_DarkColorScheme(void)
31 GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1); 31 {
32 } 32 return (ColorScheme){
33 33 .primary = (Color){255, 140, 0, 255}, // Orange
34 .secondary = (Color){45, 45, 48, 255}, // Dark gray
35 .background = (Color){30, 30, 30, 255}, // Near black
36 .text = (Color){240, 240, 240, 255}, // Near white
37 .text_light = (Color){255, 255, 255, 255}, // White
38 .border = (Color){70, 70, 70, 255}, // Dark gray
39 .highlight = (Color){255, 180, 100, 255}, // Light orange
40 .success = (Color){76, 175, 80, 255}, // Green
41 .error = (Color){244, 67, 54, 255}, // Red
42 .muted = (Color){100, 100, 100, 255}, // Gray
43 };
44 }
45
46 void PostDog_InitColorScheme(void)
47 {
48 g_colors = PostDog_DefaultColorScheme();
49 }
50
51 void PostDog_SetColorScheme(ColorScheme scheme)
52 {
53 g_colors = scheme;
54 }
55
56 // ============================================================================
57 // FUNCTIONALITY HELPERS
58 // ============================================================================
59
60 void DefaultBehaviours(void)
61 {
62 IncreaseFontSize();
63 DecreaseFontSize();
64 }
65
66 void IncreaseFontSize(void)
67 {
68 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_EQUAL))
69 GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) + 1);
70 }
71
72 void DecreaseFontSize(void)
73 {
74 if ((IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_LEFT_CONTROL)) && IsKeyDown(KEY_MINUS))
75 GuiSetStyle(DEFAULT, TEXT_SIZE, GuiGetStyle(DEFAULT, TEXT_SIZE) - 1);
76 }
34 77
35 // --- Layout helper --- // 78 // --- Layout helper --- //
36 Rectangle RightOf(Rectangle ref, float padding) 79 Rectangle RightOf(Rectangle ref, float padding)
37 { 80 {
38 return (Rectangle){ 81 return (Rectangle){
82 .height = container.height 125 .height = container.height
83 }; 126 };
84 } 127 }
85 128
86 129
130 void DrawRectangleSelectiveRounded(Rectangle rec, float radius, int segments, Color color,
131 boolean roundTL, boolean roundTR, boolean roundBR, boolean roundBL) {
132 // Clamp radius to valid range
133 float maxR = fminf(rec.width, rec.height) * 0.5f;
134 radius = fminf(fmaxf(radius, 0.0f), maxR);
135
136 // Fill center
137 DrawRectangleRec((Rectangle){
138 rec.x + radius, rec.y + radius,
139 rec.width - 2.0f * radius, rec.height - 2.0f * radius
140 }, color);
141
142 //
143 // |--------|
144 // | |
145 // | |
146 // |--------|
147
148 // Top edge
149 float topStart = rec.x + (roundTL ? radius : 0.0f);
150 float topWidth = rec.width - (roundTL ? radius : 0.0f) - (roundTR ? radius : 0.0f);
151 if (topWidth > 0.0f)
152 DrawRectangleRec((Rectangle){ topStart, rec.y, topWidth, radius }, color);
153
154 // Bottom edge
155 float botStart = rec.x + (roundBL ? radius : 0.0f);
156 float botWidth = rec.width - (roundBL ? radius : 0.0f) - (roundBR ? radius : 0.0f);
157 if (botWidth > 0.0f)
158 DrawRectangleRec((Rectangle){ botStart, rec.y + rec.height - radius, botWidth, radius }, color);
159
160 // Left edge
161 float leftStart = rec.y + (roundTL ? radius : 0.0f);
162 float leftHeight = rec.height - (roundTL ? radius : 0.0f) - (roundBL ? radius : 0.0f);
163 if (leftHeight > 0.0f)
164 DrawRectangleRec((Rectangle){ rec.x, leftStart, radius, leftHeight - radius}, color);
165
166 // Right edge
167 float rightStart = rec.y + (roundTR ? radius : 0.0f);
168 float rightHeight = rec.height - (roundTR ? radius : 0.0f) - (roundBR ? radius : 0.0f);
169 if (rightHeight > 0.0f)
170 DrawRectangleRec((Rectangle){ rec.x + rec.width - radius, rightStart, radius, rightHeight - radius }, color);
171
172 // Corners (arcs)
173 if (roundTL) {
174 DrawCircleSector((Vector2){ rec.x + radius, rec.y + radius }, radius, 180.0f, 270.0f, segments, color);
175 }
176 if (roundTR) {
177 DrawCircleSector((Vector2){ rec.x + rec.width - radius, rec.y + radius }, radius, 270.0f, 360.0f, segments, color);
178 }
179 if (roundBR) {
180 DrawCircleSector((Vector2){ rec.x + rec.width - radius, rec.y + rec.height - radius }, radius, 0.0f, 90.0f, segments, color);
181 }
182 if (roundBL) {
183 DrawCircleSector((Vector2){ rec.x + radius, rec.y + rec.height - radius }, radius, 90.0f, 180.0f, segments, color);
184 }
185 }
186
187 Rectangle AddPadding(Rectangle rect, float padding)
188 {
189 return (Rectangle){
190 rect.x + padding,
191 rect.y + padding,
192 rect.width - (2 * padding),
193 rect.height - (2 * padding)
194 };
195 }
196
197 Rectangle AddPaddingAll(Rectangle rect, float top, float right,float down, float left)
198 {
199 return (Rectangle){
200 rect.x + left,
201 rect.y + top,
202 rect.width - (right + left),
203 rect.height - (top + down),
204 };
205 }
206
207
208
209 Rectangle AddPaddingHorizontal(Rectangle rect, float padding)
210 {
211 return (Rectangle){
212 rect.x + padding,
213 rect.y,
214 rect.width - (2 * padding),
215 rect.height
216 };
217 }
218
219 Rectangle AddPaddingVertical(Rectangle rect, float padding)
220 {
221 return (Rectangle){
222 rect.x,
223 rect.y + padding,
224 rect.width,
225 rect.height - (2 * padding)
226 };
227 }
228
229 Rectangle VerticalSplit(Rectangle container, float ratio)
230 {
231 return (Rectangle){
232 .x = container.x,
233 .y = container.y,
234 .width = container.width,
235 .height = container.height * ratio
236 };
237 }
238
239 // ============================================================================
240 // DRAWING HELPERS - Additional
241 // ============================================================================
242
243 void DrawRectangleSelectiveRoundedLines(Rectangle rec, float radius, int segments, Color color,
244 boolean roundTL, boolean roundTR, boolean roundBR, boolean roundBL)
245 {
246 // Top edge
247 DrawLine(rec.x + (roundTL ? radius : 0), rec.y,
248 rec.x + rec.width - (roundTR ? radius : 0), rec.y, color);
249 // Bottom edge
250 DrawLine(rec.x + (roundBL ? radius : 0), rec.y + rec.height,
251 rec.x + rec.width - (roundBR ? radius : 0), rec.y + rec.height, color);
252 // Left edge
253 DrawLine(rec.x, rec.y + (roundTL ? radius : 0),
254 rec.x, rec.y + rec.height - (roundBL ? radius : 0), color);
255 // Right edge
256 DrawLine(rec.x + rec.width, rec.y + (roundTR ? radius : 0),
257 rec.x + rec.width, rec.y + rec.height - (roundBR ? radius : 0), color);
258
259 // Corner arcs
260 if (roundTL)
261 DrawCircleSectorLines((Vector2){rec.x + radius, rec.y + radius}, radius, 180, 270, segments, color);
262 if (roundTR)
263 DrawCircleSectorLines((Vector2){rec.x + rec.width - radius, rec.y + radius}, radius, 270, 360, segments, color);
264 if (roundBR)
265 DrawCircleSectorLines((Vector2){rec.x + rec.width - radius, rec.y + rec.height - radius}, radius, 0, 90, segments, color);
266 if (roundBL)
267 DrawCircleSectorLines((Vector2){rec.x + radius, rec.y + rec.height - radius}, radius, 90, 180, segments, color);
268 }
269
270 // ============================================================================
271 // CUSTOM TAB COMPONENT IMPLEMENTATION
272 // ============================================================================
273
274 boolean PostDog_TabBar(Rectangle bounds, TabConfig config)
275 {
276 if (config.count <= 0 || config.labels == NULL || config.active_tab == NULL)
277 return FALSE;
278
279 boolean changed = FALSE;
280 float tab_width = (bounds.width - config.padding * (config.count - 1)) / config.count;
281 Vector2 mouse = GetMousePosition();
282
283 for (int i = 0; i < config.count; i++)
284 {
285 Rectangle tab_rect = {
286 .x = bounds.x + i * (tab_width + config.padding),
287 .y = bounds.y,
288 .width = tab_width,
289 .height = bounds.height
290 };
291
292 boolean is_active = (i == *config.active_tab);
293 boolean is_hovered = CheckCollisionPointRec(mouse, tab_rect);
294 Color bg_color = is_active ? config.active_color : config.inactive_color;
295 Color txt_color = is_active ? config.active_text_color : config.text_color;
296
297 // Hover effect
298 if (is_hovered && !is_active)
299 bg_color = Fade(config.active_color, 0.5f);
300
301 // Draw tab background with rounded top corners
302 DrawRectangleSelectiveRounded(tab_rect, config.corner_radius, 8, bg_color,
303 TRUE, TRUE, FALSE, FALSE);
304
305 // Draw tab label
306 int text_width = MeasureText(config.labels[i], GuiGetStyle(DEFAULT, TEXT_SIZE));
307 Vector2 text_pos = {
308 tab_rect.x + (tab_rect.width - text_width) / 2,
309 tab_rect.y + (tab_rect.height - GuiGetStyle(DEFAULT, TEXT_SIZE)) / 2
310 };
311 DrawText(config.labels[i], text_pos.x, text_pos.y, GuiGetStyle(DEFAULT, TEXT_SIZE), txt_color);
312
313 // Handle click
314 if (is_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT) && !is_active)
315 {
316 *config.active_tab = i;
317 changed = TRUE;
318 }
319 }
320
321 return changed;
322 }
323
324 boolean PostDog_TabBarSimple(Rectangle bounds, const char **labels, int count, int *active_tab)
325 {
326 TabConfig config = {
327 .labels = labels,
328 .count = count,
329 .active_tab = active_tab,
330 .padding = 2,
331 .corner_radius = 8,
332 .active_color = g_colors.primary,
333 .inactive_color = g_colors.secondary,
334 .text_color = g_colors.text,
335 .active_text_color = g_colors.text_light,
336 };
337 return PostDog_TabBar(bounds, config);
338 }
339
340 // ============================================================================
341 // SPLIT PANEL IMPLEMENTATION
342 // ============================================================================
343
344 void PostDog_SplitPanel(Rectangle bounds, SplitPanelConfig config,
345 Rectangle *out_left, Rectangle *out_right)
346 {
347 float left_width = bounds.width * config.split_ratio - config.gap / 2;
348 float right_width = bounds.width * (1.0f - config.split_ratio) - config.gap / 2;
349
350 if (out_left)
351 {
352 *out_left = (Rectangle){
353 .x = bounds.x,
354 .y = bounds.y,
355 .width = left_width,
356 .height = bounds.height
357 };
358 }
359
360 if (out_right)
361 {
362 *out_right = (Rectangle){
363 .x = bounds.x + left_width + config.gap,
364 .y = bounds.y,
365 .width = right_width,
366 .height = bounds.height
367 };
368 }
369 }
370
371 void PostDog_SplitPanelDraw(Rectangle bounds, SplitPanelConfig config)
372 {
373 Rectangle left_rect, right_rect;
374 PostDog_SplitPanel(bounds, config, &left_rect, &right_rect);
375
376 // Draw left panel with rounded left corners
377 DrawRectangleSelectiveRounded(left_rect, config.corner_radius, 8, config.left_color,
378 TRUE, FALSE, FALSE, TRUE);
379
380 // Draw right panel with rounded right corners
381 DrawRectangleSelectiveRounded(right_rect, config.corner_radius, 8, config.right_color,
382 FALSE, TRUE, TRUE, FALSE);
383 }
384
385 // ============================================================================
386 // COMBINED INPUT COMPONENTS IMPLEMENTATION
387 // ============================================================================
388
389 // Helper: Parse semicolon-separated items and return item at index
390 static const char* GetDropdownItem(const char *items, int index, char *buffer, int buffer_size)
391 {
392 int current_index = 0;
393 int start = 0;
394 int len = strlen(items);
395
396 for (int i = 0; i <= len; i++)
397 {
398 if (items[i] == ';' || items[i] == '\0')
399 {
400 if (current_index == index)
401 {
402 int item_len = i - start;
403 if (item_len >= buffer_size) item_len = buffer_size - 1;
404 strncpy(buffer, items + start, item_len);
405 buffer[item_len] = '\0';
406 return buffer;
407 }
408 current_index++;
409 start = i + 1;
410 }
411 }
412 buffer[0] = '\0';
413 return buffer;
414 }
415
416 // Helper: Count items in semicolon-separated string
417 static int CountDropdownItems(const char *items)
418 {
419 if (!items || items[0] == '\0') return 0;
420 int count = 1;
421 for (int i = 0; items[i]; i++)
422 if (items[i] == ';') count++;
423 return count;
424 }
425
426 boolean PostDog_DropdownTextBox(Rectangle bounds, DropdownTextBoxConfig config)
427 {
428 boolean result = FALSE;
429
430 // Calculate dropdown width
431 float dropdown_w = config.dropdown_width;
432 if (dropdown_w <= 1.0f)
433 dropdown_w = bounds.width * dropdown_w;
434
435 // Calculate rectangles
436 Rectangle dropdown_rect = {
437 .x = bounds.x,
438 .y = bounds.y,
439 .width = dropdown_w,
440 .height = bounds.height
441 };
442
443 Rectangle textbox_rect = {
444 .x = bounds.x + dropdown_w,
445 .y = bounds.y,
446 .width = bounds.width - dropdown_w,
447 .height = bounds.height
448 };
449
450 // Draw combined background
451 DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8, config.background_color,
452 TRUE, FALSE, FALSE, TRUE);
453 DrawRectangleSelectiveRounded(textbox_rect, config.corner_radius, 8, config.background_color,
454 FALSE, TRUE, TRUE, FALSE);
455
456 // Draw separator line
457 DrawLine(dropdown_rect.x + dropdown_rect.width, dropdown_rect.y + 4,
458 dropdown_rect.x + dropdown_rect.width, dropdown_rect.y + dropdown_rect.height - 4,
459 config.border_color);
460
461 // Draw border
462 DrawRectangleSelectiveRoundedLines(bounds, config.corner_radius, 8, config.border_color,
463 TRUE, TRUE, TRUE, TRUE);
464
465 // Custom dropdown with per-item colors
466 Vector2 mouse_pos = GetMousePosition();
467 int item_count = config.item_count > 0 ? config.item_count : CountDropdownItems(config.dropdown_items);
468 int font_size = GuiGetStyle(DEFAULT, TEXT_SIZE);
469 char item_buffer[64];
470
471 // Get current selected item
472 GetDropdownItem(config.dropdown_items, *config.dropdown_active, item_buffer, sizeof(item_buffer));
473 Color current_color = (config.item_colors && *config.dropdown_active < item_count)
474 ? config.item_colors[*config.dropdown_active]
475 : config.text_color;
476
477 // Draw selected item in dropdown area
478 Rectangle dropdown_inner = AddPadding(dropdown_rect, 6);
479 boolean dropdown_hovered = CheckCollisionPointRec(mouse_pos, dropdown_rect);
480
481 // Highlight on hover
482 if (dropdown_hovered && !(*config.dropdown_edit_mode))
483 {
484 DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8,
485 Fade(current_color, 0.5f), TRUE, FALSE, FALSE, TRUE);
486 }
487
488 // Draw selected text with color
489 int text_width = MeasureText(item_buffer, font_size);
490 DrawText(item_buffer,
491 dropdown_inner.x + (dropdown_inner.width - text_width) / 2,
492 dropdown_inner.y + (dropdown_inner.height - font_size) / 2,
493 font_size, current_color);
494 DrawRectangleSelectiveRounded(dropdown_rect, config.corner_radius, 8,
495 Fade(current_color, 0.3f), TRUE, FALSE, FALSE, TRUE);
496
497 // Draw dropdown arrow
498 float arrow_size = 6;
499 float arrow_x = dropdown_rect.x + dropdown_rect.width - 14;
500 float arrow_y = dropdown_rect.y + dropdown_rect.height / 2;
501 DrawTriangle(
502 (Vector2){arrow_x, arrow_y - arrow_size/2},
503 (Vector2){arrow_x + arrow_size, arrow_y - arrow_size/2},
504 (Vector2){arrow_x + arrow_size/2, arrow_y + arrow_size/2},
505 config.text_color);
506
507 // Handle dropdown click
508 if (dropdown_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
509 {
510 *config.dropdown_edit_mode = !(*config.dropdown_edit_mode);
511 }
512
513 // Draw dropdown list when open
514 if (*config.dropdown_edit_mode)
515 {
516 float item_height = bounds.height;
517 Rectangle list_rect = {
518 .x = dropdown_rect.x,
519 .y = dropdown_rect.y + dropdown_rect.height + 2,
520 .width = dropdown_rect.width,
521 .height = item_height * item_count
522 };
523
524 // Background
525 DrawRectangleRounded(list_rect, 0.1f, 8, config.background_color);
526 DrawRectangleRoundedLines(list_rect, 0.1f, 8, config.border_color);
527
528 // Draw each item
529 for (int i = 0; i < item_count; i++)
530 {
531 Rectangle item_rect = {
532 .x = list_rect.x,
533 .y = list_rect.y + i * item_height,
534 .width = list_rect.width,
535 .height = item_height
536 };
537
538 GetDropdownItem(config.dropdown_items, i, item_buffer, sizeof(item_buffer));
539 Color item_color = (config.item_colors && i < item_count)
540 ? config.item_colors[i]
541 : config.text_color;
542
543 boolean item_hovered = CheckCollisionPointRec(mouse_pos, item_rect);
544
545 // Highlight hovered item
546 if (item_hovered)
547 DrawRectangleRec(item_rect, Fade(item_color, 0.15f));
548
549 // Highlight selected item
550 DrawRectangleRec(item_rect, Fade(item_color, 0.2f));
551
552 // Draw item text
553 text_width = MeasureText(item_buffer, font_size);
554 DrawText(item_buffer,
555 item_rect.x + (item_rect.width - text_width) / 2,
556 item_rect.y + (item_rect.height - font_size) / 2,
557 font_size, item_color);
558
559 // Handle item click
560 if (item_hovered && IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
561 {
562 *config.dropdown_active = i;
563 *config.dropdown_edit_mode = FALSE;
564 }
565 }
566
567 // Close dropdown if clicked outside
568 if (!CheckCollisionPointRec(mouse_pos, list_rect) &&
569 !CheckCollisionPointRec(mouse_pos, dropdown_rect) &&
570 IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
571 {
572 *config.dropdown_edit_mode = FALSE;
573 }
574 }
575
576 // Draw text box (using raygui)
577 Rectangle textbox_inner = AddPadding(textbox_rect, 4);
578 boolean ctrl_pressed = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL) || IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER);
579 if (*config.text_edit_mode && ctrl_pressed && IsKeyPressed(KEY_V))
580 {
581 const char *clipboard = GetClipboardText();
582 snprintf(config.text_buffer, config.text_buffer_size, "%s", clipboard);
583 }
584 if (GuiTextBox(textbox_inner, config.text_buffer, config.text_buffer_size, *config.text_edit_mode))
585 {
586 *config.text_edit_mode = !(*config.text_edit_mode);
587 result = TRUE;
588 }
589
590 return result;
591 }