PatchworkOS
Loading...
Searching...
No Matches
drawable.c
Go to the documentation of this file.
1#include "internal.h"
2
3#include <math.h>
4#include <stdlib.h>
5#include <string.h>
6
7void draw_rect(drawable_t* draw, const rect_t* rect, pixel_t pixel)
8{
9 if (draw == NULL || rect == NULL)
10 {
11 return;
12 }
13
14 rect_t fitRect = *rect;
15 RECT_FIT(&fitRect, &draw->contentRect);
16
17 for (int64_t y = fitRect.top; y < fitRect.bottom; y++)
18 {
19 memset32(&draw->buffer[fitRect.left + y * draw->stride], pixel, RECT_WIDTH(&fitRect));
20 }
21
22 draw_invalidate(draw, &fitRect);
23}
24
25typedef struct edge
26{
27 int64_t yMin; // scanline where edge starts
28 int64_t yMax; // scanline where edge ends
29 double x; // current x intersection
30 double invSlope; // dx/dy for incrementing x
31} edge_t;
32
33static inline int edge_compare(const void* a, const void* b)
34{
35 edge_t* edgeA = *(edge_t**)a;
36 edge_t* edgeB = *(edge_t**)b;
37 if (edgeA->x < edgeB->x)
38 {
39 return -1;
40 }
41 else if (edgeA->x > edgeB->x)
42 {
43 return 1;
44 }
45 return 0;
46}
47
48void draw_polygon(drawable_t* draw, const point_t* points, uint64_t pointCount, pixel_t pixel)
49{
50 if (draw == NULL || points == NULL || pointCount < 3)
51 {
52 return;
53 }
54
55 int64_t minY = INT64_MAX;
56 int64_t maxY = INT64_MIN;
57 int64_t minX = INT64_MAX;
58 int64_t maxX = INT64_MIN;
59
60 for (uint64_t i = 0; i < pointCount; i++)
61 {
62 minY = MIN(minY, points[i].y);
63 maxY = MAX(maxY, points[i].y);
64 minX = MIN(minX, points[i].x);
65 maxX = MAX(maxX, points[i].x);
66 }
67
68 edge_t edges[pointCount];
69 uint64_t edgeCount = 0;
70 for (uint64_t i = 0; i < pointCount; i++)
71 {
72 point_t p1 = points[i];
73 point_t p2 = points[(i + 1) % pointCount];
74 if (p1.y == p2.y)
75 {
76 continue;
77 }
78 edge_t* edge = &edges[edgeCount++];
79 if (p1.y < p2.y)
80 {
81 edge->yMin = p1.y;
82 edge->yMax = p2.y;
83 edge->x = p1.x;
84 edge->invSlope = (double)(p2.x - p1.x) / (double)(p2.y - p1.y);
85 }
86 else
87 {
88 edge->yMin = p2.y;
89 edge->yMax = p1.y;
90 edge->x = p2.x;
91 edge->invSlope = (double)(p1.x - p2.x) / (double)(p1.y - p2.y);
92 }
93 }
94
95 const double samples[4][2] = {{0.125, 0.375}, {0.375, 0.125}, {0.625, 0.875}, {0.875, 0.625}};
96 const int sampleCount = 4;
97
98 edge_t* activeEdges[pointCount];
99 uint64_t activeEdgeCount = 0;
100
101 for (int64_t y = minY; y <= maxY; y++)
102 {
103 for (uint64_t i = 0; i < edgeCount; i++)
104 {
105 if (edges[i].yMin == y)
106 {
107 activeEdges[activeEdgeCount++] = &edges[i];
108 }
109 }
110
111 for (uint64_t i = 0; i < activeEdgeCount; i++)
112 {
113 if (activeEdges[i]->yMax <= y)
114 {
115 activeEdges[i] = activeEdges[--activeEdgeCount];
116 i--;
117 }
118 }
119
120 if (activeEdgeCount < 2)
121 {
122 continue;
123 }
124
125 qsort(activeEdges, activeEdgeCount, sizeof(edge_t*), edge_compare);
126
127 int64_t scanMinX = (int64_t)(activeEdges[0]->x);
128 int64_t scanMaxX = (int64_t)(activeEdges[activeEdgeCount - 1]->x);
129
130 scanMinX = MAX(scanMinX - 1, draw->contentRect.left);
131 scanMaxX = MIN(scanMaxX + 1, draw->contentRect.right - 1);
132
133 if (y < draw->contentRect.top || y >= draw->contentRect.bottom)
134 {
135 goto update_edges;
136 }
137
138 for (int64_t x = scanMinX; x <= scanMaxX; x++)
139 {
140 int samplesInside = 0;
141
142 for (int s = 0; s < sampleCount; s++)
143 {
144 double sampleX = x + samples[s][0];
145 double sampleY = y + samples[s][1];
146
147 if (polygon_contains(sampleX, sampleY, points, pointCount))
148 {
149 samplesInside++;
150 }
151 }
152
153 if (samplesInside == 0)
154 {
155 continue;
156 }
157 else if (samplesInside == sampleCount)
158 {
159 draw->buffer[x + y * draw->stride] = pixel;
160 }
161 else
162 {
163 uint8_t originalAlpha = PIXEL_ALPHA(pixel);
164 uint8_t newAlpha = (originalAlpha * samplesInside) / sampleCount;
165
166 pixel_t aaPixel = PIXEL_ARGB(newAlpha, PIXEL_RED(pixel), PIXEL_GREEN(pixel), PIXEL_BLUE(pixel));
167 PIXEL_BLEND(&draw->buffer[x + y * draw->stride], &aaPixel);
168 }
169 }
170
171update_edges:
172 for (uint64_t i = 0; i < activeEdgeCount; i++)
173 {
174 activeEdges[i]->x += activeEdges[i]->invSlope;
175 }
176 }
177}
178
179void draw_line(drawable_t* draw, const point_t* start, const point_t* end, pixel_t pixel, uint32_t thickness)
180{
181 if (draw == NULL || start == NULL || end == NULL || thickness == 0)
182 {
183 return;
184 }
185
186 point_t fitStart = {CLAMP(start->x, draw->contentRect.left, draw->contentRect.right - 1),
187 CLAMP(start->y, draw->contentRect.top, draw->contentRect.bottom - 1)};
188 point_t fitEnd = {CLAMP(end->x, draw->contentRect.left, draw->contentRect.right - 1),
189 CLAMP(end->y, draw->contentRect.top, draw->contentRect.bottom - 1)};
190
191 point_t points[4];
192 double angle = atan2((double)(fitEnd.y - fitStart.y), (double)(fitEnd.x - fitStart.x));
193 double halfThickness = (double)thickness / 2.0;
194 double sinAngle = sin(angle + M_PI_2) * halfThickness;
195 double cosAngle = cos(angle + M_PI_2) * halfThickness;
196 points[0] = (point_t){.x = (int64_t)(fitStart.x + cosAngle), .y = (int64_t)(fitStart.y + sinAngle)};
197 points[1] = (point_t){.x = (int64_t)(fitStart.x - cosAngle), .y = (int64_t)(fitStart.y - sinAngle)};
198 points[2] = (point_t){.x = (int64_t)(fitEnd.x - cosAngle), .y = (int64_t)(fitEnd.y - sinAngle)};
199 points[3] = (point_t){.x = (int64_t)(fitEnd.x + cosAngle), .y = (int64_t)(fitEnd.y + sinAngle)};
200
201 draw_polygon(draw, points, 4, pixel);
202}
203
204void draw_frame(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t foreground, pixel_t background)
205{
206 if (draw == NULL || rect == NULL || width == 0)
207 {
208 return;
209 }
210
211 rect_t fitRect = *rect;
212 RECT_FIT(&fitRect, &draw->contentRect);
213
214 rect_t leftRect = (rect_t){
215 .left = fitRect.left,
216 .top = fitRect.top,
217 .right = fitRect.left + width,
218 .bottom = fitRect.bottom - width,
219 };
220 draw_rect(draw, &leftRect, foreground);
221
222 rect_t topRect = (rect_t){
223 .left = fitRect.left + width,
224 .top = fitRect.top,
225 .right = fitRect.right - width,
226 .bottom = fitRect.top + width,
227 };
228 draw_rect(draw, &topRect, foreground);
229
230 rect_t rightRect = (rect_t){
231 .left = fitRect.right - width,
232 .top = fitRect.top + width,
233 .right = fitRect.right,
234 .bottom = fitRect.bottom,
235 };
236 draw_rect(draw, &rightRect, background);
237
238 rect_t bottomRect = (rect_t){
239 .left = fitRect.left + width,
240 .top = fitRect.bottom - width,
241 .right = fitRect.right - width,
242 .bottom = fitRect.bottom,
243 };
244 draw_rect(draw, &bottomRect, background);
245
246 for (uint64_t y = 0; y < width; y++)
247 {
248 for (uint64_t x = 0; x < width; x++)
249 {
250 pixel_t color = x + y < width - 1 ? foreground : background;
251 draw->buffer[(fitRect.right - width + x) + (fitRect.top + y) * draw->stride] = color;
252 draw->buffer[(fitRect.left + x) + (fitRect.bottom - width + y) * draw->stride] = color;
253 }
254 }
255
256 draw_invalidate(draw, &fitRect);
257}
258
259void draw_dashed_outline(drawable_t* draw, const rect_t* rect, pixel_t pixel, uint32_t length, int32_t width)
260{
261 if (draw == NULL || rect == NULL || length == 0 || width <= 0)
262 {
263 return;
264 }
265
266 rect_t fitRect = *rect;
267 RECT_FIT(&fitRect, &draw->contentRect);
268
269 if (RECT_WIDTH(&fitRect) <= 0 || RECT_HEIGHT(&fitRect) <= 0)
270 {
271 return;
272 }
273
274 uint32_t totalLength = length * 2;
275
276 for (int32_t w = 0; w < width; w++)
277 {
278 if (fitRect.top + w >= draw->contentRect.top && fitRect.top + w < draw->contentRect.bottom)
279 {
280 int64_t y = fitRect.top + w;
281 for (int64_t x = fitRect.left; x < fitRect.right; x++)
282 {
283 uint32_t inPattern = (x - fitRect.left) % totalLength;
284 if (inPattern < length)
285 {
286 draw->buffer[x + y * draw->stride] = pixel;
287 }
288 }
289 }
290
291 if (fitRect.bottom - 1 - w >= draw->contentRect.top && fitRect.bottom - 1 - w < draw->contentRect.bottom &&
292 RECT_HEIGHT(&fitRect) > 1 && fitRect.bottom - 1 - w > fitRect.top + w)
293 {
294 int64_t y = fitRect.bottom - 1 - w;
295 for (int64_t x = fitRect.left; x < fitRect.right; x++)
296 {
297 uint32_t inPattern = (x - fitRect.left) % totalLength;
298 if (inPattern < length)
299 {
300 draw->buffer[x + y * draw->stride] = pixel;
301 }
302 }
303 }
304 }
305
306 for (int32_t w = 0; w < width; w++)
307 {
308 if (fitRect.left + w >= draw->contentRect.left && fitRect.left + w < draw->contentRect.right)
309 {
310 int64_t x = fitRect.left + w;
311 for (int64_t y = fitRect.top + width; y < fitRect.bottom - width; y++)
312 {
313 uint32_t inPattern = (y - fitRect.top - width) % totalLength;
314 if (inPattern < length)
315 {
316 draw->buffer[x + y * draw->stride] = pixel;
317 }
318 }
319 }
320
321 if (fitRect.right - 1 - w >= draw->contentRect.left && fitRect.right - 1 - w < draw->contentRect.right &&
322 RECT_WIDTH(&fitRect) > 1 && fitRect.right - 1 - w > fitRect.left + w)
323 {
324 int64_t x = fitRect.right - 1 - w;
325 for (int64_t y = fitRect.top + width; y < fitRect.bottom - width; y++)
326 {
327 uint32_t inPattern = (y - fitRect.top - width) % totalLength;
328 if (inPattern < length)
329 {
330 draw->buffer[x + y * draw->stride] = pixel;
331 }
332 }
333 }
334 }
335
336 draw_invalidate(draw, &fitRect);
337}
338
339void draw_bezel(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t pixel)
340{
341 if (draw == NULL || rect == NULL || width == 0)
342 {
343 return;
344 }
345
346 rect_t fitRect = *rect;
347 RECT_FIT(&fitRect, &draw->contentRect);
348
349 rect_t leftRect = (rect_t){
350 .left = fitRect.left,
351 .top = fitRect.top + width - width / 2,
352 .right = fitRect.left + width,
353 .bottom = fitRect.bottom - width + width / 2,
354 };
355 draw_rect(draw, &leftRect, pixel);
356
357 rect_t topRect = (rect_t){
358 .left = fitRect.left + width - width / 2,
359 .top = fitRect.top,
360 .right = fitRect.right - width + width / 2,
361 .bottom = fitRect.top + width,
362 };
363 draw_rect(draw, &topRect, pixel);
364
365 rect_t rightRect = (rect_t){
366 .left = fitRect.right - width,
367 .top = fitRect.top + width - width / 2,
368 .right = fitRect.right,
369 .bottom = fitRect.bottom - width + width / 2,
370 };
371 draw_rect(draw, &rightRect, pixel);
372
373 rect_t bottomRect = (rect_t){
374 .left = fitRect.left + width - width / 2,
375 .top = fitRect.bottom - width,
376 .right = fitRect.right - width + width / 2,
377 .bottom = fitRect.bottom,
378 };
379 draw_rect(draw, &bottomRect, pixel);
380}
381
382void draw_gradient(drawable_t* draw, const rect_t* rect, pixel_t start, pixel_t end, direction_t direction,
383 bool shouldAddNoise)
384{
385 if (draw == NULL || rect == NULL)
386 {
387 return;
388 }
389
390 rect_t fitRect = *rect;
391 RECT_FIT(&fitRect, &draw->contentRect);
392
393 int64_t width = RECT_WIDTH(&fitRect);
394 int64_t height = RECT_HEIGHT(&fitRect);
395
396 for (int64_t y = fitRect.top; y < fitRect.bottom; y++)
397 {
398 for (int64_t x = fitRect.left; x < fitRect.right; x++)
399 {
400 int32_t factorNum;
401 int32_t factorDenom;
402
403 switch (direction)
404 {
406 {
407 factorNum = (y - fitRect.top);
408 factorDenom = height;
409 }
410 break;
412 {
413 factorNum = (x - fitRect.left);
414 factorDenom = width;
415 }
416 break;
418 {
419 factorNum = (x - fitRect.left) + (y - fitRect.top);
420 factorDenom = width + height;
421 }
422 break;
423 default:
424 {
425 factorNum = 0;
426 factorDenom = 1;
427 }
428 break;
429 }
430
431 int32_t deltaRed = PIXEL_RED(end) - PIXEL_RED(start);
432 int32_t deltaGreen = PIXEL_GREEN(end) - PIXEL_GREEN(start);
433 int32_t deltaBlue = PIXEL_BLUE(end) - PIXEL_BLUE(start);
434
435 int32_t red = PIXEL_RED(start) + ((factorNum * deltaRed) / factorDenom);
436 int32_t green = PIXEL_GREEN(start) + ((factorNum * deltaGreen) / factorDenom);
437 int32_t blue = PIXEL_BLUE(start) + ((factorNum * deltaBlue) / factorDenom);
438
439 if (shouldAddNoise)
440 {
441 int32_t noiseRed = (rand() % 5) - 2;
442 int32_t noiseGreen = (rand() % 5) - 2;
443 int32_t noiseBlue = (rand() % 5) - 2;
444
445 red += noiseRed;
446 green += noiseGreen;
447 blue += noiseBlue;
448
449 red = CLAMP(0, 255, red);
450 green = CLAMP(0, 255, green);
451 blue = CLAMP(0, 255, blue);
452 }
453
454 pixel_t pixel = PIXEL_ARGB(255, red, green, blue);
455 draw->buffer[x + y * draw->stride] = pixel;
456 }
457 }
458
459 draw_invalidate(draw, &fitRect);
460}
461
462void draw_transfer(drawable_t* dest, drawable_t* src, const rect_t* destRect, const point_t* srcPoint)
463{
464 if (dest == NULL || src == NULL || destRect == NULL || srcPoint == NULL)
465 {
466 return;
467 }
468
469 int64_t width = RECT_WIDTH(destRect);
470 int64_t height = RECT_HEIGHT(destRect);
471
472 if (width <= 0 || height <= 0)
473 {
474 return;
475 }
476 if (srcPoint->x < 0 || srcPoint->y < 0 || srcPoint->x + width > RECT_WIDTH(&src->contentRect) ||
477 srcPoint->y + height > RECT_HEIGHT(&src->contentRect))
478 {
479 return;
480 }
481 if (destRect->left < 0 || destRect->top < 0 || destRect->left + width > RECT_WIDTH(&dest->contentRect) ||
482 destRect->top + height > RECT_HEIGHT(&dest->contentRect))
483 {
484 return;
485 }
486
487 if (dest == src)
488 {
489 for (int64_t y = 0; y < height; y++)
490 {
491 memmove(&dest->buffer[destRect->left + (y + destRect->top) * dest->stride],
492 &src->buffer[srcPoint->x + (y + srcPoint->y) * src->stride], width * sizeof(pixel_t));
493 }
494 }
495 else
496 {
497 for (int64_t y = 0; y < height; y++)
498 {
499 memcpy(&dest->buffer[destRect->left + (y + destRect->top) * dest->stride],
500 &src->buffer[srcPoint->x + (y + srcPoint->y) * src->stride], width * sizeof(pixel_t));
501 }
502 }
503
504 draw_invalidate(dest, destRect);
505}
506
507void draw_transfer_blend(drawable_t* dest, drawable_t* src, const rect_t* destRect, const point_t* srcPoint)
508{
509 if (dest == NULL || src == NULL || destRect == NULL || srcPoint == NULL)
510 {
511 return;
512 }
513
514 int64_t width = RECT_WIDTH(destRect);
515 int64_t height = RECT_HEIGHT(destRect);
516
517 if (width <= 0 || height <= 0)
518 {
519 return;
520 }
521 if (srcPoint->x < 0 || srcPoint->y < 0 || srcPoint->x + width > RECT_WIDTH(&src->contentRect) ||
522 srcPoint->y + height > RECT_HEIGHT(&src->contentRect))
523 {
524 return;
525 }
526 if (destRect->left < 0 || destRect->top < 0 || destRect->left + width > RECT_WIDTH(&dest->contentRect) ||
527 destRect->top + height > RECT_HEIGHT(&dest->contentRect))
528 {
529 return;
530 }
531
532 for (int64_t y = 0; y < height; y++)
533 {
534 for (int64_t x = 0; x < width; x++)
535 {
536 pixel_t srcPixel = src->buffer[(srcPoint->x + x) + (srcPoint->y + y) * src->stride];
537 pixel_t* destPixel = &dest->buffer[(destRect->left + x) + (destRect->top + y) * dest->stride];
538 PIXEL_BLEND(destPixel, &srcPixel);
539 }
540 }
541
542 draw_invalidate(dest, destRect);
543}
544
545void draw_image(drawable_t* draw, image_t* image, const rect_t* destRect, const point_t* srcPoint)
546{
547 draw_transfer(draw, image_draw(image), destRect, srcPoint);
548}
549
550void draw_image_blend(drawable_t* draw, image_t* image, const rect_t* destRect, const point_t* srcPoint)
551{
552 draw_transfer_blend(draw, image_draw(image), destRect, srcPoint);
553}
554
555static void draw_grf_char(drawable_t* draw, const font_t* font, const point_t* point, uint8_t chr, pixel_t pixel)
556{
557 uint32_t offset = font->grf.glyphOffsets[chr];
558 if (offset == GRF_NONE)
559 {
560 // TODO: Implement some sort of error char, empty box?
561 return;
562 }
563 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
564
565 int32_t baselineY = point->y + font->grf.ascender;
566
567 for (uint16_t y = 0; y < glyph->height; y++)
568 {
569 for (uint16_t x = 0; x < glyph->width; x++)
570 {
571 uint8_t gray = glyph->buffer[y * glyph->width + x];
572
573 if (gray > 0)
574 {
575 int32_t targetX = point->x + glyph->bearingX + x;
576 int32_t targetY = baselineY - glyph->bearingY + y;
577
578 if (targetX < 0 || targetY < 0 || targetX >= RECT_WIDTH(&draw->contentRect) ||
579 targetY >= RECT_HEIGHT(&draw->contentRect))
580 {
581 continue;
582 }
583
584 pixel_t output = PIXEL_ARGB(gray, PIXEL_RED(pixel), PIXEL_GREEN(pixel), PIXEL_BLUE(pixel));
585 PIXEL_BLEND(&draw->buffer[targetX + targetY * draw->stride], &output);
586 }
587 }
588 }
589}
590
591void draw_string(drawable_t* draw, const font_t* font, const point_t* point, pixel_t pixel, const char* string,
592 uint64_t length)
593{
594 if (draw == NULL || string == NULL || point == NULL || length == 0)
595 {
596 return;
597 }
598
599 if (font == NULL)
600 {
601 font = font_default(draw->disp);
602 }
603
604 uint64_t width = font_width(font, string, length);
605 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
606 rect_t textArea = RECT_INIT_DIM(point->x, point->y, width, visualTextHeight);
607
608 point_t pos = *point;
609 for (uint64_t i = 0; i < length; i++)
610 {
611 uint32_t offset = font->grf.glyphOffsets[(uint8_t)string[i]];
612 if (offset == GRF_NONE)
613 {
614 // TODO: Implement some sort of error char, empty box?
615 continue;
616 }
617 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
618
619 draw_grf_char(draw, font, &pos, string[i], pixel);
620 pos.x += glyph->advanceX;
621 if (i != length - 1)
622 {
623 pos.x += font_kerning_offset(font, string[i], string[i + 1]);
624 }
625 }
626
627 draw_invalidate(draw, &textArea);
628}
629
630static void draw_calculate_aligned_text_pos(const rect_t* rect, const font_t* font, const char* string, uint64_t length,
631 align_t xAlign, align_t yAlign, point_t* aligned)
632{
633 int64_t width = font_width(font, string, length);
634 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
635
636 switch (xAlign)
637 {
638 case ALIGN_MIN:
639 aligned->x = rect->left;
640 break;
641 case ALIGN_CENTER:
642 aligned->x = MAX(rect->left + (RECT_WIDTH(rect) / 2) - (width / 2), rect->left);
643 break;
644 case ALIGN_MAX:
645 aligned->x = MAX(rect->left + RECT_WIDTH(rect) - width, rect->left);
646 break;
647 default:
648 aligned->x = rect->left;
649 break;
650 }
651
652 switch (yAlign)
653 {
654 case ALIGN_MIN:
655 aligned->y = rect->top;
656 break;
657 case ALIGN_CENTER:
658 aligned->y = rect->top + (RECT_HEIGHT(rect) / 2) - (visualTextHeight / 2);
659 break;
660 case ALIGN_MAX:
661 aligned->y = rect->top + RECT_HEIGHT(rect) - visualTextHeight;
662 break;
663 default:
664 aligned->y = rect->top;
665 break;
666 }
667}
668
669void draw_text(drawable_t* draw, const rect_t* rect, const font_t* font, align_t xAlign, align_t yAlign, pixel_t pixel,
670 const char* text)
671{
672 if (draw == NULL || rect == NULL || text == NULL || *text == '\0')
673 {
674 return;
675 }
676
677 if (font == NULL)
678 {
679 font = font_default(draw->disp);
680 }
681
682 uint64_t maxWidth = RECT_WIDTH(rect);
683 uint64_t originalTextLen = strlen(text);
684 uint64_t textWidth = font_width(font, text, originalTextLen);
685
686 if (textWidth <= maxWidth)
687 {
688 point_t startPoint;
689 draw_calculate_aligned_text_pos(rect, font, text, originalTextLen, xAlign, yAlign, &startPoint);
690 draw_string(draw, font, &startPoint, pixel, text, originalTextLen);
691 }
692 else
693 {
694 const char* ellipsis = "...";
695 uint64_t ellipsisWidth = font_width(font, ellipsis, 3);
696
697 const char* drawText = text;
698 uint64_t drawTextLen = 0;
699 const char* drawEllipsis = NULL;
700
701 if (ellipsisWidth <= maxWidth)
702 {
703 uint64_t currentContentWidth = 0;
704 uint64_t fittedOriginalLen = 0;
705
706 for (uint64_t i = 0; i < originalTextLen; ++i)
707 {
708 uint64_t charWidth = font_width(font, text + i, 1);
709 if (i < originalTextLen - 1)
710 {
711 charWidth += font_kerning_offset(font, text[i], text[i + 1]);
712 }
713
714 if (currentContentWidth + charWidth + ellipsisWidth <= maxWidth)
715 {
716 currentContentWidth += charWidth;
717 fittedOriginalLen++;
718 }
719 else
720 {
721 break;
722 }
723 }
724
725 drawText = text;
726 drawTextLen = fittedOriginalLen;
727 drawEllipsis = ellipsis;
728 }
729 else
730 {
731 uint64_t currentContentWidth = 0;
732 uint64_t fittedEllipsisLen = 0;
733
734 for (uint64_t i = 0; i < 3; ++i)
735 {
736 uint64_t charWidth = font_width(font, ellipsis + i, 1);
737 if (i < 2)
738 {
739 charWidth += font_kerning_offset(font, ellipsis[i], ellipsis[i + 1]);
740 }
741
742 if (currentContentWidth + charWidth <= maxWidth)
743 {
744 currentContentWidth += charWidth;
745 fittedEllipsisLen++;
746 }
747 else
748 {
749 break;
750 }
751 }
752
753 drawText = ellipsis;
754 drawTextLen = fittedEllipsisLen;
755 drawEllipsis = NULL;
756 }
757
758 int64_t totalWidth;
759 if (drawEllipsis != NULL)
760 {
761 totalWidth = font_width(font, drawText, drawTextLen) + font_width(font, drawEllipsis, 3);
762 }
763 else
764 {
765 totalWidth = font_width(font, drawText, drawTextLen);
766 }
767
768 int32_t visualTextHeight = font->grf.ascender - font->grf.descender;
769 point_t startPoint;
770
771 switch (xAlign)
772 {
773 case ALIGN_MIN:
774 startPoint.x = rect->left;
775 break;
776 case ALIGN_CENTER:
777 startPoint.x = MAX(rect->left + (RECT_WIDTH(rect) / 2) - (totalWidth / 2), rect->left);
778 break;
779 case ALIGN_MAX:
780 startPoint.x = MAX(rect->left + RECT_WIDTH(rect) - totalWidth, rect->left);
781 break;
782 default:
783 startPoint.x = rect->left;
784 break;
785 }
786
787 switch (yAlign)
788 {
789 case ALIGN_MIN:
790 startPoint.y = rect->top;
791 break;
792 case ALIGN_CENTER:
793 startPoint.y = rect->top + (RECT_HEIGHT(rect) / 2) - (visualTextHeight / 2);
794 break;
795 case ALIGN_MAX:
796 startPoint.y = rect->top + RECT_HEIGHT(rect) - visualTextHeight;
797 break;
798 default:
799 startPoint.y = rect->top;
800 break;
801 }
802
803 draw_string(draw, font, &startPoint, pixel, drawText, drawTextLen);
804
805 if (drawEllipsis != NULL)
806 {
807 point_t ellipsisStartPoint = {.x = startPoint.x + font_width(font, drawText, drawTextLen),
808 .y = startPoint.y};
809 draw_string(draw, font, &ellipsisStartPoint, pixel, drawEllipsis, 3);
810 }
811 }
812}
813
814void draw_text_multiline(drawable_t* draw, const rect_t* rect, const font_t* font, align_t xAlign, align_t yAlign,
815 pixel_t pixel, const char* text)
816{
817 if (draw == NULL || rect == NULL || text == NULL || *text == '\0')
818 {
819 return;
820 }
821
822 if (font == NULL)
823 {
824 font = font_default(draw->disp);
825 }
826
827 rect_t fitRect = *rect;
828 RECT_FIT(&fitRect, &draw->contentRect);
829
830 int32_t lineHeight = font->grf.ascender - font->grf.descender;
831 int32_t maxWidth = RECT_WIDTH(&fitRect);
832
833 uint64_t lineCount = 0;
834 const char* textPtr = text;
835
836 while (*textPtr != '\0')
837 {
838 const char* lineStart = textPtr;
839 const char* lineEnd = textPtr;
840 const char* lastSpace = NULL;
841 int32_t currentWidth = 0;
842
843 while (*textPtr != '\0' && *textPtr != '\n')
844 {
845 if (*textPtr == ' ')
846 {
847 lastSpace = textPtr;
848 }
849
850 uint32_t offset = font->grf.glyphOffsets[(uint8_t)*textPtr];
851 if (offset != GRF_NONE)
852 {
853 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
854 currentWidth += glyph->advanceX;
855 if (*(textPtr + 1) != '\0' && *(textPtr + 1) != '\n')
856 {
857 currentWidth += font_kerning_offset(font, *textPtr, *(textPtr + 1));
858 }
859
860 if (currentWidth > maxWidth && lastSpace != NULL)
861 {
862 lineEnd = lastSpace;
863 break;
864 }
865 }
866
867 textPtr++;
868 }
869
870 if (*textPtr == '\n' || *textPtr == '\0')
871 {
872 lineEnd = textPtr;
873 }
874 else if (lastSpace == NULL)
875 {
876 lineEnd = textPtr;
877 }
878
879 lineCount++;
880
881 if (*textPtr == '\n')
882 {
883 textPtr++;
884 }
885 else if (*textPtr != '\0')
886 {
887 textPtr = lineEnd + 1;
888 }
889 }
890
891 int32_t totalTextHeight = lineCount * lineHeight;
892
893 int32_t startY;
894 switch (yAlign)
895 {
896 case ALIGN_MIN:
897 startY = fitRect.top;
898 break;
899 case ALIGN_CENTER:
900 startY = fitRect.top + (RECT_HEIGHT(&fitRect) / 2) - (totalTextHeight / 2);
901 break;
902 case ALIGN_MAX:
903 startY = fitRect.top + RECT_HEIGHT(&fitRect) - totalTextHeight;
904 break;
905 default:
906 startY = fitRect.top;
907 break;
908 }
909
910 textPtr = text;
911 int32_t currentY = startY;
912
913 while (*textPtr != '\0' && currentY + lineHeight <= fitRect.bottom)
914 {
915 const char* lineStart = textPtr;
916 const char* lineEnd = textPtr;
917 const char* lastSpace = NULL;
918 int32_t currentWidth = 0;
919
920 while (*textPtr != '\0' && *textPtr != '\n')
921 {
922 if (*textPtr == ' ')
923 {
924 lastSpace = textPtr;
925 }
926
927 uint32_t offset = font->grf.glyphOffsets[(uint8_t)*textPtr];
928 if (offset != GRF_NONE)
929 {
930 grf_glyph_t* glyph = (grf_glyph_t*)(&font->grf.buffer[offset]);
931 currentWidth += glyph->advanceX;
932 if (*(textPtr + 1) != '\0' && *(textPtr + 1) != '\n')
933 {
934 currentWidth += font_kerning_offset(font, *textPtr, *(textPtr + 1));
935 }
936
937 if (currentWidth > maxWidth && lastSpace != NULL)
938 {
939 lineEnd = lastSpace;
940 break;
941 }
942 }
943
944 textPtr++;
945 }
946
947 if (*textPtr == '\n' || *textPtr == '\0')
948 {
949 lineEnd = textPtr;
950 }
951 else if (lastSpace == NULL)
952 {
953 lineEnd = textPtr;
954 }
955
956 int64_t lineLength = lineEnd - lineStart;
957 int64_t lineWidth = font_width(font, lineStart, lineLength);
958
959 int32_t lineX;
960 switch (xAlign)
961 {
962 case ALIGN_MIN:
963 lineX = fitRect.left;
964 break;
965 case ALIGN_CENTER:
966 lineX = MAX(fitRect.left + (RECT_WIDTH(&fitRect) / 2) - (lineWidth / 2), fitRect.left);
967 break;
968 case ALIGN_MAX:
969 lineX = MAX(fitRect.left + RECT_WIDTH(&fitRect) - lineWidth, fitRect.left);
970 break;
971 default:
972 lineX = fitRect.left;
973 break;
974 }
975
976 point_t linePos = {.x = lineX, .y = currentY};
977 draw_string(draw, font, &linePos, pixel, lineStart, lineLength);
978
979 currentY += lineHeight;
980
981 if (*textPtr == '\n')
982 {
983 textPtr++;
984 }
985 else if (*textPtr != '\0')
986 {
987 textPtr = lineEnd + 1;
988 }
989 }
990
991 draw_invalidate(draw, &fitRect);
992}
993
994void draw_ridge(drawable_t* draw, const rect_t* rect, uint64_t width, pixel_t foreground, pixel_t background)
995{
996 if (draw == NULL || rect == NULL || width == 0)
997 {
998 return;
999 }
1000
1001 draw_frame(draw, rect, width / 2, background, foreground);
1002
1003 rect_t innerRect = *rect;
1004 RECT_SHRINK(&innerRect, width / 2);
1005 draw_frame(draw, &innerRect, width / 2, foreground, background);
1006}
1007
1008void draw_separator(drawable_t* draw, const rect_t* rect, pixel_t highlight, pixel_t shadow, direction_t direction)
1009{
1010 if (draw == NULL || rect == NULL)
1011 {
1012 return;
1013 }
1014
1015 rect_t fitRect = *rect;
1016 RECT_FIT(&fitRect, &draw->contentRect);
1017
1018 switch (direction)
1019 {
1020 case DIRECTION_VERTICAL:
1021 {
1022 int64_t width = RECT_WIDTH(&fitRect);
1023
1024 rect_t leftRect = {.left = fitRect.left,
1025 .top = fitRect.top,
1026 .right = fitRect.left + width / 2,
1027 .bottom = fitRect.bottom};
1028 rect_t rightRect = {.left = fitRect.left + width / 2,
1029 .top = fitRect.top,
1030 .right = fitRect.right,
1031 .bottom = fitRect.bottom};
1032
1033 draw_rect(draw, &leftRect, highlight);
1034 draw_rect(draw, &rightRect, shadow);
1035 }
1036 break;
1038 {
1039 int64_t height = RECT_HEIGHT(&fitRect);
1040
1041 rect_t topRect = {.left = fitRect.left,
1042 .top = fitRect.top,
1043 .right = fitRect.right,
1044 .bottom = fitRect.top + height / 2};
1045 rect_t bottomRect = {.left = fitRect.left,
1046 .top = fitRect.top + height / 2,
1047 .right = fitRect.right,
1048 .bottom = fitRect.bottom};
1049
1050 draw_rect(draw, &topRect, highlight);
1051 draw_rect(draw, &bottomRect, shadow);
1052 }
1053 break;
1054 default:
1055 {
1056 }
1057 }
1058
1059 draw_invalidate(draw, &fitRect);
1060}
1061
1062void draw_invalidate(drawable_t* draw, const rect_t* rect)
1063{
1064 if (rect == NULL)
1065 {
1066 draw->invalidRect = draw->contentRect;
1067 return;
1068 }
1069
1070 if (RECT_AREA(&draw->invalidRect) == 0)
1071 {
1072 draw->invalidRect = *rect;
1073 }
1074 else
1075 {
1076 RECT_EXPAND_TO_CONTAIN(&draw->invalidRect, rect);
1077 }
1078}
static void draw_grf_char(drawable_t *draw, const font_t *font, const point_t *point, uint8_t chr, pixel_t pixel)
Definition drawable.c:555
static void draw_calculate_aligned_text_pos(const rect_t *rect, const font_t *font, const char *string, uint64_t length, align_t xAlign, align_t yAlign, point_t *aligned)
Definition drawable.c:630
static int edge_compare(const void *a, const void *b)
Definition drawable.c:33
uint64_t font_width(const font_t *font, const char *string, uint64_t length)
Definition font.c:130
int16_t font_kerning_offset(const font_t *font, char firstChar, char secondChar)
Definition font.c:99
font_t * font_default(display_t *disp)
Definition font.c:7
#define GRF_NONE
Definition grf.h:25
void draw_text_multiline(drawable_t *draw, const rect_t *rect, const font_t *font, align_t xAlign, align_t yAlign, pixel_t pixel, const char *text)
Draw multiline text to a drawable.
Definition drawable.c:814
void draw_line(drawable_t *draw, const point_t *start, const point_t *end, pixel_t pixel, uint32_t thickness)
Draw a line between two points.
Definition drawable.c:179
align_t
Alignment type.
Definition drawable.h:48
void draw_transfer_blend(drawable_t *dest, drawable_t *src, const rect_t *destRect, const point_t *srcPoint)
Transfer pixels from one drawable to another with alpha blending.
Definition drawable.c:507
void draw_polygon(drawable_t *draw, const point_t *points, uint64_t pointCount, pixel_t pixel)
Draw a filled polygon.
Definition drawable.c:48
void draw_gradient(drawable_t *draw, const rect_t *rect, pixel_t start, pixel_t end, direction_t direction, bool shouldAddNoise)
Draw a gradient filled rectangle.
Definition drawable.c:382
void draw_string(drawable_t *draw, const font_t *font, const point_t *point, pixel_t pixel, const char *string, uint64_t length)
Draw a string.
Definition drawable.c:591
void draw_image_blend(drawable_t *draw, image_t *image, const rect_t *destRect, const point_t *srcPoint)
Draw an image with alpha blending.
Definition drawable.c:550
void draw_rect(drawable_t *draw, const rect_t *rect, pixel_t pixel)
Draw a filled rectangle.
Definition drawable.c:7
void draw_ridge(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t foreground, pixel_t background)
Draw a ridge effect.
Definition drawable.c:994
void draw_image(drawable_t *draw, image_t *image, const rect_t *destRect, const point_t *srcPoint)
Draw an image,.
Definition drawable.c:545
void draw_separator(drawable_t *draw, const rect_t *rect, pixel_t highlight, pixel_t shadow, direction_t direction)
Draw a separator line.
Definition drawable.c:1008
void draw_transfer(drawable_t *dest, drawable_t *src, const rect_t *destRect, const point_t *srcPoint)
Transfer pixels from one drawable to another.
Definition drawable.c:462
void draw_text(drawable_t *draw, const rect_t *rect, const font_t *font, align_t xAlign, align_t yAlign, pixel_t pixel, const char *text)
Draw text to a drawable.
Definition drawable.c:669
void draw_dashed_outline(drawable_t *draw, const rect_t *rect, pixel_t pixel, uint32_t length, int32_t width)
Draw a dashed outline just inside the given rectangle.
Definition drawable.c:259
direction_t
Direction type.
Definition drawable.h:59
void draw_bezel(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t pixel)
Draw a filled border bezel just inside the given rectangle.
Definition drawable.c:339
void draw_invalidate(drawable_t *draw, const rect_t *rect)
Invalidate a rectangle in the drawable.
Definition drawable.c:1062
void draw_frame(drawable_t *draw, const rect_t *rect, uint64_t width, pixel_t foreground, pixel_t background)
Draw a skeuomorphic frame.
Definition drawable.c:204
@ ALIGN_MIN
Definition drawable.h:51
@ ALIGN_CENTER
Definition drawable.h:49
@ ALIGN_MAX
Definition drawable.h:50
@ DIRECTION_DIAGONAL
Definition drawable.h:62
@ DIRECTION_HORIZONTAL
Definition drawable.h:61
@ DIRECTION_VERTICAL
Definition drawable.h:60
bool polygon_contains(double px, double py, const point_t *points, uint64_t pointCount)
Check if a point is inside a polygon.
Definition polygon.c:27
#define CLAMP(x, low, high)
Definition math.h:17
#define MIN(x, y)
Definition math.h:16
#define MAX(x, y)
Definition math.h:15
#define NULL
Pointer error value.
Definition NULL.h:23
drawable_t * image_draw(image_t *image)
Definition image.c:84
double atan2(double y, double x)
Definition atan2.c:4
double cos(double x)
Definition cos.c:6
#define M_PI_2
Definition math.h:12
double sin(double x)
Definition sin.c:6
#define PIXEL_BLEND(dest, src)
Definition pixel.h:20
#define PIXEL_GREEN(pixel)
Definition pixel.h:15
#define PIXEL_RED(pixel)
Definition pixel.h:14
uint32_t pixel_t
Definition pixel.h:11
#define PIXEL_BLUE(pixel)
Definition pixel.h:16
#define PIXEL_ARGB(a, r, g, b)
Definition pixel.h:18
#define PIXEL_ALPHA(pixel)
Definition pixel.h:13
static image_t * image
Definition main.c:5
int64_t x
Definition main.c:152
int64_t y
Definition main.c:153
static void start()
Definition main.c:542
#define RECT_SHRINK(rect, margin)
Definition rect.h:81
#define RECT_FIT(rect, parent)
Definition rect.h:73
#define RECT_AREA(rect)
Definition rect.h:40
#define RECT_INIT_DIM(x, y, width, height)
Definition rect.h:32
#define RECT_HEIGHT(rect)
Definition rect.h:39
#define RECT_WIDTH(rect)
Definition rect.h:38
#define RECT_EXPAND_TO_CONTAIN(rect, other)
Definition rect.h:44
__UINT32_TYPE__ uint32_t
Definition stdint.h:15
__INT32_TYPE__ int32_t
Definition stdint.h:14
__UINT64_TYPE__ uint64_t
Definition stdint.h:17
__UINT8_TYPE__ uint8_t
Definition stdint.h:11
__UINT16_TYPE__ uint16_t
Definition stdint.h:13
#define INT64_MIN
Definition stdint.h:72
__INT64_TYPE__ int64_t
Definition stdint.h:16
#define INT64_MAX
Definition stdint.h:73
_PUBLIC int rand(void)
Definition rand.c:5
_PUBLIC void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *))
Definition qsort.c:47
_PUBLIC void * memmove(void *_RESTRICT s1, const void *_RESTRICT s2, size_t n)
_PUBLIC void * memset32(void *s, __UINT32_TYPE__ c, size_t n)
Definition memset32.c:4
_PUBLIC void * memcpy(void *_RESTRICT s1, const void *_RESTRICT s2, size_t n)
Definition memcpy.c:4
_PUBLIC size_t strlen(const char *s)
Definition strlen.c:3
Drawable structure.
Definition drawable.h:35
pixel_t * buffer
Definition drawable.h:38
uint32_t stride
Definition drawable.h:37
rect_t contentRect
Definition drawable.h:39
display_t * disp
Definition drawable.h:36
rect_t invalidRect
Definition drawable.h:40
double invSlope
Definition drawable.c:30
double x
Definition drawable.c:29
int64_t yMax
Definition drawable.c:28
int64_t yMin
Definition drawable.c:27
grf_t grf
Definition internal.h:19
uint16_t height
Definition grf.h:49
uint8_t buffer[]
Definition grf.h:50
int16_t bearingY
Definition grf.h:45
uint16_t width
Definition grf.h:48
int16_t advanceX
Definition grf.h:46
int16_t bearingX
Definition grf.h:44
int16_t ascender
Definition grf.h:31
uint8_t buffer[]
Definition grf.h:38
uint32_t glyphOffsets[256]
Definition grf.h:34
int16_t descender
Definition grf.h:32
int64_t y
Definition point.h:14
int64_t x
Definition point.h:13
Definition rect.h:13
int32_t bottom
Definition rect.h:17
int32_t top
Definition rect.h:15
int32_t right
Definition rect.h:16
int32_t left
Definition rect.h:14