Mercurial
comparison infinite_canvas/canvas.c @ 278:8d560f50ed4c
Improve infinite canvas interactions and browser chrome
Render Lucide icons directly with Raylib, add searchable icon browsing, robust text editing, entity lifecycle animations, z-order-safe input, semantic themes, and animated editable browser controls. Document rendering, pinning, context, and component extension for future agents.
Co-authored-by: Copilot <[email protected]>
Copilot-Session: f68442b1-fa8f-46a0-9689-81710613bbd4
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 17 Aug 2026 22:16:14 -0700 |
| parents | b55c22cff335 |
| children | 49e9e591c9bb |
comparison
equal
deleted
inserted
replaced
| 277:1d99147f520c | 278:8d560f50ed4c |
|---|---|
| 1 #include "infinite_canvas/canvas.h" | 1 #include "infinite_canvas/canvas.h" |
| 2 #include "infinite_canvas/generated/lucide_data.h" | |
| 2 | 3 |
| 3 #include <math.h> | 4 #include <math.h> |
| 4 #include <stdarg.h> | 5 #include <stdarg.h> |
| 5 #include <stdio.h> | 6 #include <stdio.h> |
| 6 #include <string.h> | 7 #include <string.h> |
| 22 "Ready", | 23 "Ready", |
| 23 "Live", | 24 "Live", |
| 24 "Draft", | 25 "Draft", |
| 25 "Synced", | 26 "Synced", |
| 26 }; | 27 }; |
| 28 | |
| 29 #define CANVAS_LUCIDE_COLUMNS 10 | |
| 30 #define CANVAS_LUCIDE_CARD_WIDTH 128.0f | |
| 31 #define CANVAS_LUCIDE_CARD_HEIGHT 96.0f | |
| 32 #define CANVAS_LUCIDE_GAP 12.0f | |
| 33 #define CANVAS_LUCIDE_HEADER_HEIGHT 88.0f | |
| 34 #define CANVAS_LUCIDE_PADDING 24.0f | |
| 35 #define CANVAS_LUCIDE_SEARCH_WIDTH 360.0f | |
| 36 #define CANVAS_LUCIDE_SEARCH_HEIGHT 42.0f | |
| 37 #define CANVAS_TEXT_AREA_PADDING 16.0f | |
| 38 #define CANVAS_TEXT_AREA_FONT_SIZE 16.0f | |
| 39 #define CANVAS_TEXT_AREA_SPACING 0.1f | |
| 40 #define CANVAS_TEXT_AREA_LINE_HEIGHT 22.0f | |
| 41 | |
| 42 typedef struct { | |
| 43 int32 start; | |
| 44 int32 end; | |
| 45 int32 next; | |
| 46 float width; | |
| 47 } Canvas_Text_Line; | |
| 27 | 48 |
| 28 static float Canvas_Clamp(float value, float min_value, float max_value) | 49 static float Canvas_Clamp(float value, float min_value, float max_value) |
| 29 { | 50 { |
| 30 if (value < min_value) return min_value; | 51 if (value < min_value) return min_value; |
| 31 if (value > max_value) return max_value; | 52 if (value > max_value) return max_value; |
| 51 (uint8)((float)from.r + ((float)to.r - (float)from.r) * amount), | 72 (uint8)((float)from.r + ((float)to.r - (float)from.r) * amount), |
| 52 (uint8)((float)from.g + ((float)to.g - (float)from.g) * amount), | 73 (uint8)((float)from.g + ((float)to.g - (float)from.g) * amount), |
| 53 (uint8)((float)from.b + ((float)to.b - (float)from.b) * amount), | 74 (uint8)((float)from.b + ((float)to.b - (float)from.b) * amount), |
| 54 (uint8)((float)from.a + ((float)to.a - (float)from.a) * amount), | 75 (uint8)((float)from.a + ((float)to.a - (float)from.a) * amount), |
| 55 }; | 76 }; |
| 77 } | |
| 78 | |
| 79 static Color Canvas_Color_With_Opacity(Color color, float opacity) | |
| 80 { | |
| 81 color.a = (uint8)((float)color.a * Canvas_Clamp(opacity, 0.0f, 1.0f)); | |
| 82 return color; | |
| 83 } | |
| 84 | |
| 85 static Color Canvas_Color_Fade(Color color, float amount) | |
| 86 { | |
| 87 return Canvas_Color_With_Opacity(color, amount); | |
| 88 } | |
| 89 | |
| 90 static float Canvas_Entity_Visibility(const Canvas_Entity *p_entity) | |
| 91 { | |
| 92 float amount = Canvas_Clamp(p_entity->visibility_amount, 0.0f, 1.0f); | |
| 93 return amount * amount * (3.0f - 2.0f * amount); | |
| 94 } | |
| 95 | |
| 96 static Canvas_Theme Canvas_Theme_With_Opacity( | |
| 97 const Canvas_Theme *p_theme, | |
| 98 float opacity) | |
| 99 { | |
| 100 Canvas_Theme faded = *p_theme; | |
| 101 faded.background = Canvas_Color_With_Opacity(faded.background, opacity); | |
| 102 faded.surface = Canvas_Color_With_Opacity(faded.surface, opacity); | |
| 103 faded.surface_muted = Canvas_Color_With_Opacity(faded.surface_muted, opacity); | |
| 104 faded.border = Canvas_Color_With_Opacity(faded.border, opacity); | |
| 105 faded.text = Canvas_Color_With_Opacity(faded.text, opacity); | |
| 106 faded.text_muted = Canvas_Color_With_Opacity(faded.text_muted, opacity); | |
| 107 faded.accent = Canvas_Color_With_Opacity(faded.accent, opacity); | |
| 108 faded.accent_soft = Canvas_Color_With_Opacity(faded.accent_soft, opacity); | |
| 109 faded.on_accent = Canvas_Color_With_Opacity(faded.on_accent, opacity); | |
| 110 faded.selection = Canvas_Color_With_Opacity(faded.selection, opacity); | |
| 111 faded.entity_outline = Canvas_Color_With_Opacity(faded.entity_outline, opacity); | |
| 112 faded.button = Canvas_Color_With_Opacity(faded.button, opacity); | |
| 113 faded.button_hover = Canvas_Color_With_Opacity(faded.button_hover, opacity); | |
| 114 faded.button_active = Canvas_Color_With_Opacity(faded.button_active, opacity); | |
| 115 faded.button_active_hover = Canvas_Color_With_Opacity( | |
| 116 faded.button_active_hover, | |
| 117 opacity); | |
| 118 faded.switch_track_off = Canvas_Color_With_Opacity( | |
| 119 faded.switch_track_off, | |
| 120 opacity); | |
| 121 faded.switch_track_off_hover = Canvas_Color_With_Opacity( | |
| 122 faded.switch_track_off_hover, | |
| 123 opacity); | |
| 124 faded.switch_track_on = Canvas_Color_With_Opacity( | |
| 125 faded.switch_track_on, | |
| 126 opacity); | |
| 127 faded.switch_track_on_hover = Canvas_Color_With_Opacity( | |
| 128 faded.switch_track_on_hover, | |
| 129 opacity); | |
| 130 faded.switch_thumb = Canvas_Color_With_Opacity(faded.switch_thumb, opacity); | |
| 131 faded.pin = Canvas_Color_With_Opacity(faded.pin, opacity); | |
| 132 faded.danger = Canvas_Color_With_Opacity(faded.danger, opacity); | |
| 133 faded.shadow = Canvas_Color_With_Opacity(faded.shadow, opacity); | |
| 134 faded.toast_surface = Canvas_Color_With_Opacity(faded.toast_surface, opacity); | |
| 135 faded.toast_border = Canvas_Color_With_Opacity(faded.toast_border, opacity); | |
| 136 return faded; | |
| 137 } | |
| 138 | |
| 139 static boolean Canvas_Control_Is_Down(void) | |
| 140 { | |
| 141 return IsKeyDown(KEY_LEFT_CONTROL) || | |
| 142 IsKeyDown(KEY_RIGHT_CONTROL) || | |
| 143 IsKeyDown(KEY_LEFT_SUPER) || | |
| 144 IsKeyDown(KEY_RIGHT_SUPER); | |
| 145 } | |
| 146 | |
| 147 static boolean Canvas_Shift_Is_Down(void); | |
| 148 static boolean Canvas_Key_Activated(int32 key); | |
| 149 | |
| 150 static char Canvas_Ascii_Lower(char value) | |
| 151 { | |
| 152 if (value >= 'A' && value <= 'Z') return (char)(value + ('a' - 'A')); | |
| 153 return value; | |
| 154 } | |
| 155 | |
| 156 static boolean Canvas_Lucide_Name_Matches( | |
| 157 const char *p_name, | |
| 158 const char *p_query) | |
| 159 { | |
| 160 if (!p_query || !p_query[0]) return TRUE; | |
| 161 for (const char *p_start = p_name; *p_start; p_start++) { | |
| 162 const char *p_name_cursor = p_start; | |
| 163 const char *p_query_cursor = p_query; | |
| 164 while (*p_name_cursor && | |
| 165 *p_query_cursor && | |
| 166 Canvas_Ascii_Lower(*p_name_cursor) == | |
| 167 Canvas_Ascii_Lower(*p_query_cursor)) { | |
| 168 p_name_cursor++; | |
| 169 p_query_cursor++; | |
| 170 } | |
| 171 if (!*p_query_cursor) return TRUE; | |
| 172 } | |
| 173 return FALSE; | |
| 174 } | |
| 175 | |
| 176 int32 Canvas_Lucide_Count_Matches(const char *p_query) | |
| 177 { | |
| 178 int32 count = 0; | |
| 179 for (int32 index = 0; index < CANVAS_LUCIDE_ICON_COUNT; index++) { | |
| 180 if (Canvas_Lucide_Name_Matches( | |
| 181 CANVAS_LUCIDE_ICONS[index].p_name, | |
| 182 p_query)) { | |
| 183 count++; | |
| 184 } | |
| 185 } | |
| 186 return count; | |
| 187 } | |
| 188 | |
| 189 static Rectangle Canvas_Lucide_Search_Bounds(const Canvas_Entity *p_entity) | |
| 190 { | |
| 191 return (Rectangle){ | |
| 192 p_entity->position.x + p_entity->size.x - | |
| 193 CANVAS_LUCIDE_PADDING - CANVAS_LUCIDE_SEARCH_WIDTH, | |
| 194 p_entity->position.y + | |
| 195 (CANVAS_LUCIDE_HEADER_HEIGHT - CANVAS_LUCIDE_SEARCH_HEIGHT) * 0.5f, | |
| 196 CANVAS_LUCIDE_SEARCH_WIDTH, | |
| 197 CANVAS_LUCIDE_SEARCH_HEIGHT, | |
| 198 }; | |
| 199 } | |
| 200 | |
| 201 static float Canvas_Lucide_Content_Height(const char *p_query) | |
| 202 { | |
| 203 int32 icon_count = Canvas_Lucide_Count_Matches(p_query); | |
| 204 int32 rows = | |
| 205 (icon_count + CANVAS_LUCIDE_COLUMNS - 1) / | |
| 206 CANVAS_LUCIDE_COLUMNS; | |
| 207 if (rows == 0) { | |
| 208 return CANVAS_LUCIDE_HEADER_HEIGHT + CANVAS_LUCIDE_PADDING * 2.0f; | |
| 209 } | |
| 210 return CANVAS_LUCIDE_HEADER_HEIGHT + | |
| 211 CANVAS_LUCIDE_PADDING + | |
| 212 (float)rows * CANVAS_LUCIDE_CARD_HEIGHT + | |
| 213 (float)(rows - 1) * CANVAS_LUCIDE_GAP + | |
| 214 CANVAS_LUCIDE_PADDING; | |
| 215 } | |
| 216 | |
| 217 static float Canvas_Lucide_Max_Scroll(const Canvas_Entity *p_entity) | |
| 218 { | |
| 219 return fmaxf( | |
| 220 0.0f, | |
| 221 Canvas_Lucide_Content_Height(p_entity->text) - p_entity->size.y); | |
| 222 } | |
| 223 | |
| 224 static const Canvas_Lucide_Icon *Canvas_Lucide_Find_Icon( | |
| 225 const char *p_name) | |
| 226 { | |
| 227 for (int32 index = 0; index < CANVAS_LUCIDE_ICON_COUNT; index++) { | |
| 228 if (strcmp(CANVAS_LUCIDE_ICONS[index].p_name, p_name) == 0) { | |
| 229 return &CANVAS_LUCIDE_ICONS[index]; | |
| 230 } | |
| 231 } | |
| 232 return NULL; | |
| 233 } | |
| 234 | |
| 235 static void Canvas_Lucide_Draw_Icon( | |
| 236 const Canvas_Lucide_Icon *p_icon, | |
| 237 Vector2 origin, | |
| 238 float scale, | |
| 239 float line_thickness, | |
| 240 Color color) | |
| 241 { | |
| 242 if (!p_icon) return; | |
| 243 for (uint32 segment_index = p_icon->first_segment; | |
| 244 segment_index < p_icon->first_segment + p_icon->segment_count; | |
| 245 segment_index++) { | |
| 246 const Canvas_Lucide_Segment *p_segment = | |
| 247 &CANVAS_LUCIDE_SEGMENTS[segment_index]; | |
| 248 DrawLineEx( | |
| 249 (Vector2){ | |
| 250 origin.x + p_segment->x1 * scale, | |
| 251 origin.y + p_segment->y1 * scale, | |
| 252 }, | |
| 253 (Vector2){ | |
| 254 origin.x + p_segment->x2 * scale, | |
| 255 origin.y + p_segment->y2 * scale, | |
| 256 }, | |
| 257 line_thickness, | |
| 258 color); | |
| 259 } | |
| 260 } | |
| 261 | |
| 262 static Rectangle Canvas_Web_Toolbar_Bounds( | |
| 263 const Canvas_Camera *p_camera, | |
| 264 const Canvas_Entity *p_entity) | |
| 265 { | |
| 266 float zoom = p_camera->zoom; | |
| 267 return (Rectangle){ | |
| 268 p_entity->position.x + CANVAS_WEB_FRAME_INSET / zoom, | |
| 269 p_entity->position.y + CANVAS_WEB_FRAME_INSET / zoom, | |
| 270 p_entity->size.x - | |
| 271 (CANVAS_WEB_FRAME_INSET + CANVAS_WEB_RESIZE_HANDLE) / zoom, | |
| 272 CANVAS_WEB_TOOLBAR_HEIGHT * | |
| 273 (p_entity->web_chrome_amount * | |
| 274 p_entity->web_chrome_amount * | |
| 275 (3.0f - 2.0f * p_entity->web_chrome_amount)) / | |
| 276 zoom, | |
| 277 }; | |
| 278 } | |
| 279 | |
| 280 static Rectangle Canvas_Web_Toolbar_Button_Bounds( | |
| 281 Rectangle toolbar, | |
| 282 float zoom, | |
| 283 int32 button_index) | |
| 284 { | |
| 285 return (Rectangle){ | |
| 286 toolbar.x + (6.0f + (float)button_index * 34.0f) / zoom, | |
| 287 toolbar.y + 7.0f / zoom, | |
| 288 28.0f / zoom, | |
| 289 28.0f / zoom, | |
| 290 }; | |
| 291 } | |
| 292 | |
| 293 static Rectangle Canvas_Web_Address_Bounds(Rectangle toolbar, float zoom) | |
| 294 { | |
| 295 return (Rectangle){ | |
| 296 toolbar.x + 76.0f / zoom, | |
| 297 toolbar.y + 7.0f / zoom, | |
| 298 toolbar.width - 82.0f / zoom, | |
| 299 28.0f / zoom, | |
| 300 }; | |
| 301 } | |
| 302 | |
| 303 static void Canvas_Web_URL_Selection_Bounds( | |
| 304 const Canvas_Scene *p_scene, | |
| 305 int32 *p_start, | |
| 306 int32 *p_end) | |
| 307 { | |
| 308 *p_start = p_scene->web_url_cursor; | |
| 309 *p_end = p_scene->web_url_selection_anchor; | |
| 310 if (*p_start > *p_end) { | |
| 311 int32 swap = *p_start; | |
| 312 *p_start = *p_end; | |
| 313 *p_end = swap; | |
| 314 } | |
| 315 } | |
| 316 | |
| 317 static void Canvas_Web_URL_Delete_Selection(Canvas_Scene *p_scene) | |
| 318 { | |
| 319 int32 start = 0; | |
| 320 int32 end = 0; | |
| 321 Canvas_Web_URL_Selection_Bounds(p_scene, &start, &end); | |
| 322 if (start == end) return; | |
| 323 size_t length = strlen(p_scene->web_url_draft); | |
| 324 memmove( | |
| 325 p_scene->web_url_draft + start, | |
| 326 p_scene->web_url_draft + end, | |
| 327 length - (size_t)end + 1); | |
| 328 p_scene->web_url_cursor = start; | |
| 329 p_scene->web_url_selection_anchor = start; | |
| 330 } | |
| 331 | |
| 332 static void Canvas_Web_URL_Insert(Canvas_Scene *p_scene, const char *p_text) | |
| 333 { | |
| 334 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 335 size_t length = strlen(p_scene->web_url_draft); | |
| 336 size_t available = sizeof(p_scene->web_url_draft) - length - 1; | |
| 337 if (available == 0) return; | |
| 338 char filtered[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 339 size_t filtered_length = 0; | |
| 340 for (const char *p_cursor = p_text; | |
| 341 *p_cursor && filtered_length < available; | |
| 342 p_cursor++) { | |
| 343 if (*p_cursor >= 32 && *p_cursor <= 126) { | |
| 344 filtered[filtered_length++] = *p_cursor; | |
| 345 } | |
| 346 } | |
| 347 if (filtered_length == 0) return; | |
| 348 int32 cursor = p_scene->web_url_cursor; | |
| 349 memmove( | |
| 350 p_scene->web_url_draft + cursor + filtered_length, | |
| 351 p_scene->web_url_draft + cursor, | |
| 352 length - (size_t)cursor + 1); | |
| 353 memcpy(p_scene->web_url_draft + cursor, filtered, filtered_length); | |
| 354 p_scene->web_url_cursor += (int32)filtered_length; | |
| 355 p_scene->web_url_selection_anchor = p_scene->web_url_cursor; | |
| 356 } | |
| 357 | |
| 358 static int32 Canvas_Web_URL_Cursor_At_Point( | |
| 359 Font font, | |
| 360 const Canvas_Scene *p_scene, | |
| 361 Rectangle address, | |
| 362 float zoom, | |
| 363 Vector2 point) | |
| 364 { | |
| 365 float font_size = 13.0f / zoom; | |
| 366 float local_x = | |
| 367 point.x - address.x - 9.0f / zoom + p_scene->web_url_scroll_x; | |
| 368 int32 length = (int32)strlen(p_scene->web_url_draft); | |
| 369 for (int32 cursor = 0; cursor < length; cursor++) { | |
| 370 char prefix[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 371 memcpy(prefix, p_scene->web_url_draft, (size_t)cursor + 1); | |
| 372 prefix[cursor + 1] = '\0'; | |
| 373 float right = MeasureTextEx(font, prefix, font_size, 0.0f).x; | |
| 374 prefix[cursor] = '\0'; | |
| 375 float left = MeasureTextEx(font, prefix, font_size, 0.0f).x; | |
| 376 if (local_x < (left + right) * 0.5f) return cursor; | |
| 377 } | |
| 378 return length; | |
| 379 } | |
| 380 | |
| 381 static void Canvas_Web_URL_Ensure_Cursor_Visible( | |
| 382 Canvas_Scene *p_scene, | |
| 383 Font font, | |
| 384 Rectangle address, | |
| 385 float zoom) | |
| 386 { | |
| 387 char prefix[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 388 memcpy( | |
| 389 prefix, | |
| 390 p_scene->web_url_draft, | |
| 391 (size_t)p_scene->web_url_cursor); | |
| 392 prefix[p_scene->web_url_cursor] = '\0'; | |
| 393 float cursor_x = MeasureTextEx(font, prefix, 13.0f / zoom, 0.0f).x; | |
| 394 float visible_width = address.width - 18.0f / zoom; | |
| 395 if (cursor_x < p_scene->web_url_scroll_x) { | |
| 396 p_scene->web_url_scroll_x = cursor_x; | |
| 397 } else if (cursor_x > p_scene->web_url_scroll_x + visible_width) { | |
| 398 p_scene->web_url_scroll_x = cursor_x - visible_width; | |
| 399 } | |
| 400 if (p_scene->web_url_scroll_x < 0.0f) p_scene->web_url_scroll_x = 0.0f; | |
| 401 } | |
| 402 | |
| 403 static void Canvas_Web_URL_Copy_Selection(Canvas_Scene *p_scene) | |
| 404 { | |
| 405 int32 start = 0; | |
| 406 int32 end = 0; | |
| 407 Canvas_Web_URL_Selection_Bounds(p_scene, &start, &end); | |
| 408 if (start == end) return; | |
| 409 char selected[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 410 memcpy( | |
| 411 selected, | |
| 412 p_scene->web_url_draft + start, | |
| 413 (size_t)(end - start)); | |
| 414 selected[end - start] = '\0'; | |
| 415 SetClipboardText(selected); | |
| 416 } | |
| 417 | |
| 418 static void Canvas_Web_URL_Normalize(char *p_url, size_t capacity) | |
| 419 { | |
| 420 if (!p_url[0] || | |
| 421 strstr(p_url, "://") || | |
| 422 strncmp(p_url, "about:", 6) == 0 || | |
| 423 strncmp(p_url, "data:", 5) == 0 || | |
| 424 strncmp(p_url, "file:", 5) == 0) { | |
| 425 return; | |
| 426 } | |
| 427 char normalized[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 428 snprintf(normalized, sizeof(normalized), "https://%s", p_url); | |
| 429 snprintf(p_url, capacity, "%s", normalized); | |
| 430 } | |
| 431 | |
| 432 Canvas_Web_Chrome_Action Canvas_Web_Chrome_Update_Input( | |
| 433 const Canvas_Camera *p_camera, | |
| 434 Canvas_Scene *p_scene, | |
| 435 Font font, | |
| 436 const char *p_current_url) | |
| 437 { | |
| 438 if (p_scene->selected_index < 0 || | |
| 439 p_scene->selected_index >= | |
| 440 (int32)Dowa_Array_Length(p_scene->p_entities)) { | |
| 441 p_scene->editing_web_url = FALSE; | |
| 442 return CANVAS_WEB_CHROME_NONE; | |
| 443 } | |
| 444 Canvas_Entity *p_entity = | |
| 445 &p_scene->p_entities[p_scene->selected_index]; | |
| 446 if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT || | |
| 447 p_entity->removing) { | |
| 448 p_scene->editing_web_url = FALSE; | |
| 449 return CANVAS_WEB_CHROME_NONE; | |
| 450 } | |
| 451 | |
| 452 Rectangle toolbar = Canvas_Web_Toolbar_Bounds(p_camera, p_entity); | |
| 453 Rectangle address = Canvas_Web_Address_Bounds(toolbar, p_camera->zoom); | |
| 454 Vector2 pointer = Canvas_Camera_Screen_To_World( | |
| 455 p_camera, | |
| 456 GetMousePosition()); | |
| 457 if (p_scene->editing_web_url && | |
| 458 p_scene->web_url_entity_id == p_entity->id) { | |
| 459 boolean shortcut = Canvas_Control_Is_Down(); | |
| 460 boolean extend = Canvas_Shift_Is_Down(); | |
| 461 int32 length = (int32)strlen(p_scene->web_url_draft); | |
| 462 if (shortcut && IsKeyPressed(KEY_A)) { | |
| 463 p_scene->web_url_selection_anchor = 0; | |
| 464 p_scene->web_url_cursor = length; | |
| 465 } | |
| 466 if (shortcut && IsKeyPressed(KEY_C)) { | |
| 467 Canvas_Web_URL_Copy_Selection(p_scene); | |
| 468 } | |
| 469 if (shortcut && IsKeyPressed(KEY_X)) { | |
| 470 Canvas_Web_URL_Copy_Selection(p_scene); | |
| 471 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 472 } | |
| 473 if (shortcut && IsKeyPressed(KEY_V)) { | |
| 474 const char *p_clipboard = GetClipboardText(); | |
| 475 if (p_clipboard) Canvas_Web_URL_Insert(p_scene, p_clipboard); | |
| 476 } | |
| 477 if (Canvas_Key_Activated(KEY_LEFT)) { | |
| 478 int32 cursor = p_scene->web_url_cursor; | |
| 479 if (!extend && | |
| 480 cursor != p_scene->web_url_selection_anchor) { | |
| 481 int32 end = 0; | |
| 482 Canvas_Web_URL_Selection_Bounds(p_scene, &cursor, &end); | |
| 483 } else { | |
| 484 cursor--; | |
| 485 } | |
| 486 if (cursor < 0) cursor = 0; | |
| 487 p_scene->web_url_cursor = cursor; | |
| 488 if (!extend) p_scene->web_url_selection_anchor = cursor; | |
| 489 } | |
| 490 if (Canvas_Key_Activated(KEY_RIGHT)) { | |
| 491 int32 cursor = p_scene->web_url_cursor; | |
| 492 if (!extend && | |
| 493 cursor != p_scene->web_url_selection_anchor) { | |
| 494 int32 start = 0; | |
| 495 Canvas_Web_URL_Selection_Bounds(p_scene, &start, &cursor); | |
| 496 } else { | |
| 497 cursor++; | |
| 498 } | |
| 499 if (cursor > length) cursor = length; | |
| 500 p_scene->web_url_cursor = cursor; | |
| 501 if (!extend) p_scene->web_url_selection_anchor = cursor; | |
| 502 } | |
| 503 if (Canvas_Key_Activated(KEY_HOME)) { | |
| 504 p_scene->web_url_cursor = 0; | |
| 505 if (!extend) p_scene->web_url_selection_anchor = 0; | |
| 506 } | |
| 507 if (Canvas_Key_Activated(KEY_END)) { | |
| 508 p_scene->web_url_cursor = length; | |
| 509 if (!extend) p_scene->web_url_selection_anchor = length; | |
| 510 } | |
| 511 if (Canvas_Key_Activated(KEY_BACKSPACE)) { | |
| 512 if (p_scene->web_url_cursor != | |
| 513 p_scene->web_url_selection_anchor) { | |
| 514 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 515 } else if (p_scene->web_url_cursor > 0) { | |
| 516 p_scene->web_url_selection_anchor = | |
| 517 p_scene->web_url_cursor - 1; | |
| 518 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 519 } | |
| 520 } | |
| 521 if (Canvas_Key_Activated(KEY_DELETE)) { | |
| 522 if (p_scene->web_url_cursor != | |
| 523 p_scene->web_url_selection_anchor) { | |
| 524 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 525 } else if (p_scene->web_url_cursor < length) { | |
| 526 p_scene->web_url_selection_anchor = | |
| 527 p_scene->web_url_cursor + 1; | |
| 528 Canvas_Web_URL_Delete_Selection(p_scene); | |
| 529 } | |
| 530 } | |
| 531 int32 codepoint = GetCharPressed(); | |
| 532 while (codepoint > 0) { | |
| 533 if (!shortcut && codepoint >= 32 && codepoint <= 126) { | |
| 534 char inserted[2] = {(char)codepoint, '\0'}; | |
| 535 Canvas_Web_URL_Insert(p_scene, inserted); | |
| 536 } | |
| 537 codepoint = GetCharPressed(); | |
| 538 } | |
| 539 Canvas_Web_URL_Ensure_Cursor_Visible( | |
| 540 p_scene, | |
| 541 font, | |
| 542 address, | |
| 543 p_camera->zoom); | |
| 544 if (IsKeyPressed(KEY_ESCAPE)) { | |
| 545 p_scene->editing_web_url = FALSE; | |
| 546 } | |
| 547 if (IsKeyPressed(KEY_ENTER)) { | |
| 548 Canvas_Web_URL_Normalize( | |
| 549 p_scene->web_url_draft, | |
| 550 sizeof(p_scene->web_url_draft)); | |
| 551 snprintf( | |
| 552 p_entity->text, | |
| 553 sizeof(p_entity->text), | |
| 554 "%s", | |
| 555 p_scene->web_url_draft); | |
| 556 p_scene->editing_web_url = FALSE; | |
| 557 return CANVAS_WEB_CHROME_NAVIGATE; | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 if (!CheckCollisionPointRec(pointer, toolbar)) { | |
| 562 if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { | |
| 563 p_scene->editing_web_url = FALSE; | |
| 564 return CANVAS_WEB_CHROME_NONE; | |
| 565 } | |
| 566 return p_scene->editing_web_url ? | |
| 567 CANVAS_WEB_CHROME_CAPTURE : | |
| 568 CANVAS_WEB_CHROME_NONE; | |
| 569 } | |
| 570 if (!IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { | |
| 571 return CANVAS_WEB_CHROME_CAPTURE; | |
| 572 } | |
| 573 if (CheckCollisionPointRec( | |
| 574 pointer, | |
| 575 Canvas_Web_Toolbar_Button_Bounds(toolbar, p_camera->zoom, 0))) { | |
| 576 return CANVAS_WEB_CHROME_BACK; | |
| 577 } | |
| 578 if (CheckCollisionPointRec( | |
| 579 pointer, | |
| 580 Canvas_Web_Toolbar_Button_Bounds(toolbar, p_camera->zoom, 1))) { | |
| 581 return CANVAS_WEB_CHROME_FORWARD; | |
| 582 } | |
| 583 if (CheckCollisionPointRec(pointer, address)) { | |
| 584 if (!p_scene->editing_web_url || | |
| 585 p_scene->web_url_entity_id != p_entity->id) { | |
| 586 snprintf( | |
| 587 p_scene->web_url_draft, | |
| 588 sizeof(p_scene->web_url_draft), | |
| 589 "%s", | |
| 590 p_current_url && p_current_url[0] ? | |
| 591 p_current_url : | |
| 592 p_entity->text); | |
| 593 p_scene->web_url_entity_id = p_entity->id; | |
| 594 p_scene->web_url_cursor = | |
| 595 (int32)strlen(p_scene->web_url_draft); | |
| 596 p_scene->web_url_selection_anchor = 0; | |
| 597 p_scene->web_url_scroll_x = 0.0f; | |
| 598 p_scene->editing_web_url = TRUE; | |
| 599 } else { | |
| 600 int32 cursor = Canvas_Web_URL_Cursor_At_Point( | |
| 601 font, | |
| 602 p_scene, | |
| 603 address, | |
| 604 p_camera->zoom, | |
| 605 pointer); | |
| 606 p_scene->web_url_cursor = cursor; | |
| 607 if (!Canvas_Shift_Is_Down()) { | |
| 608 p_scene->web_url_selection_anchor = cursor; | |
| 609 } | |
| 610 } | |
| 611 Canvas_Web_URL_Ensure_Cursor_Visible( | |
| 612 p_scene, | |
| 613 font, | |
| 614 address, | |
| 615 p_camera->zoom); | |
| 616 return CANVAS_WEB_CHROME_CAPTURE; | |
| 617 } | |
| 618 p_scene->editing_web_url = FALSE; | |
| 619 return CANVAS_WEB_CHROME_CAPTURE; | |
| 620 } | |
| 621 | |
| 622 boolean Canvas_Web_Chrome_Is_Editing(const Canvas_Scene *p_scene) | |
| 623 { | |
| 624 return p_scene->editing_web_url; | |
| 56 } | 625 } |
| 57 | 626 |
| 58 void Canvas_Camera_Init(Canvas_Camera *p_camera, int32 width, int32 height) | 627 void Canvas_Camera_Init(Canvas_Camera *p_camera, int32 width, int32 height) |
| 59 { | 628 { |
| 60 *p_camera = (Canvas_Camera){ | 629 *p_camera = (Canvas_Camera){ |
| 127 return (Rectangle){ | 696 return (Rectangle){ |
| 128 .x = min.x, | 697 .x = min.x, |
| 129 .y = min.y, | 698 .y = min.y, |
| 130 .width = max.x - min.x, | 699 .width = max.x - min.x, |
| 131 .height = max.y - min.y, | 700 .height = max.y - min.y, |
| 701 }; | |
| 702 } | |
| 703 | |
| 704 void Canvas_Camera_Fit_Bounds( | |
| 705 Canvas_Camera *p_camera, | |
| 706 Rectangle bounds, | |
| 707 float padding_left, | |
| 708 float padding_top, | |
| 709 float padding_right, | |
| 710 float padding_bottom) | |
| 711 { | |
| 712 float available_width = fmaxf( | |
| 713 1.0f, | |
| 714 (float)p_camera->viewport_width - padding_left - padding_right); | |
| 715 float available_height = fmaxf( | |
| 716 1.0f, | |
| 717 (float)p_camera->viewport_height - padding_top - padding_bottom); | |
| 718 float zoom = fminf( | |
| 719 available_width / fmaxf(bounds.width, 1.0f), | |
| 720 available_height / fmaxf(bounds.height, 1.0f)); | |
| 721 p_camera->zoom = Canvas_Clamp( | |
| 722 zoom, | |
| 723 p_camera->min_zoom, | |
| 724 p_camera->max_zoom); | |
| 725 | |
| 726 Vector2 world_center = { | |
| 727 bounds.x + bounds.width * 0.5f, | |
| 728 bounds.y + bounds.height * 0.5f, | |
| 729 }; | |
| 730 Vector2 desired_screen_center = { | |
| 731 padding_left + available_width * 0.5f, | |
| 732 padding_top + available_height * 0.5f, | |
| 733 }; | |
| 734 Vector2 viewport_center = { | |
| 735 (float)p_camera->viewport_width * 0.5f, | |
| 736 (float)p_camera->viewport_height * 0.5f, | |
| 737 }; | |
| 738 p_camera->target = (Vector2){ | |
| 739 world_center.x - | |
| 740 (desired_screen_center.x - viewport_center.x) / p_camera->zoom, | |
| 741 world_center.y - | |
| 742 (desired_screen_center.y - viewport_center.y) / p_camera->zoom, | |
| 132 }; | 743 }; |
| 133 } | 744 } |
| 134 | 745 |
| 135 static Rectangle Canvas_Entity_Bounds(const Canvas_Entity *p_entity) | 746 static Rectangle Canvas_Entity_Bounds(const Canvas_Entity *p_entity) |
| 136 { | 747 { |
| 254 case CANVAS_ENTITY_TEXT_AREA: | 865 case CANVAS_ENTITY_TEXT_AREA: |
| 255 Canvas_Context_Append(p_writer, "content="); | 866 Canvas_Context_Append(p_writer, "content="); |
| 256 Canvas_Context_Append_Quoted(p_writer, p_entity->text); | 867 Canvas_Context_Append_Quoted(p_writer, p_entity->text); |
| 257 Canvas_Context_Append( | 868 Canvas_Context_Append( |
| 258 p_writer, | 869 p_writer, |
| 259 " editing=%s", | 870 " editing=%s cursor=%d selection_anchor=%d", |
| 260 p_entity->active ? "true" : "false"); | 871 p_entity->active ? "true" : "false", |
| 872 p_entity->text_cursor, | |
| 873 p_entity->text_selection_anchor); | |
| 261 break; | 874 break; |
| 262 case CANVAS_ENTITY_DROPDOWN: | 875 case CANVAS_ENTITY_DROPDOWN: |
| 263 Canvas_Context_Append(p_writer, "selected="); | 876 Canvas_Context_Append(p_writer, "selected="); |
| 264 Canvas_Context_Append_Quoted( | 877 Canvas_Context_Append_Quoted( |
| 265 p_writer, | 878 p_writer, |
| 334 break; | 947 break; |
| 335 case CANVAS_ENTITY_WEB_CONTENT: | 948 case CANVAS_ENTITY_WEB_CONTENT: |
| 336 Canvas_Context_Append(p_writer, "url="); | 949 Canvas_Context_Append(p_writer, "url="); |
| 337 Canvas_Context_Append_Quoted(p_writer, p_entity->text); | 950 Canvas_Context_Append_Quoted(p_writer, p_entity->text); |
| 338 break; | 951 break; |
| 952 case CANVAS_ENTITY_LUCIDE_GALLERY: | |
| 953 Canvas_Context_Append( | |
| 954 p_writer, | |
| 955 "icons=%d query=", | |
| 956 Canvas_Lucide_Count_Matches(p_entity->text)); | |
| 957 Canvas_Context_Append_Quoted(p_writer, p_entity->text); | |
| 958 Canvas_Context_Append( | |
| 959 p_writer, | |
| 960 " search_focused=%s scroll_offset=%d renderer=\"raylib\"", | |
| 961 p_entity->active ? "true" : "false", | |
| 962 p_entity->value); | |
| 963 break; | |
| 339 default: | 964 default: |
| 340 Canvas_Context_Append(p_writer, "value=%d", p_entity->value); | 965 Canvas_Context_Append(p_writer, "value=%d", p_entity->value); |
| 341 break; | 966 break; |
| 342 } | 967 } |
| 343 } | 968 } |
| 375 if (!CheckCollisionRecs(viewport, Canvas_Entity_Bounds(p_entity))) continue; | 1000 if (!CheckCollisionRecs(viewport, Canvas_Entity_Bounds(p_entity))) continue; |
| 376 | 1001 |
| 377 snapshot.visible_count++; | 1002 snapshot.visible_count++; |
| 378 Canvas_Context_Append( | 1003 Canvas_Context_Append( |
| 379 &writer, | 1004 &writer, |
| 380 "- id=%u type=\"%s\" position=[%.1f, %.1f] size=[%.1f, %.1f] pinned=%s\n" | 1005 "- id=%u type=\"%s\" position=[%.1f, %.1f] size=[%.1f, %.1f] pinned=%s", |
| 381 " state: ", | |
| 382 p_entity->id, | 1006 p_entity->id, |
| 383 Canvas_Entity_Type_Name(p_entity->type), | 1007 Canvas_Entity_Type_Name(p_entity->type), |
| 384 p_entity->position.x, | 1008 p_entity->position.x, |
| 385 p_entity->position.y, | 1009 p_entity->position.y, |
| 386 p_entity->size.x, | 1010 p_entity->size.x, |
| 387 p_entity->size.y, | 1011 p_entity->size.y, |
| 388 p_entity->pinned ? "true" : "false"); | 1012 p_entity->pinned ? "true" : "false"); |
| 1013 Canvas_Context_Append( | |
| 1014 &writer, | |
| 1015 " visibility=%.2f removing=%s\n" | |
| 1016 " state: ", | |
| 1017 p_entity->visibility_amount, | |
| 1018 p_entity->removing ? "true" : "false"); | |
| 389 Canvas_Context_Append_Entity_State(&writer, p_entity); | 1019 Canvas_Context_Append_Entity_State(&writer, p_entity); |
| 390 Canvas_Context_Append(&writer, "\n"); | 1020 Canvas_Context_Append(&writer, "\n"); |
| 391 } | 1021 } |
| 392 if (snapshot.visible_count == 0) { | 1022 if (snapshot.visible_count == 0) { |
| 393 Canvas_Context_Append(&writer, " (none)\n"); | 1023 Canvas_Context_Append(&writer, " (none)\n"); |
| 428 .position = position, | 1058 .position = position, |
| 429 .size = {160.0f, 100.0f}, | 1059 .size = {160.0f, 100.0f}, |
| 430 .color = Canvas_Entity_Color(index), | 1060 .color = Canvas_Entity_Color(index), |
| 431 .text = {0}, | 1061 .text = {0}, |
| 432 .value = 0, | 1062 .value = 0, |
| 1063 .visibility_amount = 0.0f, | |
| 433 .active = FALSE, | 1064 .active = FALSE, |
| 434 .pinned = FALSE, | 1065 .pinned = FALSE, |
| 1066 .removing = FALSE, | |
| 435 .hover_amount = 0.0f, | 1067 .hover_amount = 0.0f, |
| 436 .animation_amount = 0.0f, | 1068 .animation_amount = 0.0f, |
| 437 }; | 1069 }; |
| 438 | 1070 |
| 439 if (type == CANVAS_ENTITY_CIRCLE) entity.size = (Vector2){56.0f, 56.0f}; | 1071 if (type == CANVAS_ENTITY_CIRCLE) entity.size = (Vector2){56.0f, 56.0f}; |
| 489 } | 1121 } |
| 490 if (type == CANVAS_ENTITY_WEB_CONTENT) { | 1122 if (type == CANVAS_ENTITY_WEB_CONTENT) { |
| 491 entity.size = (Vector2){720.0f, 460.0f}; | 1123 entity.size = (Vector2){720.0f, 460.0f}; |
| 492 entity.active = TRUE; | 1124 entity.active = TRUE; |
| 493 snprintf(entity.text, sizeof(entity.text), "https://mrjunejune.com"); | 1125 snprintf(entity.text, sizeof(entity.text), "https://mrjunejune.com"); |
| 1126 } | |
| 1127 if (type == CANVAS_ENTITY_LUCIDE_GALLERY) { | |
| 1128 entity.size = (Vector2){1440.0f, 1040.0f}; | |
| 494 } | 1129 } |
| 495 | 1130 |
| 496 Dowa_Array_Push_Arena(p_scene->p_entities, entity, p_scene->p_arena); | 1131 Dowa_Array_Push_Arena(p_scene->p_entities, entity, p_scene->p_arena); |
| 497 return TRUE; | 1132 return TRUE; |
| 498 } | 1133 } |
| 504 p_scene->selected_index = -1; | 1139 p_scene->selected_index = -1; |
| 505 p_scene->pressed_index = -1; | 1140 p_scene->pressed_index = -1; |
| 506 p_scene->hover_index = -1; | 1141 p_scene->hover_index = -1; |
| 507 p_scene->dragging = FALSE; | 1142 p_scene->dragging = FALSE; |
| 508 p_scene->resizing = FALSE; | 1143 p_scene->resizing = FALSE; |
| 1144 p_scene->selecting_text = FALSE; | |
| 1145 p_scene->editing_web_url = FALSE; | |
| 1146 p_scene->web_url_entity_id = 0; | |
| 1147 p_scene->web_url_draft[0] = '\0'; | |
| 509 p_scene->notification_stack_amount = 0.0f; | 1148 p_scene->notification_stack_amount = 0.0f; |
| 1149 } | |
| 1150 | |
| 1151 void Canvas_Scene_Fade_Out_All(Canvas_Scene *p_scene) | |
| 1152 { | |
| 1153 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { | |
| 1154 p_scene->p_entities[index].removing = TRUE; | |
| 1155 p_scene->p_entities[index].active = FALSE; | |
| 1156 } | |
| 1157 p_scene->selected_index = -1; | |
| 1158 p_scene->pressed_index = -1; | |
| 1159 p_scene->hover_index = -1; | |
| 1160 p_scene->dragging = FALSE; | |
| 1161 p_scene->resizing = FALSE; | |
| 1162 p_scene->selecting_text = FALSE; | |
| 1163 p_scene->editing_web_url = FALSE; | |
| 1164 } | |
| 1165 | |
| 1166 void Canvas_Scene_Fade_Out_Selected(Canvas_Scene *p_scene) | |
| 1167 { | |
| 1168 if (p_scene->selected_index < 0 || | |
| 1169 p_scene->selected_index >= (int32)Dowa_Array_Length(p_scene->p_entities)) { | |
| 1170 return; | |
| 1171 } | |
| 1172 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; | |
| 1173 p_entity->removing = TRUE; | |
| 1174 p_entity->active = FALSE; | |
| 1175 p_scene->selected_index = -1; | |
| 1176 p_scene->pressed_index = -1; | |
| 1177 p_scene->hover_index = -1; | |
| 1178 p_scene->dragging = FALSE; | |
| 1179 p_scene->resizing = FALSE; | |
| 1180 p_scene->selecting_text = FALSE; | |
| 1181 p_scene->editing_web_url = FALSE; | |
| 510 } | 1182 } |
| 511 | 1183 |
| 512 void Canvas_Scene_Add_Demo(Canvas_Scene *p_scene) | 1184 void Canvas_Scene_Add_Demo(Canvas_Scene *p_scene) |
| 513 { | 1185 { |
| 514 Canvas_Scene_Clear(p_scene); | 1186 Canvas_Scene_Clear(p_scene); |
| 548 Canvas_Entity *p_entity = | 1220 Canvas_Entity *p_entity = |
| 549 &p_scene->p_entities[Dowa_Array_Length(p_scene->p_entities) - 1]; | 1221 &p_scene->p_entities[Dowa_Array_Length(p_scene->p_entities) - 1]; |
| 550 p_entity->size = (Vector2){236.0f, 176.0f}; | 1222 p_entity->size = (Vector2){236.0f, 176.0f}; |
| 551 } | 1223 } |
| 552 } | 1224 } |
| 1225 } | |
| 1226 | |
| 1227 boolean Canvas_Scene_Show_Lucide_Gallery( | |
| 1228 Canvas_Scene *p_scene, | |
| 1229 Canvas_Camera *p_camera) | |
| 1230 { | |
| 1231 int32 gallery_index = -1; | |
| 1232 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { | |
| 1233 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; | |
| 1234 if (p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY) { | |
| 1235 gallery_index = (int32)index; | |
| 1236 break; | |
| 1237 } | |
| 1238 } | |
| 1239 | |
| 1240 if (gallery_index < 0) { | |
| 1241 if (!Canvas_Scene_Add( | |
| 1242 p_scene, | |
| 1243 CANVAS_ENTITY_LUCIDE_GALLERY, | |
| 1244 (Vector2){1200.0f, -520.0f})) { | |
| 1245 return FALSE; | |
| 1246 } | |
| 1247 gallery_index = (int32)Dowa_Array_Length(p_scene->p_entities) - 1; | |
| 1248 } | |
| 1249 | |
| 1250 Canvas_Entity *p_gallery = &p_scene->p_entities[gallery_index]; | |
| 1251 p_gallery->removing = FALSE; | |
| 1252 p_scene->selected_index = gallery_index; | |
| 1253 Canvas_Camera_Fit_Bounds( | |
| 1254 p_camera, | |
| 1255 (Rectangle){ | |
| 1256 p_gallery->position.x, | |
| 1257 p_gallery->position.y, | |
| 1258 p_gallery->size.x, | |
| 1259 p_gallery->size.y, | |
| 1260 }, | |
| 1261 320.0f, | |
| 1262 56.0f, | |
| 1263 56.0f, | |
| 1264 56.0f); | |
| 1265 return TRUE; | |
| 553 } | 1266 } |
| 554 | 1267 |
| 555 static float Canvas_Distance_To_Segment(Vector2 point, Vector2 start, Vector2 end) | 1268 static float Canvas_Distance_To_Segment(Vector2 point, Vector2 start, Vector2 end) |
| 556 { | 1269 { |
| 557 Vector2 segment = {end.x - start.x, end.y - start.y}; | 1270 Vector2 segment = {end.x - start.x, end.y - start.y}; |
| 621 p_entity->position.y, | 1334 p_entity->position.y, |
| 622 p_entity->size.x, | 1335 p_entity->size.x, |
| 623 height, | 1336 height, |
| 624 }) ? TRUE : FALSE; | 1337 }) ? TRUE : FALSE; |
| 625 } | 1338 } |
| 1339 case CANVAS_ENTITY_LUCIDE_GALLERY: | |
| 1340 return CheckCollisionPointRec( | |
| 1341 point, | |
| 1342 (Rectangle){ | |
| 1343 p_entity->position.x, | |
| 1344 p_entity->position.y, | |
| 1345 p_entity->size.x, | |
| 1346 p_entity->size.y, | |
| 1347 }) ? TRUE : FALSE; | |
| 626 case CANVAS_ENTITY_NOTIFICATION: | 1348 case CANVAS_ENTITY_NOTIFICATION: |
| 627 if (p_entity->animation_amount <= 0.01f) return FALSE; | 1349 if (p_entity->animation_amount <= 0.01f) return FALSE; |
| 628 return CheckCollisionPointRec( | 1350 return CheckCollisionPointRec( |
| 629 point, | 1351 point, |
| 630 (Rectangle){ | 1352 (Rectangle){ |
| 681 Vector2 world_pointer, | 1403 Vector2 world_pointer, |
| 682 float zoom) | 1404 float zoom) |
| 683 { | 1405 { |
| 684 for (int32 index = (int32)Dowa_Array_Length(p_scene->p_entities) - 1; index >= 0; index--) { | 1406 for (int32 index = (int32)Dowa_Array_Length(p_scene->p_entities) - 1; index >= 0; index--) { |
| 685 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; | 1407 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; |
| 1408 if (p_entity->removing) continue; | |
| 686 if (p_entity->type == CANVAS_ENTITY_DROPDOWN && | 1409 if (p_entity->type == CANVAS_ENTITY_DROPDOWN && |
| 687 p_entity->active && | 1410 p_entity->active && |
| 688 Canvas_Entity_Contains(p_entity, world_pointer, zoom)) { | 1411 Canvas_Entity_Contains(p_entity, world_pointer, zoom)) { |
| 689 return index; | 1412 return index; |
| 690 } | 1413 } |
| 691 } | 1414 } |
| 692 | 1415 |
| 693 for (int32 index = (int32)Dowa_Array_Length(p_scene->p_entities) - 1; index >= 0; index--) { | 1416 for (int32 index = (int32)Dowa_Array_Length(p_scene->p_entities) - 1; index >= 0; index--) { |
| 1417 if (p_scene->p_entities[index].removing) continue; | |
| 694 if (Canvas_Entity_Contains(&p_scene->p_entities[index], world_pointer, zoom)) return index; | 1418 if (Canvas_Entity_Contains(&p_scene->p_entities[index], world_pointer, zoom)) return index; |
| 1419 } | |
| 1420 return -1; | |
| 1421 } | |
| 1422 | |
| 1423 int32 Canvas_Scene_Topmost_Visual( | |
| 1424 const Canvas_Scene *p_scene, | |
| 1425 Vector2 world_pointer, | |
| 1426 float zoom) | |
| 1427 { | |
| 1428 for (int32 index = | |
| 1429 (int32)Dowa_Array_Length(p_scene->p_entities) - 1; | |
| 1430 index >= 0; | |
| 1431 index--) { | |
| 1432 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; | |
| 1433 if (p_entity->removing) continue; | |
| 1434 if (p_entity->type == CANVAS_ENTITY_WEB_CONTENT) { | |
| 1435 if (CheckCollisionPointRec( | |
| 1436 world_pointer, | |
| 1437 (Rectangle){ | |
| 1438 p_entity->position.x, | |
| 1439 p_entity->position.y, | |
| 1440 p_entity->size.x, | |
| 1441 p_entity->size.y, | |
| 1442 })) { | |
| 1443 return index; | |
| 1444 } | |
| 1445 continue; | |
| 1446 } | |
| 1447 if (Canvas_Entity_Contains(p_entity, world_pointer, zoom)) { | |
| 1448 return index; | |
| 1449 } | |
| 695 } | 1450 } |
| 696 return -1; | 1451 return -1; |
| 697 } | 1452 } |
| 698 | 1453 |
| 699 static void Canvas_Scene_Activate(Canvas_Scene *p_scene, int32 index, Vector2 world_pointer) | 1454 static void Canvas_Scene_Activate(Canvas_Scene *p_scene, int32 index, Vector2 world_pointer) |
| 761 default: | 1516 default: |
| 762 break; | 1517 break; |
| 763 } | 1518 } |
| 764 } | 1519 } |
| 765 | 1520 |
| 766 static void Canvas_Scene_Update_Text_Input(Canvas_Scene *p_scene) | 1521 static boolean Canvas_Shift_Is_Down(void) |
| 1522 { | |
| 1523 return IsKeyDown(KEY_LEFT_SHIFT) || IsKeyDown(KEY_RIGHT_SHIFT); | |
| 1524 } | |
| 1525 | |
| 1526 static boolean Canvas_Alt_Is_Down(void) | |
| 1527 { | |
| 1528 return IsKeyDown(KEY_LEFT_ALT) || IsKeyDown(KEY_RIGHT_ALT); | |
| 1529 } | |
| 1530 | |
| 1531 static boolean Canvas_Super_Is_Down(void) | |
| 1532 { | |
| 1533 return IsKeyDown(KEY_LEFT_SUPER) || IsKeyDown(KEY_RIGHT_SUPER); | |
| 1534 } | |
| 1535 | |
| 1536 static boolean Canvas_Key_Activated(int32 key) | |
| 1537 { | |
| 1538 return IsKeyPressed(key) || IsKeyPressedRepeat(key); | |
| 1539 } | |
| 1540 | |
| 1541 static float Canvas_Text_Measure_Range( | |
| 1542 Font font, | |
| 1543 const char *p_text, | |
| 1544 int32 start, | |
| 1545 int32 end) | |
| 1546 { | |
| 1547 if (end <= start) return 0.0f; | |
| 1548 char buffer[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1549 int32 length = end - start; | |
| 1550 if (length >= (int32)sizeof(buffer)) length = (int32)sizeof(buffer) - 1; | |
| 1551 memcpy(buffer, p_text + start, (size_t)length); | |
| 1552 buffer[length] = '\0'; | |
| 1553 return MeasureTextEx( | |
| 1554 font, | |
| 1555 buffer, | |
| 1556 CANVAS_TEXT_AREA_FONT_SIZE, | |
| 1557 CANVAS_TEXT_AREA_SPACING).x; | |
| 1558 } | |
| 1559 | |
| 1560 static int32 Canvas_Text_Build_Lines( | |
| 1561 Font font, | |
| 1562 const char *p_text, | |
| 1563 float max_width, | |
| 1564 Canvas_Text_Line *p_lines, | |
| 1565 int32 line_capacity) | |
| 1566 { | |
| 1567 int32 length = (int32)strlen(p_text); | |
| 1568 int32 line_count = 0; | |
| 1569 int32 start = 0; | |
| 1570 | |
| 1571 while (start <= length && line_count < line_capacity) { | |
| 1572 int32 index = start; | |
| 1573 int32 last_space = -1; | |
| 1574 float width = 0.0f; | |
| 1575 boolean ended_with_newline = FALSE; | |
| 1576 | |
| 1577 while (index < length) { | |
| 1578 if (p_text[index] == '\n') { | |
| 1579 ended_with_newline = TRUE; | |
| 1580 break; | |
| 1581 } | |
| 1582 float character_width = Canvas_Text_Measure_Range( | |
| 1583 font, | |
| 1584 p_text, | |
| 1585 index, | |
| 1586 index + 1); | |
| 1587 if (width + character_width > max_width && index > start) { | |
| 1588 if (last_space >= start) index = last_space + 1; | |
| 1589 break; | |
| 1590 } | |
| 1591 width += character_width; | |
| 1592 if (p_text[index] == ' ' || p_text[index] == '\t') { | |
| 1593 last_space = index; | |
| 1594 } | |
| 1595 index++; | |
| 1596 } | |
| 1597 | |
| 1598 int32 end = index; | |
| 1599 int32 next = ended_with_newline ? index + 1 : index; | |
| 1600 p_lines[line_count++] = (Canvas_Text_Line){ | |
| 1601 .start = start, | |
| 1602 .end = end, | |
| 1603 .next = next, | |
| 1604 .width = Canvas_Text_Measure_Range(font, p_text, start, end), | |
| 1605 }; | |
| 1606 if (index >= length) break; | |
| 1607 start = next; | |
| 1608 } | |
| 1609 | |
| 1610 return line_count; | |
| 1611 } | |
| 1612 | |
| 1613 static Rectangle Canvas_Text_Area_Content_Bounds( | |
| 1614 const Canvas_Entity *p_entity) | |
| 1615 { | |
| 1616 return (Rectangle){ | |
| 1617 p_entity->position.x + CANVAS_TEXT_AREA_PADDING, | |
| 1618 p_entity->position.y + CANVAS_TEXT_AREA_PADDING, | |
| 1619 p_entity->size.x - CANVAS_TEXT_AREA_PADDING * 2.0f, | |
| 1620 p_entity->size.y - CANVAS_TEXT_AREA_PADDING * 2.0f, | |
| 1621 }; | |
| 1622 } | |
| 1623 | |
| 1624 static int32 Canvas_Text_Line_For_Cursor( | |
| 1625 const Canvas_Text_Line *p_lines, | |
| 1626 int32 line_count, | |
| 1627 int32 cursor) | |
| 1628 { | |
| 1629 for (int32 line = 0; line < line_count; line++) { | |
| 1630 if (cursor <= p_lines[line].end) return line; | |
| 1631 if (line + 1 < line_count && cursor < p_lines[line + 1].start) { | |
| 1632 return line; | |
| 1633 } | |
| 1634 } | |
| 1635 return line_count > 0 ? line_count - 1 : 0; | |
| 1636 } | |
| 1637 | |
| 1638 static int32 Canvas_Text_Cursor_At_Point( | |
| 1639 Font font, | |
| 1640 const Canvas_Entity *p_entity, | |
| 1641 Vector2 point) | |
| 1642 { | |
| 1643 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1644 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 1645 int32 line_count = Canvas_Text_Build_Lines( | |
| 1646 font, | |
| 1647 p_entity->text, | |
| 1648 content.width, | |
| 1649 lines, | |
| 1650 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 1651 int32 line = (int32)floorf( | |
| 1652 (point.y - content.y + p_entity->text_scroll_y) / | |
| 1653 CANVAS_TEXT_AREA_LINE_HEIGHT); | |
| 1654 if (line < 0) line = 0; | |
| 1655 if (line >= line_count) line = line_count - 1; | |
| 1656 float local_x = point.x - content.x; | |
| 1657 if (local_x <= 0.0f) return lines[line].start; | |
| 1658 | |
| 1659 for (int32 cursor = lines[line].start; cursor < lines[line].end; cursor++) { | |
| 1660 float left = Canvas_Text_Measure_Range( | |
| 1661 font, | |
| 1662 p_entity->text, | |
| 1663 lines[line].start, | |
| 1664 cursor); | |
| 1665 float right = Canvas_Text_Measure_Range( | |
| 1666 font, | |
| 1667 p_entity->text, | |
| 1668 lines[line].start, | |
| 1669 cursor + 1); | |
| 1670 if (local_x < (left + right) * 0.5f) return cursor; | |
| 1671 } | |
| 1672 return lines[line].end; | |
| 1673 } | |
| 1674 | |
| 1675 static void Canvas_Text_Selection_Bounds( | |
| 1676 const Canvas_Entity *p_entity, | |
| 1677 int32 *p_start, | |
| 1678 int32 *p_end) | |
| 1679 { | |
| 1680 *p_start = p_entity->text_cursor; | |
| 1681 *p_end = p_entity->text_selection_anchor; | |
| 1682 if (*p_start > *p_end) { | |
| 1683 int32 swap = *p_start; | |
| 1684 *p_start = *p_end; | |
| 1685 *p_end = swap; | |
| 1686 } | |
| 1687 } | |
| 1688 | |
| 1689 static boolean Canvas_Text_Has_Selection(const Canvas_Entity *p_entity) | |
| 1690 { | |
| 1691 return p_entity->text_cursor != p_entity->text_selection_anchor; | |
| 1692 } | |
| 1693 | |
| 1694 static void Canvas_Text_Delete_Selection(Canvas_Entity *p_entity) | |
| 1695 { | |
| 1696 int32 start = 0; | |
| 1697 int32 end = 0; | |
| 1698 Canvas_Text_Selection_Bounds(p_entity, &start, &end); | |
| 1699 if (start == end) return; | |
| 1700 size_t length = strlen(p_entity->text); | |
| 1701 memmove( | |
| 1702 p_entity->text + start, | |
| 1703 p_entity->text + end, | |
| 1704 length - (size_t)end + 1); | |
| 1705 p_entity->text_cursor = start; | |
| 1706 p_entity->text_selection_anchor = start; | |
| 1707 } | |
| 1708 | |
| 1709 static void Canvas_Text_Insert( | |
| 1710 Canvas_Entity *p_entity, | |
| 1711 const char *p_text) | |
| 1712 { | |
| 1713 Canvas_Text_Delete_Selection(p_entity); | |
| 1714 size_t length = strlen(p_entity->text); | |
| 1715 size_t available = | |
| 1716 sizeof(p_entity->text) - length - 1; | |
| 1717 if (available == 0) return; | |
| 1718 | |
| 1719 char filtered[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1720 size_t filtered_length = 0; | |
| 1721 for (const char *p_cursor = p_text; | |
| 1722 *p_cursor && filtered_length < available; | |
| 1723 p_cursor++) { | |
| 1724 if (*p_cursor == '\r') continue; | |
| 1725 if (*p_cursor == '\n' || *p_cursor == '\t' || | |
| 1726 (*p_cursor >= 32 && *p_cursor <= 126)) { | |
| 1727 filtered[filtered_length++] = *p_cursor == '\t' ? ' ' : *p_cursor; | |
| 1728 } | |
| 1729 } | |
| 1730 if (filtered_length == 0) return; | |
| 1731 | |
| 1732 int32 cursor = p_entity->text_cursor; | |
| 1733 memmove( | |
| 1734 p_entity->text + cursor + filtered_length, | |
| 1735 p_entity->text + cursor, | |
| 1736 length - (size_t)cursor + 1); | |
| 1737 memcpy(p_entity->text + cursor, filtered, filtered_length); | |
| 1738 p_entity->text_cursor += (int32)filtered_length; | |
| 1739 p_entity->text_selection_anchor = p_entity->text_cursor; | |
| 1740 } | |
| 1741 | |
| 1742 static int32 Canvas_Text_Previous_Word(const char *p_text, int32 cursor) | |
| 1743 { | |
| 1744 while (cursor > 0 && p_text[cursor - 1] == ' ') cursor--; | |
| 1745 while (cursor > 0 && | |
| 1746 p_text[cursor - 1] != ' ' && | |
| 1747 p_text[cursor - 1] != '\n') { | |
| 1748 cursor--; | |
| 1749 } | |
| 1750 return cursor; | |
| 1751 } | |
| 1752 | |
| 1753 static int32 Canvas_Text_Next_Word(const char *p_text, int32 cursor) | |
| 1754 { | |
| 1755 int32 length = (int32)strlen(p_text); | |
| 1756 while (cursor < length && | |
| 1757 p_text[cursor] != ' ' && | |
| 1758 p_text[cursor] != '\n') { | |
| 1759 cursor++; | |
| 1760 } | |
| 1761 while (cursor < length && p_text[cursor] == ' ') cursor++; | |
| 1762 return cursor; | |
| 1763 } | |
| 1764 | |
| 1765 static void Canvas_Text_Move_Cursor( | |
| 1766 Canvas_Entity *p_entity, | |
| 1767 int32 cursor, | |
| 1768 boolean extend_selection) | |
| 1769 { | |
| 1770 int32 length = (int32)strlen(p_entity->text); | |
| 1771 if (cursor < 0) cursor = 0; | |
| 1772 if (cursor > length) cursor = length; | |
| 1773 p_entity->text_cursor = cursor; | |
| 1774 if (!extend_selection) p_entity->text_selection_anchor = cursor; | |
| 1775 } | |
| 1776 | |
| 1777 static void Canvas_Text_Ensure_Cursor_Visible( | |
| 1778 Canvas_Entity *p_entity, | |
| 1779 Font font) | |
| 1780 { | |
| 1781 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1782 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 1783 int32 line_count = Canvas_Text_Build_Lines( | |
| 1784 font, | |
| 1785 p_entity->text, | |
| 1786 content.width, | |
| 1787 lines, | |
| 1788 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 1789 int32 line = Canvas_Text_Line_For_Cursor( | |
| 1790 lines, | |
| 1791 line_count, | |
| 1792 p_entity->text_cursor); | |
| 1793 float cursor_top = (float)line * CANVAS_TEXT_AREA_LINE_HEIGHT; | |
| 1794 float cursor_bottom = cursor_top + CANVAS_TEXT_AREA_LINE_HEIGHT; | |
| 1795 if (cursor_top < p_entity->text_scroll_y) { | |
| 1796 p_entity->text_scroll_y = cursor_top; | |
| 1797 } else if (cursor_bottom > | |
| 1798 p_entity->text_scroll_y + content.height) { | |
| 1799 p_entity->text_scroll_y = cursor_bottom - content.height; | |
| 1800 } | |
| 1801 float max_scroll = fmaxf( | |
| 1802 0.0f, | |
| 1803 (float)line_count * CANVAS_TEXT_AREA_LINE_HEIGHT - content.height); | |
| 1804 p_entity->text_scroll_y = Canvas_Clamp( | |
| 1805 p_entity->text_scroll_y, | |
| 1806 0.0f, | |
| 1807 max_scroll); | |
| 1808 } | |
| 1809 | |
| 1810 static void Canvas_Text_Update_Keyboard(Canvas_Entity *p_entity, Font font) | |
| 1811 { | |
| 1812 boolean shortcut = Canvas_Control_Is_Down(); | |
| 1813 boolean control = IsKeyDown(KEY_LEFT_CONTROL) || IsKeyDown(KEY_RIGHT_CONTROL); | |
| 1814 boolean super = Canvas_Super_Is_Down(); | |
| 1815 boolean word_modifier = control || Canvas_Alt_Is_Down(); | |
| 1816 boolean extend = Canvas_Shift_Is_Down(); | |
| 1817 int32 length = (int32)strlen(p_entity->text); | |
| 1818 int32 original_cursor = p_entity->text_cursor; | |
| 1819 int32 original_length = length; | |
| 1820 | |
| 1821 if (shortcut && IsKeyPressed(KEY_A)) { | |
| 1822 p_entity->text_selection_anchor = 0; | |
| 1823 p_entity->text_cursor = length; | |
| 1824 } | |
| 1825 if (shortcut && IsKeyPressed(KEY_C) && Canvas_Text_Has_Selection(p_entity)) { | |
| 1826 int32 start = 0; | |
| 1827 int32 end = 0; | |
| 1828 char selected[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1829 Canvas_Text_Selection_Bounds(p_entity, &start, &end); | |
| 1830 memcpy(selected, p_entity->text + start, (size_t)(end - start)); | |
| 1831 selected[end - start] = '\0'; | |
| 1832 SetClipboardText(selected); | |
| 1833 } | |
| 1834 if (shortcut && IsKeyPressed(KEY_X) && Canvas_Text_Has_Selection(p_entity)) { | |
| 1835 int32 start = 0; | |
| 1836 int32 end = 0; | |
| 1837 char selected[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1838 Canvas_Text_Selection_Bounds(p_entity, &start, &end); | |
| 1839 memcpy(selected, p_entity->text + start, (size_t)(end - start)); | |
| 1840 selected[end - start] = '\0'; | |
| 1841 SetClipboardText(selected); | |
| 1842 Canvas_Text_Delete_Selection(p_entity); | |
| 1843 } | |
| 1844 if (shortcut && IsKeyPressed(KEY_V)) { | |
| 1845 const char *p_clipboard = GetClipboardText(); | |
| 1846 if (p_clipboard) Canvas_Text_Insert(p_entity, p_clipboard); | |
| 1847 } | |
| 1848 | |
| 1849 if (Canvas_Key_Activated(KEY_LEFT)) { | |
| 1850 int32 cursor = p_entity->text_cursor; | |
| 1851 if (!extend && Canvas_Text_Has_Selection(p_entity)) { | |
| 1852 int32 end = 0; | |
| 1853 Canvas_Text_Selection_Bounds(p_entity, &cursor, &end); | |
| 1854 } else if (super) { | |
| 1855 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1856 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 1857 int32 count = Canvas_Text_Build_Lines( | |
| 1858 font, | |
| 1859 p_entity->text, | |
| 1860 content.width, | |
| 1861 lines, | |
| 1862 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 1863 cursor = lines[Canvas_Text_Line_For_Cursor( | |
| 1864 lines, | |
| 1865 count, | |
| 1866 cursor)].start; | |
| 1867 } else { | |
| 1868 cursor = word_modifier ? | |
| 1869 Canvas_Text_Previous_Word(p_entity->text, cursor) : | |
| 1870 cursor - 1; | |
| 1871 } | |
| 1872 Canvas_Text_Move_Cursor(p_entity, cursor, extend); | |
| 1873 } | |
| 1874 if (Canvas_Key_Activated(KEY_RIGHT)) { | |
| 1875 int32 cursor = p_entity->text_cursor; | |
| 1876 if (!extend && Canvas_Text_Has_Selection(p_entity)) { | |
| 1877 int32 start = 0; | |
| 1878 Canvas_Text_Selection_Bounds(p_entity, &start, &cursor); | |
| 1879 } else if (super) { | |
| 1880 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1881 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 1882 int32 count = Canvas_Text_Build_Lines( | |
| 1883 font, | |
| 1884 p_entity->text, | |
| 1885 content.width, | |
| 1886 lines, | |
| 1887 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 1888 cursor = lines[Canvas_Text_Line_For_Cursor( | |
| 1889 lines, | |
| 1890 count, | |
| 1891 cursor)].end; | |
| 1892 } else { | |
| 1893 cursor = word_modifier ? | |
| 1894 Canvas_Text_Next_Word(p_entity->text, cursor) : | |
| 1895 cursor + 1; | |
| 1896 } | |
| 1897 Canvas_Text_Move_Cursor(p_entity, cursor, extend); | |
| 1898 } | |
| 1899 | |
| 1900 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 1901 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 1902 int32 line_count = Canvas_Text_Build_Lines( | |
| 1903 font, | |
| 1904 p_entity->text, | |
| 1905 content.width, | |
| 1906 lines, | |
| 1907 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 1908 int32 current_line = Canvas_Text_Line_For_Cursor( | |
| 1909 lines, | |
| 1910 line_count, | |
| 1911 p_entity->text_cursor); | |
| 1912 float cursor_x = Canvas_Text_Measure_Range( | |
| 1913 font, | |
| 1914 p_entity->text, | |
| 1915 lines[current_line].start, | |
| 1916 p_entity->text_cursor); | |
| 1917 if (Canvas_Key_Activated(KEY_UP) || Canvas_Key_Activated(KEY_DOWN)) { | |
| 1918 int32 target_line = current_line + | |
| 1919 (IsKeyDown(KEY_UP) ? -1 : 1); | |
| 1920 if (target_line < 0) target_line = 0; | |
| 1921 if (target_line >= line_count) target_line = line_count - 1; | |
| 1922 Vector2 target = { | |
| 1923 content.x + cursor_x, | |
| 1924 content.y + | |
| 1925 (float)target_line * CANVAS_TEXT_AREA_LINE_HEIGHT - | |
| 1926 p_entity->text_scroll_y + | |
| 1927 CANVAS_TEXT_AREA_LINE_HEIGHT * 0.5f, | |
| 1928 }; | |
| 1929 Canvas_Text_Move_Cursor( | |
| 1930 p_entity, | |
| 1931 Canvas_Text_Cursor_At_Point(font, p_entity, target), | |
| 1932 extend); | |
| 1933 } | |
| 1934 if (Canvas_Key_Activated(KEY_HOME)) { | |
| 1935 Canvas_Text_Move_Cursor( | |
| 1936 p_entity, | |
| 1937 lines[current_line].start, | |
| 1938 extend); | |
| 1939 } | |
| 1940 if (Canvas_Key_Activated(KEY_END)) { | |
| 1941 Canvas_Text_Move_Cursor( | |
| 1942 p_entity, | |
| 1943 lines[current_line].end, | |
| 1944 extend); | |
| 1945 } | |
| 1946 | |
| 1947 if (Canvas_Key_Activated(KEY_BACKSPACE)) { | |
| 1948 if (Canvas_Text_Has_Selection(p_entity)) { | |
| 1949 Canvas_Text_Delete_Selection(p_entity); | |
| 1950 } else if (p_entity->text_cursor > 0) { | |
| 1951 int32 start = p_entity->text_cursor - 1; | |
| 1952 if (super) { | |
| 1953 start = lines[Canvas_Text_Line_For_Cursor( | |
| 1954 lines, | |
| 1955 line_count, | |
| 1956 p_entity->text_cursor)].start; | |
| 1957 } else if (word_modifier) { | |
| 1958 start = Canvas_Text_Previous_Word( | |
| 1959 p_entity->text, | |
| 1960 p_entity->text_cursor); | |
| 1961 } | |
| 1962 p_entity->text_selection_anchor = start; | |
| 1963 Canvas_Text_Delete_Selection(p_entity); | |
| 1964 } | |
| 1965 } | |
| 1966 if (Canvas_Key_Activated(KEY_DELETE)) { | |
| 1967 if (Canvas_Text_Has_Selection(p_entity)) { | |
| 1968 Canvas_Text_Delete_Selection(p_entity); | |
| 1969 } else if (p_entity->text_cursor < length) { | |
| 1970 int32 end = p_entity->text_cursor + 1; | |
| 1971 if (super) { | |
| 1972 end = lines[Canvas_Text_Line_For_Cursor( | |
| 1973 lines, | |
| 1974 line_count, | |
| 1975 p_entity->text_cursor)].end; | |
| 1976 } else if (word_modifier) { | |
| 1977 end = Canvas_Text_Next_Word( | |
| 1978 p_entity->text, | |
| 1979 p_entity->text_cursor); | |
| 1980 } | |
| 1981 p_entity->text_selection_anchor = end; | |
| 1982 Canvas_Text_Delete_Selection(p_entity); | |
| 1983 } | |
| 1984 } | |
| 1985 if (IsKeyPressed(KEY_ENTER)) Canvas_Text_Insert(p_entity, "\n"); | |
| 1986 | |
| 1987 int32 codepoint = GetCharPressed(); | |
| 1988 while (codepoint > 0) { | |
| 1989 if (!shortcut && codepoint >= 32 && codepoint <= 126) { | |
| 1990 char inserted[2] = {(char)codepoint, '\0'}; | |
| 1991 Canvas_Text_Insert(p_entity, inserted); | |
| 1992 } | |
| 1993 codepoint = GetCharPressed(); | |
| 1994 } | |
| 1995 if (p_entity->text_cursor != original_cursor || | |
| 1996 (int32)strlen(p_entity->text) != original_length) { | |
| 1997 Canvas_Text_Ensure_Cursor_Visible(p_entity, font); | |
| 1998 } | |
| 1999 } | |
| 2000 | |
| 2001 static void Canvas_Scene_Update_Text_Input( | |
| 2002 Canvas_Scene *p_scene, | |
| 2003 Font font) | |
| 767 { | 2004 { |
| 768 if (p_scene->selected_index < 0 || | 2005 if (p_scene->selected_index < 0 || |
| 769 p_scene->selected_index >= (int32)Dowa_Array_Length(p_scene->p_entities)) return; | 2006 p_scene->selected_index >= (int32)Dowa_Array_Length(p_scene->p_entities)) return; |
| 770 | 2007 |
| 771 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; | 2008 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; |
| 772 if (p_entity->type != CANVAS_ENTITY_TEXT_AREA || !p_entity->active) return; | 2009 if ((p_entity->type != CANVAS_ENTITY_TEXT_AREA && |
| 2010 p_entity->type != CANVAS_ENTITY_LUCIDE_GALLERY) || | |
| 2011 !p_entity->active) { | |
| 2012 return; | |
| 2013 } | |
| 2014 | |
| 2015 if (p_entity->type == CANVAS_ENTITY_TEXT_AREA) { | |
| 2016 Canvas_Text_Update_Keyboard(p_entity, font); | |
| 2017 return; | |
| 2018 } | |
| 773 | 2019 |
| 774 size_t length = strlen(p_entity->text); | 2020 size_t length = strlen(p_entity->text); |
| 2021 boolean changed = FALSE; | |
| 775 int32 codepoint = GetCharPressed(); | 2022 int32 codepoint = GetCharPressed(); |
| 776 while (codepoint > 0) { | 2023 while (codepoint > 0) { |
| 777 if (codepoint >= 32 && codepoint <= 126 && length + 1 < sizeof(p_entity->text)) { | 2024 size_t input_capacity = p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY ? |
| 778 p_entity->text[length++] = (char)codepoint; | 2025 64 : |
| 2026 sizeof(p_entity->text); | |
| 2027 if (codepoint >= 32 && | |
| 2028 codepoint <= 126 && | |
| 2029 length + 1 < input_capacity) { | |
| 2030 p_entity->text[length++] = p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY ? | |
| 2031 Canvas_Ascii_Lower((char)codepoint) : | |
| 2032 (char)codepoint; | |
| 779 p_entity->text[length] = '\0'; | 2033 p_entity->text[length] = '\0'; |
| 2034 changed = TRUE; | |
| 780 } | 2035 } |
| 781 codepoint = GetCharPressed(); | 2036 codepoint = GetCharPressed(); |
| 782 } | 2037 } |
| 783 | 2038 |
| 784 if (IsKeyPressed(KEY_ENTER) && length + 1 < sizeof(p_entity->text)) { | 2039 if (p_entity->type == CANVAS_ENTITY_TEXT_AREA && |
| 2040 IsKeyPressed(KEY_ENTER) && | |
| 2041 length + 1 < sizeof(p_entity->text)) { | |
| 785 p_entity->text[length++] = '\n'; | 2042 p_entity->text[length++] = '\n'; |
| 786 p_entity->text[length] = '\0'; | 2043 p_entity->text[length] = '\0'; |
| 787 } | 2044 } |
| 788 if (IsKeyPressed(KEY_BACKSPACE) && length > 0) { | 2045 if (IsKeyPressed(KEY_BACKSPACE) && length > 0) { |
| 789 p_entity->text[length - 1] = '\0'; | 2046 p_entity->text[length - 1] = '\0'; |
| 2047 changed = TRUE; | |
| 2048 } | |
| 2049 if (changed && p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY) { | |
| 2050 p_entity->value = 0; | |
| 790 } | 2051 } |
| 791 } | 2052 } |
| 792 | 2053 |
| 793 static void Canvas_Entity_Capture_Pin( | 2054 static void Canvas_Entity_Capture_Pin( |
| 794 Canvas_Entity *p_entity, | 2055 Canvas_Entity *p_entity, |
| 798 Canvas_Camera_World_To_Screen(p_camera, p_entity->position); | 2059 Canvas_Camera_World_To_Screen(p_camera, p_entity->position); |
| 799 p_entity->pinned_screen_size = (Vector2){ | 2060 p_entity->pinned_screen_size = (Vector2){ |
| 800 p_entity->size.x * p_camera->zoom, | 2061 p_entity->size.x * p_camera->zoom, |
| 801 p_entity->size.y * p_camera->zoom, | 2062 p_entity->size.y * p_camera->zoom, |
| 802 }; | 2063 }; |
| 2064 } | |
| 2065 | |
| 2066 static void Canvas_Scene_Adjust_Index_After_Removal( | |
| 2067 int32 *p_index, | |
| 2068 int32 removed_index) | |
| 2069 { | |
| 2070 if (*p_index == removed_index) { | |
| 2071 *p_index = -1; | |
| 2072 } else if (*p_index > removed_index) { | |
| 2073 (*p_index)--; | |
| 2074 } | |
| 2075 } | |
| 2076 | |
| 2077 static void Canvas_Scene_Update_Lifecycle(Canvas_Scene *p_scene) | |
| 2078 { | |
| 2079 float follow = 1.0f - expf(-GetFrameTime() * 9.0f); | |
| 2080 size_t index = 0; | |
| 2081 while (index < Dowa_Array_Length(p_scene->p_entities)) { | |
| 2082 Canvas_Entity *p_entity = &p_scene->p_entities[index]; | |
| 2083 float target = p_entity->removing ? 0.0f : 1.0f; | |
| 2084 p_entity->visibility_amount += | |
| 2085 (target - p_entity->visibility_amount) * follow; | |
| 2086 p_entity->visibility_amount = Canvas_Clamp( | |
| 2087 p_entity->visibility_amount, | |
| 2088 0.0f, | |
| 2089 1.0f); | |
| 2090 float chrome_target = | |
| 2091 p_entity->type == CANVAS_ENTITY_WEB_CONTENT && | |
| 2092 (int32)index == p_scene->selected_index && | |
| 2093 !p_entity->removing ? | |
| 2094 1.0f : | |
| 2095 0.0f; | |
| 2096 float chrome_follow = 1.0f - expf(-GetFrameTime() * 12.0f); | |
| 2097 p_entity->web_chrome_amount += | |
| 2098 (chrome_target - p_entity->web_chrome_amount) * chrome_follow; | |
| 2099 p_entity->web_chrome_amount = Canvas_Clamp( | |
| 2100 p_entity->web_chrome_amount, | |
| 2101 0.0f, | |
| 2102 1.0f); | |
| 2103 | |
| 2104 if (p_entity->removing && p_entity->visibility_amount <= 0.01f) { | |
| 2105 int32 removed_index = (int32)index; | |
| 2106 size_t length = Dowa_Array_Length(p_scene->p_entities); | |
| 2107 for (size_t move = index + 1; move < length; move++) { | |
| 2108 p_scene->p_entities[move - 1] = p_scene->p_entities[move]; | |
| 2109 } | |
| 2110 (void)Dowa_Array_Pop(p_scene->p_entities); | |
| 2111 Canvas_Scene_Adjust_Index_After_Removal( | |
| 2112 &p_scene->selected_index, | |
| 2113 removed_index); | |
| 2114 Canvas_Scene_Adjust_Index_After_Removal( | |
| 2115 &p_scene->pressed_index, | |
| 2116 removed_index); | |
| 2117 Canvas_Scene_Adjust_Index_After_Removal( | |
| 2118 &p_scene->hover_index, | |
| 2119 removed_index); | |
| 2120 continue; | |
| 2121 } | |
| 2122 index++; | |
| 2123 } | |
| 803 } | 2124 } |
| 804 | 2125 |
| 805 static void Canvas_Scene_Update_Animations( | 2126 static void Canvas_Scene_Update_Animations( |
| 806 Canvas_Scene *p_scene, | 2127 Canvas_Scene *p_scene, |
| 807 const Canvas_Camera *p_camera) | 2128 const Canvas_Camera *p_camera) |
| 841 } | 2162 } |
| 842 | 2163 |
| 843 boolean Canvas_Scene_Update_Interaction( | 2164 boolean Canvas_Scene_Update_Interaction( |
| 844 Canvas_Scene *p_scene, | 2165 Canvas_Scene *p_scene, |
| 845 const Canvas_Camera *p_camera, | 2166 const Canvas_Camera *p_camera, |
| 2167 Font font, | |
| 846 boolean block_pointer_input) | 2168 boolean block_pointer_input) |
| 847 { | 2169 { |
| 848 Canvas_Scene_Update_Text_Input(p_scene); | 2170 Canvas_Scene_Update_Lifecycle(p_scene); |
| 2171 Canvas_Scene_Update_Text_Input(p_scene, font); | |
| 849 Canvas_Scene_Update_Animations(p_scene, p_camera); | 2172 Canvas_Scene_Update_Animations(p_scene, p_camera); |
| 850 | 2173 |
| 851 if (block_pointer_input && p_scene->pressed_index < 0) { | 2174 if (block_pointer_input && p_scene->pressed_index < 0) { |
| 852 return FALSE; | 2175 return FALSE; |
| 853 } | 2176 } |
| 857 -1 : | 2180 -1 : |
| 858 Canvas_Scene_Pick(p_scene, world_pointer, p_camera->zoom); | 2181 Canvas_Scene_Pick(p_scene, world_pointer, p_camera->zoom); |
| 859 boolean consumed_scroll = FALSE; | 2182 boolean consumed_scroll = FALSE; |
| 860 if (p_scene->hover_index >= 0) { | 2183 if (p_scene->hover_index >= 0) { |
| 861 Canvas_Entity *p_hovered = &p_scene->p_entities[p_scene->hover_index]; | 2184 Canvas_Entity *p_hovered = &p_scene->p_entities[p_scene->hover_index]; |
| 862 if (p_hovered->type == CANVAS_ENTITY_SCROLL_AREA) { | 2185 boolean control_down = Canvas_Control_Is_Down(); |
| 2186 if (p_hovered->type == CANVAS_ENTITY_TEXT_AREA && | |
| 2187 p_hovered->active && | |
| 2188 !control_down) { | |
| 2189 float wheel = GetMouseWheelMove(); | |
| 2190 if (wheel != 0.0f) { | |
| 2191 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 2192 Rectangle content = Canvas_Text_Area_Content_Bounds(p_hovered); | |
| 2193 int32 line_count = Canvas_Text_Build_Lines( | |
| 2194 font, | |
| 2195 p_hovered->text, | |
| 2196 content.width, | |
| 2197 lines, | |
| 2198 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 2199 float max_scroll = fmaxf( | |
| 2200 0.0f, | |
| 2201 (float)line_count * CANVAS_TEXT_AREA_LINE_HEIGHT - | |
| 2202 content.height); | |
| 2203 p_hovered->text_scroll_y = Canvas_Clamp( | |
| 2204 p_hovered->text_scroll_y - | |
| 2205 wheel * CANVAS_TEXT_AREA_LINE_HEIGHT * 2.0f, | |
| 2206 0.0f, | |
| 2207 max_scroll); | |
| 2208 consumed_scroll = TRUE; | |
| 2209 } | |
| 2210 } else if (p_hovered->type == CANVAS_ENTITY_SCROLL_AREA && | |
| 2211 !control_down) { | |
| 863 float wheel = GetMouseWheelMove(); | 2212 float wheel = GetMouseWheelMove(); |
| 864 if (wheel != 0.0f) { | 2213 if (wheel != 0.0f) { |
| 865 p_hovered->value = (int32)Canvas_Clamp( | 2214 p_hovered->value = (int32)Canvas_Clamp( |
| 866 (float)p_hovered->value - wheel * 28.0f, | 2215 (float)p_hovered->value - wheel * 28.0f, |
| 867 0.0f, | 2216 0.0f, |
| 868 240.0f); | 2217 240.0f); |
| 2218 consumed_scroll = TRUE; | |
| 2219 } | |
| 2220 } else if (p_hovered->type == CANVAS_ENTITY_LUCIDE_GALLERY && | |
| 2221 !control_down) { | |
| 2222 float wheel = GetMouseWheelMove(); | |
| 2223 if (wheel != 0.0f) { | |
| 2224 p_hovered->value = (int32)Canvas_Clamp( | |
| 2225 (float)p_hovered->value - wheel * 72.0f, | |
| 2226 0.0f, | |
| 2227 Canvas_Lucide_Max_Scroll(p_hovered)); | |
| 869 consumed_scroll = TRUE; | 2228 consumed_scroll = TRUE; |
| 870 } | 2229 } |
| 871 } | 2230 } |
| 872 } | 2231 } |
| 873 | 2232 |
| 887 } | 2246 } |
| 888 if ((int32)index != p_scene->selected_index && | 2247 if ((int32)index != p_scene->selected_index && |
| 889 p_scene->p_entities[index].type == CANVAS_ENTITY_DROPDOWN) { | 2248 p_scene->p_entities[index].type == CANVAS_ENTITY_DROPDOWN) { |
| 890 p_scene->p_entities[index].active = FALSE; | 2249 p_scene->p_entities[index].active = FALSE; |
| 891 } | 2250 } |
| 2251 if ((int32)index != p_scene->selected_index && | |
| 2252 p_scene->p_entities[index].type == CANVAS_ENTITY_LUCIDE_GALLERY) { | |
| 2253 p_scene->p_entities[index].active = FALSE; | |
| 2254 } | |
| 892 } | 2255 } |
| 893 | 2256 |
| 894 if (p_scene->selected_index >= 0) { | 2257 if (p_scene->selected_index >= 0) { |
| 895 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; | 2258 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; |
| 2259 if (p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY) { | |
| 2260 p_entity->active = CheckCollisionPointRec( | |
| 2261 world_pointer, | |
| 2262 Canvas_Lucide_Search_Bounds(p_entity)) ? TRUE : FALSE; | |
| 2263 } | |
| 2264 p_scene->selecting_text = FALSE; | |
| 2265 if (p_entity->type == CANVAS_ENTITY_TEXT_AREA) { | |
| 2266 boolean inside_content = CheckCollisionPointRec( | |
| 2267 world_pointer, | |
| 2268 Canvas_Text_Area_Content_Bounds(p_entity)) ? TRUE : FALSE; | |
| 2269 p_entity->active = inside_content; | |
| 2270 if (inside_content) { | |
| 2271 int32 cursor = Canvas_Text_Cursor_At_Point( | |
| 2272 font, | |
| 2273 p_entity, | |
| 2274 world_pointer); | |
| 2275 p_entity->text_cursor = cursor; | |
| 2276 if (!Canvas_Shift_Is_Down()) { | |
| 2277 p_entity->text_selection_anchor = cursor; | |
| 2278 } | |
| 2279 p_scene->selecting_text = TRUE; | |
| 2280 } | |
| 2281 } | |
| 896 p_scene->pressed_index = p_scene->selected_index; | 2282 p_scene->pressed_index = p_scene->selected_index; |
| 897 p_scene->dragging = FALSE; | 2283 p_scene->dragging = FALSE; |
| 898 p_scene->resizing = Canvas_Web_Resize_Handle_Contains( | 2284 p_scene->resizing = Canvas_Web_Resize_Handle_Contains( |
| 899 p_entity, | 2285 p_entity, |
| 900 world_pointer, | 2286 world_pointer, |
| 911 world_pointer.y - p_scene->pointer_start.y, | 2297 world_pointer.y - p_scene->pointer_start.y, |
| 912 }; | 2298 }; |
| 913 float screen_distance_squared = | 2299 float screen_distance_squared = |
| 914 (delta.x * p_camera->zoom) * (delta.x * p_camera->zoom) + | 2300 (delta.x * p_camera->zoom) * (delta.x * p_camera->zoom) + |
| 915 (delta.y * p_camera->zoom) * (delta.y * p_camera->zoom); | 2301 (delta.y * p_camera->zoom) * (delta.y * p_camera->zoom); |
| 916 if (screen_distance_squared >= 16.0f) p_scene->dragging = TRUE; | 2302 Canvas_Entity *p_pressed = |
| 2303 &p_scene->p_entities[p_scene->pressed_index]; | |
| 2304 if (p_scene->selecting_text && | |
| 2305 p_pressed->type == CANVAS_ENTITY_TEXT_AREA) { | |
| 2306 p_pressed->text_cursor = Canvas_Text_Cursor_At_Point( | |
| 2307 font, | |
| 2308 p_pressed, | |
| 2309 world_pointer); | |
| 2310 Canvas_Text_Ensure_Cursor_Visible(p_pressed, font); | |
| 2311 } | |
| 2312 if (screen_distance_squared >= 16.0f && | |
| 2313 !p_scene->selecting_text && | |
| 2314 !(p_pressed->type == CANVAS_ENTITY_LUCIDE_GALLERY && | |
| 2315 p_pressed->active)) { | |
| 2316 p_scene->dragging = TRUE; | |
| 2317 } | |
| 917 | 2318 |
| 918 if (p_scene->dragging) { | 2319 if (p_scene->dragging) { |
| 919 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->pressed_index]; | 2320 Canvas_Entity *p_entity = p_pressed; |
| 920 if (p_entity->type == CANVAS_ENTITY_NOTIFICATION) { | 2321 if (p_entity->type == CANVAS_ENTITY_NOTIFICATION) { |
| 921 p_scene->dragging = FALSE; | 2322 p_scene->dragging = FALSE; |
| 922 } else if (p_scene->resizing) { | 2323 } else if (p_scene->resizing) { |
| 923 p_entity->size = (Vector2){ | 2324 p_entity->size = (Vector2){ |
| 924 fmaxf(CANVAS_WEB_MIN_WIDTH, p_scene->entity_size_start.x + delta.x), | 2325 fmaxf(CANVAS_WEB_MIN_WIDTH, p_scene->entity_size_start.x + delta.x), |
| 934 } | 2335 } |
| 935 } | 2336 } |
| 936 | 2337 |
| 937 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { | 2338 if (IsMouseButtonReleased(MOUSE_BUTTON_LEFT)) { |
| 938 if (p_scene->pressed_index >= 0 && !p_scene->dragging) { | 2339 if (p_scene->pressed_index >= 0 && !p_scene->dragging) { |
| 939 Canvas_Scene_Activate(p_scene, p_scene->pressed_index, world_pointer); | |
| 940 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->pressed_index]; | 2340 Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->pressed_index]; |
| 2341 if (p_entity->type != CANVAS_ENTITY_TEXT_AREA) { | |
| 2342 Canvas_Scene_Activate( | |
| 2343 p_scene, | |
| 2344 p_scene->pressed_index, | |
| 2345 world_pointer); | |
| 2346 } | |
| 941 if (p_entity->pinned) Canvas_Entity_Capture_Pin(p_entity, p_camera); | 2347 if (p_entity->pinned) Canvas_Entity_Capture_Pin(p_entity, p_camera); |
| 942 } | 2348 } |
| 943 p_scene->pressed_index = -1; | 2349 p_scene->pressed_index = -1; |
| 944 p_scene->dragging = FALSE; | 2350 p_scene->dragging = FALSE; |
| 945 p_scene->resizing = FALSE; | 2351 p_scene->resizing = FALSE; |
| 2352 p_scene->selecting_text = FALSE; | |
| 946 } | 2353 } |
| 947 return p_scene->pressed_index >= 0 || consumed_scroll; | 2354 return p_scene->pressed_index >= 0 || consumed_scroll; |
| 948 } | 2355 } |
| 949 | 2356 |
| 950 boolean Canvas_Scene_Is_Text_Editing(const Canvas_Scene *p_scene) | 2357 boolean Canvas_Scene_Is_Text_Editing(const Canvas_Scene *p_scene) |
| 953 p_scene->selected_index >= (int32)Dowa_Array_Length(p_scene->p_entities)) { | 2360 p_scene->selected_index >= (int32)Dowa_Array_Length(p_scene->p_entities)) { |
| 954 return FALSE; | 2361 return FALSE; |
| 955 } | 2362 } |
| 956 | 2363 |
| 957 const Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; | 2364 const Canvas_Entity *p_entity = &p_scene->p_entities[p_scene->selected_index]; |
| 958 return (p_entity->type == CANVAS_ENTITY_TEXT_AREA && p_entity->active) ? TRUE : FALSE; | 2365 return ((p_entity->type == CANVAS_ENTITY_TEXT_AREA || |
| 2366 p_entity->type == CANVAS_ENTITY_LUCIDE_GALLERY) && | |
| 2367 p_entity->active) ? TRUE : FALSE; | |
| 959 } | 2368 } |
| 960 | 2369 |
| 961 void Canvas_Scene_Toggle_Selected_Pin( | 2370 void Canvas_Scene_Toggle_Selected_Pin( |
| 962 Canvas_Scene *p_scene, | 2371 Canvas_Scene *p_scene, |
| 963 const Canvas_Camera *p_camera) | 2372 const Canvas_Camera *p_camera) |
| 1159 } | 2568 } |
| 1160 | 2569 |
| 1161 if (!block_pointer_input) { | 2570 if (!block_pointer_input) { |
| 1162 float wheel = GetMouseWheelMove(); | 2571 float wheel = GetMouseWheelMove(); |
| 1163 if (wheel != 0.0f) { | 2572 if (wheel != 0.0f) { |
| 1164 Canvas_Camera_Zoom_At( | 2573 if (Canvas_Control_Is_Down()) { |
| 1165 p_camera, | 2574 Canvas_Camera_Zoom_At( |
| 1166 GetMousePosition(), | 2575 p_camera, |
| 1167 powf(1.15f, wheel)); | 2576 GetMousePosition(), |
| 2577 powf(1.15f, wheel)); | |
| 2578 } else { | |
| 2579 Canvas_Camera_Pan( | |
| 2580 p_camera, | |
| 2581 (Vector2){0.0f, wheel * 72.0f}); | |
| 2582 } | |
| 1168 } | 2583 } |
| 1169 } | 2584 } |
| 1170 | 2585 |
| 1171 if (!block_keyboard_input) { | 2586 if (!block_keyboard_input) { |
| 1172 Vector2 keyboard_delta = {0.0f, 0.0f}; | 2587 Vector2 keyboard_delta = {0.0f, 0.0f}; |
| 1187 float spacing = Canvas_Grid_Spacing(p_camera->zoom); | 2602 float spacing = Canvas_Grid_Spacing(p_camera->zoom); |
| 1188 int32 first_x = (int32)floorf(bounds.x / spacing); | 2603 int32 first_x = (int32)floorf(bounds.x / spacing); |
| 1189 int32 last_x = (int32)ceilf((bounds.x + bounds.width) / spacing); | 2604 int32 last_x = (int32)ceilf((bounds.x + bounds.width) / spacing); |
| 1190 int32 first_y = (int32)floorf(bounds.y / spacing); | 2605 int32 first_y = (int32)floorf(bounds.y / spacing); |
| 1191 int32 last_y = (int32)ceilf((bounds.y + bounds.height) / spacing); | 2606 int32 last_y = (int32)ceilf((bounds.y + bounds.height) / spacing); |
| 1192 Color minor = Fade(p_theme->border, 0.36f); | 2607 Color minor = Canvas_Color_Fade(p_theme->border, 0.36f); |
| 1193 Color major = Fade(p_theme->border, 0.65f); | 2608 Color major = Canvas_Color_Fade(p_theme->border, 0.65f); |
| 1194 Color axis = p_theme->text_muted; | 2609 Color axis = p_theme->text_muted; |
| 1195 | 2610 |
| 1196 for (int32 index = first_x; index <= last_x; index++) { | 2611 for (int32 index = first_x; index <= last_x; index++) { |
| 1197 float x = (float)index * spacing; | 2612 float x = (float)index * spacing; |
| 1198 Color color = (index == 0) ? axis : ((index % 4 == 0) ? major : minor); | 2613 Color color = (index == 0) ? axis : ((index % 4 == 0) ? major : minor); |
| 1212 } | 2627 } |
| 1213 } | 2628 } |
| 1214 | 2629 |
| 1215 static void Canvas_Draw_Selection( | 2630 static void Canvas_Draw_Selection( |
| 1216 const Canvas_Entity *p_entity, | 2631 const Canvas_Entity *p_entity, |
| 1217 float zoom) | 2632 float zoom, |
| 2633 Color selection) | |
| 1218 { | 2634 { |
| 1219 float line = 2.0f / zoom; | 2635 float line = 2.0f / zoom; |
| 1220 float padding = 7.0f / zoom; | 2636 float padding = 7.0f / zoom; |
| 1221 Color selection = {0, 122, 255, 230}; | |
| 1222 | 2637 |
| 1223 switch (p_entity->type) { | 2638 switch (p_entity->type) { |
| 1224 case CANVAS_ENTITY_RECTANGLE: | 2639 case CANVAS_ENTITY_RECTANGLE: |
| 1225 DrawRectangleRoundedLinesEx( | 2640 DrawRectangleRoundedLinesEx( |
| 1226 (Rectangle){ | 2641 (Rectangle){ |
| 1246 (Vector2){ | 2661 (Vector2){ |
| 1247 p_entity->position.x + p_entity->size.x, | 2662 p_entity->position.x + p_entity->size.x, |
| 1248 p_entity->position.y + p_entity->size.y, | 2663 p_entity->position.y + p_entity->size.y, |
| 1249 }, | 2664 }, |
| 1250 10.0f / zoom, | 2665 10.0f / zoom, |
| 1251 Fade(selection, 0.35f)); | 2666 Canvas_Color_Fade(selection, 0.35f)); |
| 1252 break; | 2667 break; |
| 1253 case CANVAS_ENTITY_TEXT: | 2668 case CANVAS_ENTITY_TEXT: |
| 1254 case CANVAS_ENTITY_BUTTON: | 2669 case CANVAS_ENTITY_BUTTON: |
| 1255 case CANVAS_ENTITY_TEXT_AREA: | 2670 case CANVAS_ENTITY_TEXT_AREA: |
| 1256 case CANVAS_ENTITY_DROPDOWN: | 2671 case CANVAS_ENTITY_DROPDOWN: |
| 1273 10, | 2688 10, |
| 1274 line, | 2689 line, |
| 1275 selection); | 2690 selection); |
| 1276 break; | 2691 break; |
| 1277 } | 2692 } |
| 2693 case CANVAS_ENTITY_LUCIDE_GALLERY: { | |
| 2694 DrawRectangleRoundedLinesEx( | |
| 2695 (Rectangle){ | |
| 2696 p_entity->position.x - padding, | |
| 2697 p_entity->position.y - padding, | |
| 2698 p_entity->size.x + padding * 2.0f, | |
| 2699 p_entity->size.y + padding * 2.0f, | |
| 2700 }, | |
| 2701 0.04f, | |
| 2702 10, | |
| 2703 line, | |
| 2704 selection); | |
| 2705 break; | |
| 2706 } | |
| 1278 case CANVAS_ENTITY_WEB_CONTENT: | 2707 case CANVAS_ENTITY_WEB_CONTENT: |
| 1279 DrawRectangleRoundedLinesEx( | 2708 DrawRectangleRoundedLinesEx( |
| 1280 (Rectangle){ | 2709 (Rectangle){ |
| 1281 p_entity->position.x - padding, | 2710 p_entity->position.x - padding, |
| 1282 p_entity->position.y - padding, | 2711 p_entity->position.y - padding, |
| 1294 p_entity->position.y + p_entity->size.y - | 2723 p_entity->position.y + p_entity->size.y - |
| 1295 CANVAS_WEB_RESIZE_HANDLE / zoom, | 2724 CANVAS_WEB_RESIZE_HANDLE / zoom, |
| 1296 CANVAS_WEB_RESIZE_HANDLE / zoom, | 2725 CANVAS_WEB_RESIZE_HANDLE / zoom, |
| 1297 CANVAS_WEB_RESIZE_HANDLE / zoom, | 2726 CANVAS_WEB_RESIZE_HANDLE / zoom, |
| 1298 }, | 2727 }, |
| 1299 Fade(selection, 0.18f)); | 2728 Canvas_Color_Fade(selection, 0.18f)); |
| 1300 DrawLineEx( | 2729 DrawLineEx( |
| 1301 (Vector2){ | 2730 (Vector2){ |
| 1302 p_entity->position.x + p_entity->size.x - 11.0f / zoom, | 2731 p_entity->position.x + p_entity->size.x - 11.0f / zoom, |
| 1303 p_entity->position.y + p_entity->size.y - 4.0f / zoom, | 2732 p_entity->position.y + p_entity->size.y - 4.0f / zoom, |
| 1304 }, | 2733 }, |
| 1313 break; | 2742 break; |
| 1314 } | 2743 } |
| 1315 | 2744 |
| 1316 } | 2745 } |
| 1317 | 2746 |
| 2747 static void Canvas_Draw_Text_Area_Content( | |
| 2748 const Canvas_Entity *p_entity, | |
| 2749 Font font, | |
| 2750 const Canvas_Theme *p_theme) | |
| 2751 { | |
| 2752 Rectangle content = Canvas_Text_Area_Content_Bounds(p_entity); | |
| 2753 if (!p_entity->text[0]) { | |
| 2754 DrawTextEx( | |
| 2755 font, | |
| 2756 "Write something...", | |
| 2757 (Vector2){content.x, content.y}, | |
| 2758 CANVAS_TEXT_AREA_FONT_SIZE, | |
| 2759 CANVAS_TEXT_AREA_SPACING, | |
| 2760 p_theme->text_muted); | |
| 2761 if (p_entity->active && fmod(GetTime(), 1.0) < 0.55) { | |
| 2762 DrawLineEx( | |
| 2763 (Vector2){content.x, content.y}, | |
| 2764 (Vector2){ | |
| 2765 content.x, | |
| 2766 content.y + CANVAS_TEXT_AREA_FONT_SIZE + 2.0f, | |
| 2767 }, | |
| 2768 1.5f, | |
| 2769 p_theme->accent); | |
| 2770 } | |
| 2771 return; | |
| 2772 } | |
| 2773 | |
| 2774 Canvas_Text_Line lines[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 2775 int32 line_count = Canvas_Text_Build_Lines( | |
| 2776 font, | |
| 2777 p_entity->text, | |
| 2778 content.width, | |
| 2779 lines, | |
| 2780 CANVAS_ENTITY_TEXT_CAPACITY); | |
| 2781 int32 selection_start = 0; | |
| 2782 int32 selection_end = 0; | |
| 2783 Canvas_Text_Selection_Bounds( | |
| 2784 p_entity, | |
| 2785 &selection_start, | |
| 2786 &selection_end); | |
| 2787 int32 cursor_line = Canvas_Text_Line_For_Cursor( | |
| 2788 lines, | |
| 2789 line_count, | |
| 2790 p_entity->text_cursor); | |
| 2791 | |
| 2792 for (int32 line = 0; line < line_count; line++) { | |
| 2793 float y = content.y + | |
| 2794 (float)line * CANVAS_TEXT_AREA_LINE_HEIGHT - | |
| 2795 p_entity->text_scroll_y; | |
| 2796 if (y < content.y || | |
| 2797 y + CANVAS_TEXT_AREA_FONT_SIZE > content.y + content.height) { | |
| 2798 continue; | |
| 2799 } | |
| 2800 | |
| 2801 int32 line_selection_start = | |
| 2802 selection_start > lines[line].start ? | |
| 2803 selection_start : | |
| 2804 lines[line].start; | |
| 2805 int32 line_selection_end = | |
| 2806 selection_end < lines[line].end ? | |
| 2807 selection_end : | |
| 2808 lines[line].end; | |
| 2809 if (line_selection_end > line_selection_start) { | |
| 2810 float selection_x = content.x + Canvas_Text_Measure_Range( | |
| 2811 font, | |
| 2812 p_entity->text, | |
| 2813 lines[line].start, | |
| 2814 line_selection_start); | |
| 2815 float selection_width = Canvas_Text_Measure_Range( | |
| 2816 font, | |
| 2817 p_entity->text, | |
| 2818 line_selection_start, | |
| 2819 line_selection_end); | |
| 2820 DrawRectangleRec( | |
| 2821 (Rectangle){ | |
| 2822 selection_x, | |
| 2823 y - 1.0f, | |
| 2824 fmaxf(selection_width, 2.0f), | |
| 2825 CANVAS_TEXT_AREA_LINE_HEIGHT, | |
| 2826 }, | |
| 2827 p_theme->accent_soft); | |
| 2828 } | |
| 2829 | |
| 2830 char line_text[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 2831 int32 line_length = lines[line].end - lines[line].start; | |
| 2832 memcpy( | |
| 2833 line_text, | |
| 2834 p_entity->text + lines[line].start, | |
| 2835 (size_t)line_length); | |
| 2836 line_text[line_length] = '\0'; | |
| 2837 DrawTextEx( | |
| 2838 font, | |
| 2839 line_text, | |
| 2840 (Vector2){content.x, y}, | |
| 2841 CANVAS_TEXT_AREA_FONT_SIZE, | |
| 2842 CANVAS_TEXT_AREA_SPACING, | |
| 2843 p_theme->text); | |
| 2844 | |
| 2845 if (p_entity->active && | |
| 2846 line == cursor_line && | |
| 2847 fmod(GetTime(), 1.0) < 0.55) { | |
| 2848 float cursor_x = content.x + Canvas_Text_Measure_Range( | |
| 2849 font, | |
| 2850 p_entity->text, | |
| 2851 lines[line].start, | |
| 2852 p_entity->text_cursor); | |
| 2853 DrawLineEx( | |
| 2854 (Vector2){cursor_x, y}, | |
| 2855 (Vector2){ | |
| 2856 cursor_x, | |
| 2857 y + CANVAS_TEXT_AREA_FONT_SIZE + 2.0f, | |
| 2858 }, | |
| 2859 1.5f, | |
| 2860 p_theme->accent); | |
| 2861 } | |
| 2862 } | |
| 2863 } | |
| 2864 | |
| 1318 static void Canvas_Draw_Entity( | 2865 static void Canvas_Draw_Entity( |
| 1319 const Canvas_Entity *p_entity, | 2866 const Canvas_Entity *p_entity, |
| 1320 Font font, | 2867 Font font, |
| 1321 float zoom, | 2868 float zoom, |
| 1322 boolean selected, | 2869 boolean selected, |
| 1323 boolean pressed, | 2870 boolean pressed, |
| 1324 const Canvas_Theme *p_theme) | 2871 const Canvas_Theme *p_theme) |
| 1325 { | 2872 { |
| 2873 float opacity = Canvas_Entity_Visibility(p_entity); | |
| 2874 if (opacity <= 0.001f) return; | |
| 2875 Canvas_Entity faded_entity = *p_entity; | |
| 2876 faded_entity.color = Canvas_Color_With_Opacity( | |
| 2877 faded_entity.color, | |
| 2878 opacity); | |
| 2879 Canvas_Theme faded_theme = Canvas_Theme_With_Opacity(p_theme, opacity); | |
| 2880 p_entity = &faded_entity; | |
| 2881 p_theme = &faded_theme; | |
| 2882 | |
| 1326 float shadow_offset = 6.0f / zoom; | 2883 float shadow_offset = 6.0f / zoom; |
| 1327 Color shadow = p_theme->shadow; | 2884 Color shadow = p_theme->shadow; |
| 1328 static const char *calendar_weekdays[] = { | 2885 static const char *calendar_weekdays[] = { |
| 1329 "S", "M", "T", "W", "T", "F", "S", | 2886 "S", "M", "T", "W", "T", "F", "S", |
| 1330 }; | 2887 }; |
| 1350 p_entity->size.x, | 2907 p_entity->size.x, |
| 1351 p_entity->size.y, | 2908 p_entity->size.y, |
| 1352 }, | 2909 }, |
| 1353 0.14f, | 2910 0.14f, |
| 1354 12, | 2911 12, |
| 1355 Fade(p_entity->color, 0.92f)); | 2912 Canvas_Color_Fade(p_entity->color, 0.92f)); |
| 1356 DrawRectangleRoundedLinesEx( | 2913 DrawRectangleRoundedLinesEx( |
| 1357 (Rectangle){ | 2914 (Rectangle){ |
| 1358 p_entity->position.x, | 2915 p_entity->position.x, |
| 1359 p_entity->position.y, | 2916 p_entity->position.y, |
| 1360 p_entity->size.x, | 2917 p_entity->size.x, |
| 1361 p_entity->size.y, | 2918 p_entity->size.y, |
| 1362 }, | 2919 }, |
| 1363 0.14f, | 2920 0.14f, |
| 1364 12, | 2921 12, |
| 1365 1.0f / zoom, | 2922 1.0f / zoom, |
| 1366 Fade(BLACK, 0.08f)); | 2923 p_theme->entity_outline); |
| 1367 break; | 2924 break; |
| 1368 case CANVAS_ENTITY_CIRCLE: | 2925 case CANVAS_ENTITY_CIRCLE: |
| 1369 DrawCircleV( | 2926 DrawCircleV( |
| 1370 (Vector2){ | 2927 (Vector2){ |
| 1371 p_entity->position.x + shadow_offset, | 2928 p_entity->position.x + shadow_offset, |
| 1411 p_entity->position.y - grow - lift, | 2968 p_entity->position.y - grow - lift, |
| 1412 p_entity->size.x + grow * 2.0f, | 2969 p_entity->size.x + grow * 2.0f, |
| 1413 p_entity->size.y + grow * 2.0f, | 2970 p_entity->size.y + grow * 2.0f, |
| 1414 }; | 2971 }; |
| 1415 Color base_fill = p_entity->active ? | 2972 Color base_fill = p_entity->active ? |
| 1416 (Color){35, 99, 235, 255} : | 2973 p_theme->button_active : |
| 1417 (Color){17, 24, 39, 255}; | 2974 p_theme->button; |
| 1418 Color hover_fill = p_entity->active ? | 2975 Color hover_fill = p_entity->active ? |
| 1419 (Color){59, 130, 246, 255} : | 2976 p_theme->button_active_hover : |
| 1420 (Color){39, 48, 64, 255}; | 2977 p_theme->button_hover; |
| 1421 Color fill = Canvas_Color_Lerp(base_fill, hover_fill, hover); | 2978 Color fill = Canvas_Color_Lerp(base_fill, hover_fill, hover); |
| 1422 Canvas_Theme_Draw_Shadow( | 2979 Canvas_Theme_Draw_Shadow( |
| 1423 p_theme, | 2980 p_theme, |
| 1424 bounds, | 2981 bounds, |
| 1425 0.46f, | 2982 0.46f, |
| 1435 bounds.x + (bounds.width - text_size.x) * 0.5f, | 2992 bounds.x + (bounds.width - text_size.x) * 0.5f, |
| 1436 bounds.y + (bounds.height - text_size.y) * 0.5f, | 2993 bounds.y + (bounds.height - text_size.y) * 0.5f, |
| 1437 }, | 2994 }, |
| 1438 16.0f, | 2995 16.0f, |
| 1439 0.1f, | 2996 0.1f, |
| 1440 WHITE); | 2997 p_theme->on_accent); |
| 1441 break; | 2998 break; |
| 1442 } | 2999 } |
| 1443 case CANVAS_ENTITY_TEXT_AREA: { | 3000 case CANVAS_ENTITY_TEXT_AREA: { |
| 1444 Rectangle bounds = { | 3001 Rectangle bounds = { |
| 1445 p_entity->position.x, | 3002 p_entity->position.x, |
| 1453 bounds, | 3010 bounds, |
| 1454 0.10f, | 3011 0.10f, |
| 1455 14, | 3012 14, |
| 1456 (p_entity->active ? 2.0f : 1.0f) / zoom, | 3013 (p_entity->active ? 2.0f : 1.0f) / zoom, |
| 1457 p_entity->active ? p_theme->accent : p_theme->border); | 3014 p_entity->active ? p_theme->accent : p_theme->border); |
| 1458 DrawTextEx( | 3015 Canvas_Draw_Text_Area_Content(p_entity, font, p_theme); |
| 1459 font, | |
| 1460 p_entity->text[0] ? p_entity->text : "Write something...", | |
| 1461 (Vector2){bounds.x + 16.0f, bounds.y + 15.0f}, | |
| 1462 16.0f, | |
| 1463 0.1f, | |
| 1464 p_entity->text[0] ? p_theme->text : p_theme->text_muted); | |
| 1465 if (p_entity->active) { | |
| 1466 DrawCircleV( | |
| 1467 (Vector2){bounds.x + bounds.width - 16.0f, bounds.y + 16.0f}, | |
| 1468 3.0f, | |
| 1469 p_theme->accent); | |
| 1470 } | |
| 1471 break; | 3016 break; |
| 1472 } | 3017 } |
| 1473 case CANVAS_ENTITY_DROPDOWN: { | 3018 case CANVAS_ENTITY_DROPDOWN: { |
| 1474 Rectangle bounds = { | 3019 Rectangle bounds = { |
| 1475 p_entity->position.x, | 3020 p_entity->position.x, |
| 1550 if (amount > 0.0f) { | 3095 if (amount > 0.0f) { |
| 1551 DrawLineEx( | 3096 DrawLineEx( |
| 1552 (Vector2){bounds.x, bounds.y + 56.0f}, | 3097 (Vector2){bounds.x, bounds.y + 56.0f}, |
| 1553 (Vector2){bounds.x + bounds.width, bounds.y + 56.0f}, | 3098 (Vector2){bounds.x + bounds.width, bounds.y + 56.0f}, |
| 1554 1.0f / zoom, | 3099 1.0f / zoom, |
| 1555 Fade(p_theme->border, amount)); | 3100 Canvas_Color_Fade(p_theme->border, amount)); |
| 1556 if (amount > 0.55f) { | 3101 if (amount > 0.55f) { |
| 1557 float body_alpha = Canvas_Clamp( | 3102 float body_alpha = Canvas_Clamp( |
| 1558 (amount - 0.55f) / 0.45f, | 3103 (amount - 0.55f) / 0.45f, |
| 1559 0.0f, | 3104 0.0f, |
| 1560 1.0f); | 3105 1.0f); |
| 1562 font, | 3107 font, |
| 1563 "Drag, zoom, pin, and combine primitives\nacross one continuous workspace.", | 3108 "Drag, zoom, pin, and combine primitives\nacross one continuous workspace.", |
| 1564 (Vector2){bounds.x + 16.0f, bounds.y + 76.0f}, | 3109 (Vector2){bounds.x + 16.0f, bounds.y + 76.0f}, |
| 1565 14.0f, | 3110 14.0f, |
| 1566 0.1f, | 3111 0.1f, |
| 1567 Fade(p_theme->text_muted, body_alpha)); | 3112 Canvas_Color_Fade(p_theme->text_muted, body_alpha)); |
| 1568 } | 3113 } |
| 1569 } | 3114 } |
| 1570 break; | 3115 break; |
| 1571 } | 3116 } |
| 1572 case CANVAS_ENTITY_CARD: { | 3117 case CANVAS_ENTITY_CARD: { |
| 1583 bounds.width - 24.0f, 92.0f}, | 3128 bounds.width - 24.0f, 92.0f}, |
| 1584 0.08f, | 3129 0.08f, |
| 1585 12, | 3130 12, |
| 1586 p_entity->active ? | 3131 p_entity->active ? |
| 1587 p_theme->accent_soft : | 3132 p_theme->accent_soft : |
| 1588 Fade(p_entity->color, 0.22f)); | 3133 Canvas_Color_Fade(p_entity->color, 0.22f)); |
| 1589 DrawCircleV( | 3134 DrawCircleV( |
| 1590 (Vector2){bounds.x + 44.0f, bounds.y + 58.0f}, | 3135 (Vector2){bounds.x + 44.0f, bounds.y + 58.0f}, |
| 1591 18.0f, | 3136 18.0f, |
| 1592 p_entity->active ? (Color){37, 99, 235, 255} : p_entity->color); | 3137 p_entity->active ? |
| 3138 p_theme->accent : | |
| 3139 p_entity->color); | |
| 1593 DrawTextEx( | 3140 DrawTextEx( |
| 1594 font, | 3141 font, |
| 1595 p_entity->text, | 3142 p_entity->text, |
| 1596 (Vector2){bounds.x + 16.0f, bounds.y + 122.0f}, | 3143 (Vector2){bounds.x + 16.0f, bounds.y + 122.0f}, |
| 1597 20.0f, | 3144 20.0f, |
| 1664 label, | 3211 label, |
| 1665 (Vector2){center.x - label_size.x * 0.5f, | 3212 (Vector2){center.x - label_size.x * 0.5f, |
| 1666 center.y - label_size.y * 0.5f}, | 3213 center.y - label_size.y * 0.5f}, |
| 1667 13.0f, | 3214 13.0f, |
| 1668 0.0f, | 3215 0.0f, |
| 1669 day == p_entity->value ? WHITE : p_theme->text); | 3216 day == p_entity->value ? |
| 3217 p_theme->on_accent : | |
| 3218 p_theme->text); | |
| 1670 } | 3219 } |
| 1671 break; | 3220 break; |
| 1672 } | 3221 } |
| 1673 case CANVAS_ENTITY_SWITCH: { | 3222 case CANVAS_ENTITY_SWITCH: { |
| 1674 float amount = p_entity->animation_amount; | 3223 float amount = p_entity->animation_amount; |
| 1677 p_entity->position.x, | 3226 p_entity->position.x, |
| 1678 p_entity->position.y, | 3227 p_entity->position.y, |
| 1679 p_entity->size.x, | 3228 p_entity->size.x, |
| 1680 p_entity->size.y, | 3229 p_entity->size.y, |
| 1681 }; | 3230 }; |
| 1682 Color track = Canvas_Color_Lerp( | 3231 Color off_track = Canvas_Color_Lerp( |
| 1683 p_theme->border, | 3232 p_theme->switch_track_off, |
| 1684 p_theme->accent, | 3233 p_theme->switch_track_off_hover, |
| 1685 eased); | 3234 p_entity->hover_amount); |
| 1686 if (!p_entity->active) { | 3235 Color on_track = Canvas_Color_Lerp( |
| 1687 track = Canvas_Color_Lerp( | 3236 p_theme->switch_track_on, |
| 1688 track, | 3237 p_theme->switch_track_on_hover, |
| 1689 p_theme->text_muted, | 3238 p_entity->hover_amount); |
| 1690 p_entity->hover_amount); | 3239 Color track = Canvas_Color_Lerp(off_track, on_track, eased); |
| 1691 } | |
| 1692 Canvas_Theme_Draw_Shadow(p_theme, bounds, 0.50f, 18, zoom, 1.0f); | 3240 Canvas_Theme_Draw_Shadow(p_theme, bounds, 0.50f, 18, zoom, 1.0f); |
| 1693 DrawRectangleRounded(bounds, 0.50f, 18, track); | 3241 DrawRectangleRounded(bounds, 0.50f, 18, track); |
| 1694 float knob_x = bounds.x + 18.0f + | 3242 float knob_x = bounds.x + 18.0f + |
| 1695 eased * (bounds.width - 36.0f); | 3243 eased * (bounds.width - 36.0f); |
| 1696 float knob_radius = pressed ? 12.5f : 14.0f; | 3244 float knob_radius = pressed ? 12.5f : 14.0f; |
| 1697 DrawCircleV( | 3245 DrawCircleV( |
| 1698 (Vector2){knob_x, bounds.y + bounds.height * 0.5f}, | 3246 (Vector2){knob_x, bounds.y + bounds.height * 0.5f}, |
| 1699 knob_radius, | 3247 knob_radius, |
| 1700 WHITE); | 3248 p_theme->switch_thumb); |
| 1701 break; | 3249 break; |
| 1702 } | 3250 } |
| 1703 case CANVAS_ENTITY_TABLE: { | 3251 case CANVAS_ENTITY_TABLE: { |
| 1704 Rectangle bounds = { | 3252 Rectangle bounds = { |
| 1705 p_entity->position.x, | 3253 p_entity->position.x, |
| 1782 opacity); | 3330 opacity); |
| 1783 DrawRectangleRounded( | 3331 DrawRectangleRounded( |
| 1784 bounds, | 3332 bounds, |
| 1785 0.20f, | 3333 0.20f, |
| 1786 16, | 3334 16, |
| 1787 Fade(p_theme->toast_surface, opacity)); | 3335 Canvas_Color_Fade(p_theme->toast_surface, opacity)); |
| 1788 DrawRectangleRoundedLinesEx( | 3336 DrawRectangleRoundedLinesEx( |
| 1789 bounds, | 3337 bounds, |
| 1790 0.20f, | 3338 0.20f, |
| 1791 16, | 3339 16, |
| 1792 1.0f / zoom, | 3340 1.0f / zoom, |
| 1793 Fade(p_theme->toast_border, opacity)); | 3341 Canvas_Color_Fade(p_theme->toast_border, opacity)); |
| 1794 DrawTextEx( | 3342 DrawTextEx( |
| 1795 font, | 3343 font, |
| 1796 p_entity->text, | 3344 p_entity->text, |
| 1797 (Vector2){bounds.x + 16.0f * unit, bounds.y + 18.0f * unit}, | 3345 (Vector2){bounds.x + 16.0f * unit, bounds.y + 18.0f * unit}, |
| 1798 15.0f * unit, | 3346 15.0f * unit, |
| 1799 0.0f, | 3347 0.0f, |
| 1800 Fade(p_theme->text, opacity)); | 3348 Canvas_Color_Fade(p_theme->text, opacity)); |
| 1801 DrawTextEx( | 3349 DrawTextEx( |
| 1802 font, | 3350 font, |
| 1803 "Sunday, December 3 at 9:00 AM", | 3351 "Sunday, December 3 at 9:00 AM", |
| 1804 (Vector2){bounds.x + 16.0f * unit, bounds.y + 45.0f * unit}, | 3352 (Vector2){bounds.x + 16.0f * unit, bounds.y + 45.0f * unit}, |
| 1805 14.0f * unit, | 3353 14.0f * unit, |
| 1806 0.0f, | 3354 0.0f, |
| 1807 Fade(p_theme->text_muted, opacity)); | 3355 Canvas_Color_Fade(p_theme->text_muted, opacity)); |
| 1808 Rectangle undo = { | 3356 Rectangle undo = { |
| 1809 bounds.x + bounds.width - 108.0f * unit, | 3357 bounds.x + bounds.width - 108.0f * unit, |
| 1810 bounds.y + 24.0f * unit, | 3358 bounds.y + 24.0f * unit, |
| 1811 54.0f * unit, | 3359 54.0f * unit, |
| 1812 34.0f * unit, | 3360 34.0f * unit, |
| 1813 }; | 3361 }; |
| 1814 DrawRectangleRounded( | 3362 DrawRectangleRounded( |
| 1815 undo, | 3363 undo, |
| 1816 0.26f, | 3364 0.26f, |
| 1817 10, | 3365 10, |
| 1818 Fade(p_theme->surface_muted, opacity)); | 3366 Canvas_Color_Fade(p_theme->surface_muted, opacity)); |
| 1819 DrawRectangleRoundedLinesEx( | 3367 DrawRectangleRoundedLinesEx( |
| 1820 undo, | 3368 undo, |
| 1821 0.26f, | 3369 0.26f, |
| 1822 10, | 3370 10, |
| 1823 1.0f / zoom, | 3371 1.0f / zoom, |
| 1824 Fade(p_theme->border, opacity)); | 3372 Canvas_Color_Fade(p_theme->border, opacity)); |
| 1825 DrawTextEx( | 3373 DrawTextEx( |
| 1826 font, | 3374 font, |
| 1827 "Undo", | 3375 "Undo", |
| 1828 (Vector2){undo.x + 12.0f * unit, undo.y + 10.0f * unit}, | 3376 (Vector2){undo.x + 12.0f * unit, undo.y + 10.0f * unit}, |
| 1829 13.0f * unit, | 3377 13.0f * unit, |
| 1830 0.0f, | 3378 0.0f, |
| 1831 Fade(p_theme->text, opacity)); | 3379 Canvas_Color_Fade(p_theme->text, opacity)); |
| 1832 Vector2 close = { | 3380 Vector2 close = { |
| 1833 bounds.x + bounds.width - 24.0f * unit, | 3381 bounds.x + bounds.width - 24.0f * unit, |
| 1834 bounds.y + bounds.height * 0.5f, | 3382 bounds.y + bounds.height * 0.5f, |
| 1835 }; | 3383 }; |
| 1836 DrawLineEx( | 3384 DrawLineEx( |
| 1837 (Vector2){close.x - 4.0f * unit, close.y - 4.0f * unit}, | 3385 (Vector2){close.x - 4.0f * unit, close.y - 4.0f * unit}, |
| 1838 (Vector2){close.x + 4.0f * unit, close.y + 4.0f * unit}, | 3386 (Vector2){close.x + 4.0f * unit, close.y + 4.0f * unit}, |
| 1839 1.5f / zoom, | 3387 1.5f / zoom, |
| 1840 Fade(p_theme->text_muted, opacity)); | 3388 Canvas_Color_Fade(p_theme->text_muted, opacity)); |
| 1841 DrawLineEx( | 3389 DrawLineEx( |
| 1842 (Vector2){close.x + 4.0f * unit, close.y - 4.0f * unit}, | 3390 (Vector2){close.x + 4.0f * unit, close.y - 4.0f * unit}, |
| 1843 (Vector2){close.x - 4.0f * unit, close.y + 4.0f * unit}, | 3391 (Vector2){close.x - 4.0f * unit, close.y + 4.0f * unit}, |
| 1844 1.5f / zoom, | 3392 1.5f / zoom, |
| 1845 Fade(p_theme->text_muted, opacity)); | 3393 Canvas_Color_Fade(p_theme->text_muted, opacity)); |
| 1846 break; | 3394 break; |
| 1847 } | 3395 } |
| 1848 case CANVAS_ENTITY_SCROLL_AREA: { | 3396 case CANVAS_ENTITY_SCROLL_AREA: { |
| 1849 Rectangle bounds = { | 3397 Rectangle bounds = { |
| 1850 p_entity->position.x, | 3398 p_entity->position.x, |
| 1894 ((float)p_entity->value / 240.0f) * (bounds.height - 106.0f); | 3442 ((float)p_entity->value / 240.0f) * (bounds.height - 106.0f); |
| 1895 DrawRectangleRounded( | 3443 DrawRectangleRounded( |
| 1896 (Rectangle){bounds.x + bounds.width - 14.0f, thumb_y, 5.0f, 42.0f}, | 3444 (Rectangle){bounds.x + bounds.width - 14.0f, thumb_y, 5.0f, 42.0f}, |
| 1897 0.50f, | 3445 0.50f, |
| 1898 6, | 3446 6, |
| 1899 Fade(p_theme->text_muted, 0.82f)); | 3447 Canvas_Color_Fade(p_theme->text_muted, 0.82f)); |
| 1900 break; | 3448 break; |
| 1901 } | 3449 } |
| 1902 case CANVAS_ENTITY_IMAGE: { | 3450 case CANVAS_ENTITY_IMAGE: { |
| 1903 Rectangle bounds = { | 3451 Rectangle bounds = { |
| 1904 p_entity->position.x, | 3452 p_entity->position.x, |
| 1955 0.1f, | 3503 0.1f, |
| 1956 p_theme->text_muted); | 3504 p_theme->text_muted); |
| 1957 #endif | 3505 #endif |
| 1958 break; | 3506 break; |
| 1959 } | 3507 } |
| 3508 case CANVAS_ENTITY_LUCIDE_GALLERY: { | |
| 3509 Rectangle bounds = { | |
| 3510 p_entity->position.x, | |
| 3511 p_entity->position.y, | |
| 3512 p_entity->size.x, | |
| 3513 p_entity->size.y, | |
| 3514 }; | |
| 3515 Canvas_Theme_Draw_Shadow(p_theme, bounds, 0.04f, 14, zoom, 1.0f); | |
| 3516 DrawRectangleRounded(bounds, 0.04f, 14, p_theme->surface); | |
| 3517 DrawRectangleRoundedLinesEx( | |
| 3518 bounds, | |
| 3519 0.04f, | |
| 3520 14, | |
| 3521 1.0f / zoom, | |
| 3522 p_theme->border); | |
| 3523 DrawTextEx( | |
| 3524 font, | |
| 3525 "Lucide icons", | |
| 3526 (Vector2){bounds.x + 24.0f, bounds.y + 22.0f}, | |
| 3527 26.0f, | |
| 3528 0.0f, | |
| 3529 p_theme->text); | |
| 3530 DrawTextEx( | |
| 3531 font, | |
| 3532 TextFormat( | |
| 3533 "%d of %d icons - Raylib vectors", | |
| 3534 Canvas_Lucide_Count_Matches(p_entity->text), | |
| 3535 CANVAS_LUCIDE_ICON_COUNT), | |
| 3536 (Vector2){bounds.x + 24.0f, bounds.y + 56.0f}, | |
| 3537 13.0f, | |
| 3538 0.0f, | |
| 3539 p_theme->text_muted); | |
| 3540 Rectangle search = Canvas_Lucide_Search_Bounds(p_entity); | |
| 3541 DrawRectangleRounded( | |
| 3542 search, | |
| 3543 0.24f, | |
| 3544 10, | |
| 3545 p_entity->active ? p_theme->surface : p_theme->surface_muted); | |
| 3546 DrawRectangleRoundedLinesEx( | |
| 3547 search, | |
| 3548 0.24f, | |
| 3549 10, | |
| 3550 (p_entity->active ? 2.0f : 1.0f) / zoom, | |
| 3551 p_entity->active ? p_theme->accent : p_theme->border); | |
| 3552 const char *p_search_text = | |
| 3553 p_entity->text[0] ? p_entity->text : "Search icons"; | |
| 3554 DrawTextEx( | |
| 3555 font, | |
| 3556 p_search_text, | |
| 3557 (Vector2){search.x + 16.0f, search.y + 13.0f}, | |
| 3558 14.0f, | |
| 3559 0.0f, | |
| 3560 p_entity->text[0] ? p_theme->text : p_theme->text_muted); | |
| 3561 if (p_entity->active) { | |
| 3562 Vector2 query_size = MeasureTextEx( | |
| 3563 font, | |
| 3564 p_entity->text, | |
| 3565 14.0f, | |
| 3566 0.0f); | |
| 3567 float caret_x = search.x + 16.0f + query_size.x + 1.0f; | |
| 3568 DrawLineEx( | |
| 3569 (Vector2){caret_x, search.y + 11.0f}, | |
| 3570 (Vector2){caret_x, search.y + 31.0f}, | |
| 3571 1.0f / zoom, | |
| 3572 p_theme->accent); | |
| 3573 } | |
| 3574 DrawLineEx( | |
| 3575 (Vector2){bounds.x, bounds.y + CANVAS_LUCIDE_HEADER_HEIGHT}, | |
| 3576 (Vector2){ | |
| 3577 bounds.x + bounds.width, | |
| 3578 bounds.y + CANVAS_LUCIDE_HEADER_HEIGHT, | |
| 3579 }, | |
| 3580 1.0f / zoom, | |
| 3581 p_theme->border); | |
| 3582 | |
| 3583 float content_top = bounds.y + CANVAS_LUCIDE_HEADER_HEIGHT; | |
| 3584 float content_bottom = bounds.y + bounds.height - CANVAS_LUCIDE_PADDING; | |
| 3585 int32 visible_index = 0; | |
| 3586 for (int32 icon_index = 0; | |
| 3587 icon_index < CANVAS_LUCIDE_ICON_COUNT; | |
| 3588 icon_index++) { | |
| 3589 const Canvas_Lucide_Icon *p_icon = | |
| 3590 &CANVAS_LUCIDE_ICONS[icon_index]; | |
| 3591 if (!Canvas_Lucide_Name_Matches( | |
| 3592 p_icon->p_name, | |
| 3593 p_entity->text)) { | |
| 3594 continue; | |
| 3595 } | |
| 3596 int32 row = visible_index / CANVAS_LUCIDE_COLUMNS; | |
| 3597 int32 column = visible_index % CANVAS_LUCIDE_COLUMNS; | |
| 3598 visible_index++; | |
| 3599 float card_x = | |
| 3600 bounds.x + CANVAS_LUCIDE_PADDING + | |
| 3601 (float)column * | |
| 3602 (CANVAS_LUCIDE_CARD_WIDTH + CANVAS_LUCIDE_GAP); | |
| 3603 float card_y = | |
| 3604 content_top + CANVAS_LUCIDE_PADDING + | |
| 3605 (float)row * | |
| 3606 (CANVAS_LUCIDE_CARD_HEIGHT + CANVAS_LUCIDE_GAP) - | |
| 3607 (float)p_entity->value; | |
| 3608 if (card_y < content_top || | |
| 3609 card_y + CANVAS_LUCIDE_CARD_HEIGHT > content_bottom) { | |
| 3610 continue; | |
| 3611 } | |
| 3612 Rectangle card = { | |
| 3613 card_x, | |
| 3614 card_y, | |
| 3615 CANVAS_LUCIDE_CARD_WIDTH, | |
| 3616 CANVAS_LUCIDE_CARD_HEIGHT, | |
| 3617 }; | |
| 3618 DrawRectangleRounded(card, 0.10f, 8, p_theme->surface_muted); | |
| 3619 DrawRectangleRoundedLinesEx( | |
| 3620 card, | |
| 3621 0.10f, | |
| 3622 8, | |
| 3623 1.0f / zoom, | |
| 3624 p_theme->border); | |
| 3625 | |
| 3626 float icon_scale = 2.0f; | |
| 3627 Vector2 icon_origin = { | |
| 3628 card.x + (card.width - 24.0f * icon_scale) * 0.5f, | |
| 3629 card.y + 13.0f, | |
| 3630 }; | |
| 3631 Canvas_Lucide_Draw_Icon( | |
| 3632 p_icon, | |
| 3633 icon_origin, | |
| 3634 icon_scale, | |
| 3635 1.8f, | |
| 3636 p_theme->text); | |
| 3637 Vector2 label_size = MeasureTextEx( | |
| 3638 font, | |
| 3639 p_icon->p_name, | |
| 3640 11.0f, | |
| 3641 0.0f); | |
| 3642 float label_x = card.x + (card.width - label_size.x) * 0.5f; | |
| 3643 if (label_x < card.x + 5.0f) label_x = card.x + 5.0f; | |
| 3644 DrawTextEx( | |
| 3645 font, | |
| 3646 p_icon->p_name, | |
| 3647 (Vector2){label_x, card.y + 73.0f}, | |
| 3648 11.0f, | |
| 3649 0.0f, | |
| 3650 p_theme->text_muted); | |
| 3651 } | |
| 3652 | |
| 3653 float max_scroll = Canvas_Lucide_Max_Scroll(p_entity); | |
| 3654 if (max_scroll > 0.0f) { | |
| 3655 float track_height = bounds.height - CANVAS_LUCIDE_HEADER_HEIGHT - 24.0f; | |
| 3656 float thumb_height = fmaxf( | |
| 3657 36.0f, | |
| 3658 track_height * bounds.height / | |
| 3659 Canvas_Lucide_Content_Height(p_entity->text)); | |
| 3660 float thumb_y = | |
| 3661 content_top + 12.0f + | |
| 3662 ((float)p_entity->value / max_scroll) * | |
| 3663 (track_height - thumb_height); | |
| 3664 DrawRectangleRounded( | |
| 3665 (Rectangle){ | |
| 3666 bounds.x + bounds.width - 10.0f, | |
| 3667 thumb_y, | |
| 3668 5.0f, | |
| 3669 thumb_height, | |
| 3670 }, | |
| 3671 0.50f, | |
| 3672 6, | |
| 3673 Canvas_Color_Fade(p_theme->text_muted, 0.72f)); | |
| 3674 } | |
| 3675 break; | |
| 3676 } | |
| 1960 default: | 3677 default: |
| 1961 break; | 3678 break; |
| 1962 } | 3679 } |
| 1963 | 3680 |
| 1964 if (p_entity->pinned) { | 3681 if (p_entity->pinned) { |
| 1965 float radius = 7.0f / zoom; | 3682 const Canvas_Lucide_Icon *p_pin = Canvas_Lucide_Find_Icon("pin"); |
| 1966 Vector2 center = { | 3683 float scale = 0.72f / zoom; |
| 1967 p_entity->position.x + p_entity->size.x - 10.0f / zoom, | 3684 Vector2 origin = { |
| 1968 p_entity->position.y + 10.0f / zoom, | 3685 p_entity->position.x + p_entity->size.x - 21.0f / zoom, |
| 3686 p_entity->position.y + 3.0f / zoom, | |
| 1969 }; | 3687 }; |
| 1970 DrawCircleV(center, radius, p_theme->accent); | 3688 Canvas_Lucide_Draw_Icon( |
| 1971 DrawLineEx( | 3689 p_pin, |
| 1972 (Vector2){center.x, center.y - 3.5f / zoom}, | 3690 origin, |
| 1973 (Vector2){center.x, center.y + 4.0f / zoom}, | 3691 scale, |
| 1974 1.5f / zoom, | 3692 1.8f / zoom, |
| 1975 WHITE); | 3693 p_theme->pin); |
| 1976 } | 3694 } |
| 1977 if (selected && p_entity->type != CANVAS_ENTITY_NOTIFICATION) { | 3695 if (selected && p_entity->type != CANVAS_ENTITY_NOTIFICATION) { |
| 1978 Canvas_Draw_Selection(p_entity, zoom); | 3696 Canvas_Draw_Selection(p_entity, zoom, p_theme->selection); |
| 1979 } | 3697 } |
| 1980 } | 3698 } |
| 1981 | 3699 |
| 1982 static void Canvas_Draw_Dropdown_Menu( | 3700 static void Canvas_Draw_Dropdown_Menu( |
| 1983 const Canvas_Entity *p_entity, | 3701 const Canvas_Entity *p_entity, |
| 1984 Font font, | 3702 Font font, |
| 1985 Vector2 world_pointer, | 3703 Vector2 world_pointer, |
| 1986 float zoom, | 3704 float zoom, |
| 1987 const Canvas_Theme *p_theme) | 3705 const Canvas_Theme *p_theme) |
| 1988 { | 3706 { |
| 3707 float opacity = Canvas_Entity_Visibility(p_entity); | |
| 3708 if (opacity <= 0.001f) return; | |
| 3709 Canvas_Theme faded_theme = Canvas_Theme_With_Opacity(p_theme, opacity); | |
| 3710 p_theme = &faded_theme; | |
| 3711 | |
| 1989 static const char *options[] = { | 3712 static const char *options[] = { |
| 1990 "Design", | 3713 "Design", |
| 1991 "Engineering", | 3714 "Engineering", |
| 1992 "Research", | 3715 "Research", |
| 1993 }; | 3716 }; |
| 2036 p_theme->accent); | 3759 p_theme->accent); |
| 2037 } | 3760 } |
| 2038 } | 3761 } |
| 2039 } | 3762 } |
| 2040 | 3763 |
| 2041 void Canvas_Draw_World( | 3764 static Camera2D Canvas_Raylib_Camera(const Canvas_Camera *p_camera) |
| 2042 const Canvas_Camera *p_camera, | 3765 { |
| 2043 const Canvas_Scene *p_scene, | 3766 return (Camera2D){ |
| 2044 Font font, | |
| 2045 const Canvas_Theme *p_theme) | |
| 2046 { | |
| 2047 Vector2 world_pointer = Canvas_Camera_Screen_To_World(p_camera, GetMousePosition()); | |
| 2048 Camera2D camera = { | |
| 2049 .offset = { | 3767 .offset = { |
| 2050 (float)p_camera->viewport_width * 0.5f, | 3768 (float)p_camera->viewport_width * 0.5f, |
| 2051 (float)p_camera->viewport_height * 0.5f, | 3769 (float)p_camera->viewport_height * 0.5f, |
| 2052 }, | 3770 }, |
| 2053 .target = p_camera->target, | 3771 .target = p_camera->target, |
| 2054 .rotation = 0.0f, | 3772 .rotation = 0.0f, |
| 2055 .zoom = p_camera->zoom, | 3773 .zoom = p_camera->zoom, |
| 2056 }; | 3774 }; |
| 2057 | 3775 } |
| 2058 BeginMode2D(camera); | 3776 |
| 3777 void Canvas_Draw_World_Background( | |
| 3778 const Canvas_Camera *p_camera, | |
| 3779 const Canvas_Scene *p_scene, | |
| 3780 const Canvas_Theme *p_theme) | |
| 3781 { | |
| 3782 BeginMode2D(Canvas_Raylib_Camera(p_camera)); | |
| 2059 if (p_scene->show_grid) Canvas_Draw_Grid(p_camera, p_theme); | 3783 if (p_scene->show_grid) Canvas_Draw_Grid(p_camera, p_theme); |
| 2060 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { | 3784 EndMode2D(); |
| 2061 Canvas_Draw_Entity( | 3785 } |
| 2062 &p_scene->p_entities[index], | 3786 |
| 2063 font, | 3787 void Canvas_Draw_World_Entity( |
| 2064 p_camera->zoom, | 3788 const Canvas_Camera *p_camera, |
| 2065 (int32)index == p_scene->selected_index, | 3789 const Canvas_Scene *p_scene, |
| 2066 (int32)index == p_scene->pressed_index && IsMouseButtonDown(MOUSE_BUTTON_LEFT), | 3790 size_t entity_index, |
| 2067 p_theme); | 3791 Font font, |
| 2068 } | 3792 const Canvas_Theme *p_theme) |
| 3793 { | |
| 3794 if (entity_index >= Dowa_Array_Length(p_scene->p_entities)) return; | |
| 3795 BeginMode2D(Canvas_Raylib_Camera(p_camera)); | |
| 3796 Canvas_Draw_Entity( | |
| 3797 &p_scene->p_entities[entity_index], | |
| 3798 font, | |
| 3799 p_camera->zoom, | |
| 3800 (int32)entity_index == p_scene->selected_index, | |
| 3801 (int32)entity_index == p_scene->pressed_index && | |
| 3802 IsMouseButtonDown(MOUSE_BUTTON_LEFT), | |
| 3803 p_theme); | |
| 3804 EndMode2D(); | |
| 3805 } | |
| 3806 | |
| 3807 void Canvas_Draw_Web_Chrome_Entity( | |
| 3808 const Canvas_Camera *p_camera, | |
| 3809 const Canvas_Scene *p_scene, | |
| 3810 size_t entity_index, | |
| 3811 Font font, | |
| 3812 const Canvas_Theme *p_theme, | |
| 3813 const char *p_url, | |
| 3814 boolean can_go_back, | |
| 3815 boolean can_go_forward) | |
| 3816 { | |
| 3817 if (entity_index >= Dowa_Array_Length(p_scene->p_entities)) return; | |
| 3818 const Canvas_Entity *p_entity = &p_scene->p_entities[entity_index]; | |
| 3819 if (p_entity->type != CANVAS_ENTITY_WEB_CONTENT || | |
| 3820 p_entity->web_chrome_amount <= 0.01f) { | |
| 3821 return; | |
| 3822 } | |
| 3823 | |
| 3824 float zoom = p_camera->zoom; | |
| 3825 float opacity = Canvas_Entity_Visibility(p_entity); | |
| 3826 Canvas_Theme faded_theme = Canvas_Theme_With_Opacity(p_theme, opacity); | |
| 3827 p_theme = &faded_theme; | |
| 3828 Rectangle toolbar = Canvas_Web_Toolbar_Bounds(p_camera, p_entity); | |
| 3829 Rectangle back = Canvas_Web_Toolbar_Button_Bounds(toolbar, zoom, 0); | |
| 3830 Rectangle forward = Canvas_Web_Toolbar_Button_Bounds(toolbar, zoom, 1); | |
| 3831 Rectangle address = Canvas_Web_Address_Bounds(toolbar, zoom); | |
| 3832 Vector2 pointer = Canvas_Camera_Screen_To_World( | |
| 3833 p_camera, | |
| 3834 GetMousePosition()); | |
| 3835 | |
| 3836 BeginMode2D(Canvas_Raylib_Camera(p_camera)); | |
| 3837 Vector2 toolbar_screen = Canvas_Camera_World_To_Screen( | |
| 3838 p_camera, | |
| 3839 (Vector2){toolbar.x, toolbar.y}); | |
| 3840 BeginScissorMode( | |
| 3841 (int32)toolbar_screen.x, | |
| 3842 (int32)toolbar_screen.y, | |
| 3843 (int32)(toolbar.width * zoom), | |
| 3844 (int32)fmaxf(1.0f, toolbar.height * zoom)); | |
| 3845 DrawRectangleRounded(toolbar, 0.18f, 10, p_theme->surface); | |
| 3846 DrawRectangleRoundedLinesEx( | |
| 3847 toolbar, | |
| 3848 0.18f, | |
| 3849 10, | |
| 3850 1.0f / zoom, | |
| 3851 p_theme->border); | |
| 3852 | |
| 3853 Rectangle buttons[] = {back, forward}; | |
| 3854 boolean enabled[] = {can_go_back, can_go_forward}; | |
| 3855 const char *icon_names[] = {"arrow-left", "arrow-right"}; | |
| 3856 for (int32 index = 0; index < 2; index++) { | |
| 3857 boolean hovered = | |
| 3858 CheckCollisionPointRec(pointer, buttons[index]) && enabled[index]; | |
| 3859 if (hovered) { | |
| 3860 DrawRectangleRounded( | |
| 3861 buttons[index], | |
| 3862 0.28f, | |
| 3863 8, | |
| 3864 p_theme->accent_soft); | |
| 3865 } | |
| 3866 Canvas_Lucide_Draw_Icon( | |
| 3867 Canvas_Lucide_Find_Icon(icon_names[index]), | |
| 3868 (Vector2){ | |
| 3869 buttons[index].x + 5.5f / zoom, | |
| 3870 buttons[index].y + 5.5f / zoom, | |
| 3871 }, | |
| 3872 0.71f / zoom, | |
| 3873 1.8f / zoom, | |
| 3874 enabled[index] ? p_theme->text : p_theme->text_muted); | |
| 3875 } | |
| 3876 | |
| 3877 DrawRectangleRounded(address, 0.32f, 8, p_theme->surface_muted); | |
| 3878 DrawRectangleRoundedLinesEx( | |
| 3879 address, | |
| 3880 0.32f, | |
| 3881 8, | |
| 3882 1.0f / zoom, | |
| 3883 p_theme->border); | |
| 3884 boolean editing = | |
| 3885 p_scene->editing_web_url && | |
| 3886 p_scene->web_url_entity_id == p_entity->id; | |
| 3887 const char *p_display_url = editing ? | |
| 3888 p_scene->web_url_draft : | |
| 3889 p_url; | |
| 3890 char visible_url[CANVAS_ENTITY_TEXT_CAPACITY] = {0}; | |
| 3891 snprintf( | |
| 3892 visible_url, | |
| 3893 sizeof(visible_url), | |
| 3894 "%s", | |
| 3895 p_display_url && p_display_url[0] ? p_display_url : "about:blank"); | |
| 3896 float font_size = 13.0f / zoom; | |
| 3897 float max_width = address.width - 18.0f / zoom; | |
| 3898 size_t url_length = strlen(visible_url); | |
| 3899 while (!editing && url_length > 3 && | |
| 3900 MeasureTextEx(font, visible_url, font_size, 0.0f).x > max_width) { | |
| 3901 url_length--; | |
| 3902 visible_url[url_length] = '\0'; | |
| 3903 } | |
| 3904 if (!editing && | |
| 3905 p_display_url && | |
| 3906 strlen(p_display_url) > url_length && | |
| 3907 url_length > 3) { | |
| 3908 visible_url[url_length - 3] = '.'; | |
| 3909 visible_url[url_length - 2] = '.'; | |
| 3910 visible_url[url_length - 1] = '.'; | |
| 3911 } | |
| 3912 Vector2 address_screen = Canvas_Camera_World_To_Screen( | |
| 3913 p_camera, | |
| 3914 (Vector2){address.x, address.y}); | |
| 3915 float address_clip_height = fminf( | |
| 3916 address.height, | |
| 3917 toolbar.y + toolbar.height - address.y); | |
| 3918 if (address_clip_height < 1.0f / zoom) { | |
| 3919 EndScissorMode(); | |
| 3920 EndMode2D(); | |
| 3921 return; | |
| 3922 } | |
| 3923 BeginScissorMode( | |
| 3924 (int32)address_screen.x, | |
| 3925 (int32)address_screen.y, | |
| 3926 (int32)(address.width * zoom), | |
| 3927 (int32)(address_clip_height * zoom)); | |
| 3928 float text_x = address.x + 9.0f / zoom - | |
| 3929 (editing ? p_scene->web_url_scroll_x : 0.0f); | |
| 3930 if (editing && | |
| 3931 p_scene->web_url_cursor != p_scene->web_url_selection_anchor) { | |
| 3932 int32 selection_start = 0; | |
| 3933 int32 selection_end = 0; | |
| 3934 Canvas_Web_URL_Selection_Bounds( | |
| 3935 p_scene, | |
| 3936 &selection_start, | |
| 3937 &selection_end); | |
| 3938 char prefix[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 3939 char selected[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 3940 memcpy(prefix, visible_url, (size_t)selection_start); | |
| 3941 prefix[selection_start] = '\0'; | |
| 3942 memcpy( | |
| 3943 selected, | |
| 3944 visible_url + selection_start, | |
| 3945 (size_t)(selection_end - selection_start)); | |
| 3946 selected[selection_end - selection_start] = '\0'; | |
| 3947 float selection_x = | |
| 3948 text_x + MeasureTextEx(font, prefix, font_size, 0.0f).x; | |
| 3949 float selection_width = | |
| 3950 MeasureTextEx(font, selected, font_size, 0.0f).x; | |
| 3951 DrawRectangleRec( | |
| 3952 (Rectangle){ | |
| 3953 selection_x, | |
| 3954 address.y + 5.0f / zoom, | |
| 3955 selection_width, | |
| 3956 18.0f / zoom, | |
| 3957 }, | |
| 3958 p_theme->accent_soft); | |
| 3959 } | |
| 3960 DrawTextEx( | |
| 3961 font, | |
| 3962 visible_url, | |
| 3963 (Vector2){ | |
| 3964 text_x, | |
| 3965 address.y + 7.0f / zoom, | |
| 3966 }, | |
| 3967 font_size, | |
| 3968 0.0f, | |
| 3969 editing ? p_theme->text : p_theme->text_muted); | |
| 3970 if (editing && ((int32)(GetTime() * 2.0) % 2) == 0) { | |
| 3971 char prefix[CANVAS_ENTITY_TEXT_CAPACITY]; | |
| 3972 memcpy( | |
| 3973 prefix, | |
| 3974 visible_url, | |
| 3975 (size_t)p_scene->web_url_cursor); | |
| 3976 prefix[p_scene->web_url_cursor] = '\0'; | |
| 3977 float caret_x = | |
| 3978 text_x + MeasureTextEx(font, prefix, font_size, 0.0f).x; | |
| 3979 DrawLineEx( | |
| 3980 (Vector2){caret_x, address.y + 6.0f / zoom}, | |
| 3981 (Vector2){caret_x, address.y + 22.0f / zoom}, | |
| 3982 1.5f / zoom, | |
| 3983 p_theme->accent); | |
| 3984 } | |
| 3985 EndScissorMode(); | |
| 3986 EndMode2D(); | |
| 3987 } | |
| 3988 | |
| 3989 void Canvas_Draw_World_Overlays( | |
| 3990 const Canvas_Camera *p_camera, | |
| 3991 const Canvas_Scene *p_scene, | |
| 3992 Font font, | |
| 3993 const Canvas_Theme *p_theme) | |
| 3994 { | |
| 3995 Vector2 world_pointer = | |
| 3996 Canvas_Camera_Screen_To_World(p_camera, GetMousePosition()); | |
| 3997 BeginMode2D(Canvas_Raylib_Camera(p_camera)); | |
| 2069 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { | 3998 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { |
| 2070 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; | 3999 const Canvas_Entity *p_entity = &p_scene->p_entities[index]; |
| 2071 if (p_entity->type == CANVAS_ENTITY_DROPDOWN && p_entity->active) { | 4000 if (p_entity->type == CANVAS_ENTITY_DROPDOWN && p_entity->active) { |
| 2072 Canvas_Draw_Dropdown_Menu( | 4001 Canvas_Draw_Dropdown_Menu( |
| 2073 p_entity, | 4002 p_entity, |
| 2076 p_camera->zoom, | 4005 p_camera->zoom, |
| 2077 p_theme); | 4006 p_theme); |
| 2078 } | 4007 } |
| 2079 } | 4008 } |
| 2080 EndMode2D(); | 4009 EndMode2D(); |
| 4010 } | |
| 4011 | |
| 4012 void Canvas_Draw_World( | |
| 4013 const Canvas_Camera *p_camera, | |
| 4014 const Canvas_Scene *p_scene, | |
| 4015 Font font, | |
| 4016 const Canvas_Theme *p_theme) | |
| 4017 { | |
| 4018 Canvas_Draw_World_Background(p_camera, p_scene, p_theme); | |
| 4019 for (size_t index = 0; index < Dowa_Array_Length(p_scene->p_entities); index++) { | |
| 4020 Canvas_Draw_World_Entity( | |
| 4021 p_camera, | |
| 4022 p_scene, | |
| 4023 index, | |
| 4024 font, | |
| 4025 p_theme); | |
| 4026 } | |
| 4027 Canvas_Draw_World_Overlays(p_camera, p_scene, font, p_theme); | |
| 2081 } | 4028 } |
| 2082 | 4029 |
| 2083 const char *Canvas_Entity_Type_Name(Canvas_Entity_Type type) | 4030 const char *Canvas_Entity_Type_Name(Canvas_Entity_Type type) |
| 2084 { | 4031 { |
| 2085 switch (type) { | 4032 switch (type) { |
| 2097 case CANVAS_ENTITY_TABLE: return "Table"; | 4044 case CANVAS_ENTITY_TABLE: return "Table"; |
| 2098 case CANVAS_ENTITY_NOTIFICATION: return "Notification"; | 4045 case CANVAS_ENTITY_NOTIFICATION: return "Notification"; |
| 2099 case CANVAS_ENTITY_SCROLL_AREA: return "Scrollable area"; | 4046 case CANVAS_ENTITY_SCROLL_AREA: return "Scrollable area"; |
| 2100 case CANVAS_ENTITY_IMAGE: return "Image"; | 4047 case CANVAS_ENTITY_IMAGE: return "Image"; |
| 2101 case CANVAS_ENTITY_WEB_CONTENT: return "Web content"; | 4048 case CANVAS_ENTITY_WEB_CONTENT: return "Web content"; |
| 4049 case CANVAS_ENTITY_LUCIDE_GALLERY: return "Lucide gallery"; | |
| 2102 default: return "Unknown"; | 4050 default: return "Unknown"; |
| 2103 } | 4051 } |
| 2104 } | 4052 } |