summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/gmdocument.c98
-rw-r--r--src/gmdocument.h27
-rw-r--r--src/ui/color.h6
-rw-r--r--src/ui/documentwidget.c23
-rw-r--r--src/ui/documentwidget.h10
-rw-r--r--src/ui/inputwidget.c2
-rw-r--r--src/ui/labelwidget.c2
-rw-r--r--src/ui/paint.c2
-rw-r--r--src/ui/paint.h2
-rw-r--r--src/ui/sidebarwidget.c227
-rw-r--r--src/ui/sidebarwidget.h3
-rw-r--r--src/ui/text.h6
12 files changed, 316 insertions, 92 deletions
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 76b5ef22..25427391 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -86,6 +86,7 @@ struct Impl_GmDocument {
86 iArray layout; /* contents of source, laid out in document space */ 86 iArray layout; /* contents of source, laid out in document space */
87 iPtrArray links; 87 iPtrArray links;
88 iString title; /* the first top-level title */ 88 iString title; /* the first top-level title */
89 iArray headings;
89 iPtrArray images; /* persistent across layouts, references links by ID */ 90 iPtrArray images; /* persistent across layouts, references links by ID */
90 uint32_t themeSeed; 91 uint32_t themeSeed;
91 iChar siteIcon; 92 iChar siteIcon;
@@ -98,9 +99,9 @@ enum iGmLineType {
98 bullet_GmLineType, 99 bullet_GmLineType,
99 preformatted_GmLineType, 100 preformatted_GmLineType,
100 quote_GmLineType, 101 quote_GmLineType,
101 header1_GmLineType, 102 heading1_GmLineType,
102 header2_GmLineType, 103 heading2_GmLineType,
103 header3_GmLineType, 104 heading3_GmLineType,
104 link_GmLineType, 105 link_GmLineType,
105 max_GmLineType, 106 max_GmLineType,
106}; 107};
@@ -116,13 +117,13 @@ static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangec
116 return link_GmLineType; 117 return link_GmLineType;
117 } 118 }
118 if (startsWith_Rangecc(line, "###")) { 119 if (startsWith_Rangecc(line, "###")) {
119 return header3_GmLineType; 120 return heading3_GmLineType;
120 } 121 }
121 if (startsWith_Rangecc(line, "##")) { 122 if (startsWith_Rangecc(line, "##")) {
122 return header2_GmLineType; 123 return heading2_GmLineType;
123 } 124 }
124 if (startsWith_Rangecc(line, "#")) { 125 if (startsWith_Rangecc(line, "#")) {
125 return header1_GmLineType; 126 return heading1_GmLineType;
126 } 127 }
127 if (startsWith_Rangecc(line, "```")) { 128 if (startsWith_Rangecc(line, "```")) {
128 return preformatted_GmLineType; 129 return preformatted_GmLineType;
@@ -260,9 +261,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
260 paragraph_FontId, /* bullet */ 261 paragraph_FontId, /* bullet */
261 preformatted_FontId, 262 preformatted_FontId,
262 quote_FontId, 263 quote_FontId,
263 header1_FontId, 264 heading1_FontId,
264 header2_FontId, 265 heading2_FontId,
265 header3_FontId, 266 heading3_FontId,
266 regular_FontId, 267 regular_FontId,
267 }; 268 };
268 static const int colors[max_GmLineType] = { 269 static const int colors[max_GmLineType] = {
@@ -270,9 +271,9 @@ static void doLayout_GmDocument_(iGmDocument *d) {
270 tmParagraph_ColorId, 271 tmParagraph_ColorId,
271 tmPreformatted_ColorId, 272 tmPreformatted_ColorId,
272 tmQuote_ColorId, 273 tmQuote_ColorId,
273 tmHeader1_ColorId, 274 tmHeading1_ColorId,
274 tmHeader2_ColorId, 275 tmHeading2_ColorId,
275 tmHeader3_ColorId, 276 tmHeading3_ColorId,
276 tmLinkText_ColorId, 277 tmLinkText_ColorId,
277 }; 278 };
278 static const int indents[max_GmLineType] = { 279 static const int indents[max_GmLineType] = {
@@ -291,6 +292,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
291 const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */ 292 const float midRunSkip = 0.1f; /* extra space between wrapped text/quote lines */
292 clear_Array(&d->layout); 293 clear_Array(&d->layout);
293 clearLinks_GmDocument_(d); 294 clearLinks_GmDocument_(d);
295 clear_Array(&d->headings);
294 clear_String(&d->title); 296 clear_String(&d->title);
295 if (d->size.x <= 0 || isEmpty_String(&d->source)) { 297 if (d->size.x <= 0 || isEmpty_String(&d->source)) {
296 return; 298 return;
@@ -345,6 +347,12 @@ static void doLayout_GmDocument_(iGmDocument *d) {
345 } 347 }
346 trimLine_Rangecc_(&line, type); 348 trimLine_Rangecc_(&line, type);
347 run.font = fonts[type]; 349 run.font = fonts[type];
350 /* Remember headings for the document outline. */
351 if (type == heading1_GmLineType || type == heading2_GmLineType || type == heading3_GmLineType) {
352 pushBack_Array(
353 &d->headings,
354 &(iGmHeading){ .text = line, .level = type - heading1_GmLineType });
355 }
348 } 356 }
349 else { 357 else {
350 /* Preformatted line. */ 358 /* Preformatted line. */
@@ -363,7 +371,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
363 addSiteBanner = iFalse; 371 addSiteBanner = iFalse;
364 const iRangecc bannerText = urlHost_String(&d->url); 372 const iRangecc bannerText = urlHost_String(&d->url);
365 if (!isEmpty_Range(&bannerText)) { 373 if (!isEmpty_Range(&bannerText)) {
366 iGmRun banner = { .flags = siteBanner_GmRunFlag }; 374 iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag };
367 banner.bounds = zero_Rect(); 375 banner.bounds = zero_Rect();
368 banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); 376 banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2);
369 banner.font = banner_FontId; 377 banner.font = banner_FontId;
@@ -406,33 +414,37 @@ static void doLayout_GmDocument_(iGmDocument *d) {
406 } 414 }
407 } 415 }
408 /* Save the document title. */ 416 /* Save the document title. */
409 if (type == header1_GmLineType && isEmpty_String(&d->title)) { 417 if (type == heading1_GmLineType && isEmpty_String(&d->title)) {
410 setRange_String(&d->title, line); 418 setRange_String(&d->title, line);
411 } 419 }
412 /* List bullet. */ 420 /* List bullet. */
413 run.color = colors[type]; 421 run.color = colors[type];
414 if (type == bullet_GmLineType) { 422 if (type == bullet_GmLineType) {
415 run.visBounds.pos = addX_I2(pos, indent * gap_Text); 423 iGmRun bulRun = run;
416 run.visBounds.size = advance_Text(run.font, bullet); 424 bulRun.visBounds.pos = addX_I2(pos, indent * gap_Text);
417 run.visBounds.pos.x -= 4 * gap_Text - width_Rect(run.visBounds) / 2; 425 bulRun.visBounds.size = advance_Text(run.font, bullet);
418 run.bounds = zero_Rect(); /* just visual */ 426 bulRun.visBounds.pos.x -= 4 * gap_Text - width_Rect(run.visBounds) / 2;
419 run.text = range_CStr(bullet); 427 bulRun.bounds = zero_Rect(); /* just visual */
420 pushBack_Array(&d->layout, &run); 428 bulRun.text = range_CStr(bullet);
429 bulRun.flags |= decoration_GmRunFlag;
430 pushBack_Array(&d->layout, &bulRun);
421 } 431 }
422 /* Link icon. */ 432 /* Link icon. */
423 if (type == link_GmLineType) { 433 if (type == link_GmLineType) {
424 run.visBounds.pos = pos; 434 iGmRun icon = run;
425 run.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.font)); 435 icon.visBounds.pos = pos;
426 run.bounds = zero_Rect(); /* just visual */ 436 icon.visBounds.size = init_I2(indent * gap_Text, lineHeight_Text(run.font));
437 icon.bounds = zero_Rect(); /* just visual */
427 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1); 438 const iGmLink *link = constAt_PtrArray(&d->links, run.linkId - 1);
428 run.text = range_CStr(link->flags & file_GmLinkFlag 439 icon.text = range_CStr(link->flags & file_GmLinkFlag
429 ? folder 440 ? folder
430 : link->flags & remote_GmLinkFlag ? globe : arrow); 441 : link->flags & remote_GmLinkFlag ? globe : arrow);
431 if (link->flags & remote_GmLinkFlag) { 442 if (link->flags & remote_GmLinkFlag) {
432 run.visBounds.pos.x -= gap_Text / 2; 443 icon.visBounds.pos.x -= gap_Text / 2;
433 } 444 }
434 run.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart); 445 icon.color = linkColor_GmDocument(d, run.linkId, icon_GmLinkPart);
435 pushBack_Array(&d->layout, &run); 446 icon.flags |= decoration_GmRunFlag;
447 pushBack_Array(&d->layout, &icon);
436 } 448 }
437 run.color = colors[type]; 449 run.color = colors[type];
438 if (d->format == plainText_GmDocumentFormat) { 450 if (d->format == plainText_GmDocumentFormat) {
@@ -446,7 +458,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
446 bigCount = 15; /* max lines -- what if the whole document is one paragraph? */ 458 bigCount = 15; /* max lines -- what if the whole document is one paragraph? */
447 isFirstText = iFalse; 459 isFirstText = iFalse;
448 } 460 }
449 else if (type != header1_GmLineType) { 461 else if (type != heading1_GmLineType) {
450 isFirstText = iFalse; 462 isFirstText = iFalse;
451 } 463 }
452 iRangecc runLine = line; 464 iRangecc runLine = line;
@@ -531,6 +543,7 @@ void init_GmDocument(iGmDocument *d) {
531 init_Array(&d->layout, sizeof(iGmRun)); 543 init_Array(&d->layout, sizeof(iGmRun));
532 init_PtrArray(&d->links); 544 init_PtrArray(&d->links);
533 init_String(&d->title); 545 init_String(&d->title);
546 init_Array(&d->headings, sizeof(iGmHeading));
534 init_PtrArray(&d->images); 547 init_PtrArray(&d->images);
535 setThemeSeed_GmDocument(d, NULL); 548 setThemeSeed_GmDocument(d, NULL);
536} 549}
@@ -539,6 +552,7 @@ void deinit_GmDocument(iGmDocument *d) {
539 deinit_String(&d->title); 552 deinit_String(&d->title);
540 clearLinks_GmDocument_(d); 553 clearLinks_GmDocument_(d);
541 deinit_PtrArray(&d->links); 554 deinit_PtrArray(&d->links);
555 deinit_Array(&d->headings);
542 deinit_Array(&d->layout); 556 deinit_Array(&d->layout);
543 deinit_String(&d->localHost); 557 deinit_String(&d->localHost);
544 deinit_String(&d->url); 558 deinit_String(&d->url);
@@ -553,6 +567,7 @@ void reset_GmDocument(iGmDocument *d) {
553 clear_PtrArray(&d->images); 567 clear_PtrArray(&d->images);
554 clearLinks_GmDocument_(d); 568 clearLinks_GmDocument_(d);
555 clear_Array(&d->layout); 569 clear_Array(&d->layout);
570 clear_Array(&d->headings);
556 clear_String(&d->url); 571 clear_String(&d->url);
557 clear_String(&d->localHost); 572 clear_String(&d->localHost);
558 d->themeSeed = 0; 573 d->themeSeed = 0;
@@ -576,9 +591,9 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
576 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0, 0.75f)); 591 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0, 0.75f));
577 set_Color(tmQuote_ColorId, get_Color(cyan_ColorId)); 592 set_Color(tmQuote_ColorId, get_Color(cyan_ColorId));
578 set_Color(tmPreformatted_ColorId, get_Color(cyan_ColorId)); 593 set_Color(tmPreformatted_ColorId, get_Color(cyan_ColorId));
579 set_Color(tmHeader1_ColorId, get_Color(white_ColorId)); 594 set_Color(tmHeading1_ColorId, get_Color(white_ColorId));
580 setHsl_Color(tmHeader2_ColorId, addSatLum_HSLColor(base, 0, 0.7f)); 595 setHsl_Color(tmHeading2_ColorId, addSatLum_HSLColor(base, 0, 0.7f));
581 setHsl_Color(tmHeader3_ColorId, addSatLum_HSLColor(base, 0, 0.6f)); 596 setHsl_Color(tmHeading3_ColorId, addSatLum_HSLColor(base, 0, 0.6f));
582 set_Color(tmBannerBackground_ColorId, get_Color(black_ColorId)); 597 set_Color(tmBannerBackground_ColorId, get_Color(black_ColorId));
583 set_Color(tmBannerTitle_ColorId, get_Color(white_ColorId)); 598 set_Color(tmBannerTitle_ColorId, get_Color(white_ColorId));
584 set_Color(tmBannerIcon_ColorId, get_Color(orange_ColorId)); 599 set_Color(tmBannerIcon_ColorId, get_Color(orange_ColorId));
@@ -652,15 +667,15 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
652 const float altHue2 = hues[altHues[primIndex].index[altIndex[1]]]; 667 const float altHue2 = hues[altHues[primIndex].index[altIndex[1]]];
653 iHSLColor altBase = { altHue, base.sat, base.lum, 1 }; 668 iHSLColor altBase = { altHue, base.sat, base.lum, 1 };
654 const float titleLum = 0.2f * ((d->themeSeed >> 17) & 0x7) / 7.0f; 669 const float titleLum = 0.2f * ((d->themeSeed >> 17) & 0x7) / 7.0f;
655 setHsl_Color(tmHeader1_ColorId, setLum_HSLColor(altBase, titleLum + 0.80f)); 670 setHsl_Color(tmHeading1_ColorId, setLum_HSLColor(altBase, titleLum + 0.80f));
656 setHsl_Color(tmHeader2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f)); 671 setHsl_Color(tmHeading2_ColorId, setLum_HSLColor(altBase, titleLum + 0.70f));
657 setHsl_Color(tmHeader3_ColorId, setLum_HSLColor(altBase, titleLum + 0.60f)); 672 setHsl_Color(tmHeading3_ColorId, setLum_HSLColor(altBase, titleLum + 0.60f));
658 673
659 setHsl_Color(tmParagraph_ColorId, addSatLum_HSLColor(base, 0.1f, 0.6f)); 674 setHsl_Color(tmParagraph_ColorId, addSatLum_HSLColor(base, 0.1f, 0.6f));
660 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0.2f, 0.8f)); 675 setHsl_Color(tmFirstParagraph_ColorId, addSatLum_HSLColor(base, 0.2f, 0.8f));
661 setHsl_Color(tmPreformatted_ColorId, (iHSLColor){ altHue2, 1.0f, 0.75f, 1.0f }); 676 setHsl_Color(tmPreformatted_ColorId, (iHSLColor){ altHue2, 1.0f, 0.75f, 1.0f });
662 set_Color(tmQuote_ColorId, get_Color(tmPreformatted_ColorId)); 677 set_Color(tmQuote_ColorId, get_Color(tmPreformatted_ColorId));
663 set_Color(tmInlineContentMetadata_ColorId, get_Color(tmHeader3_ColorId)); 678 set_Color(tmInlineContentMetadata_ColorId, get_Color(tmHeading3_ColorId));
664 679
665 /* Adjust colors based on light/dark mode. */ 680 /* Adjust colors based on light/dark mode. */
666 for (int i = tmFirst_ColorId; i < max_ColorId; i++) { 681 for (int i = tmFirst_ColorId; i < max_ColorId; i++) {
@@ -675,7 +690,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
675 color.sat = (color.sat + 1) / 2; 690 color.sat = (color.sat + 1) / 2;
676 color.lum += 0.06f; 691 color.lum += 0.06f;
677 } 692 }
678 else if (i == tmHeader3_ColorId) { 693 else if (i == tmHeading3_ColorId) {
679 color.lum *= 0.75f; 694 color.lum *= 0.75f;
680 } 695 }
681 else if (isLink_ColorId(i)) { 696 else if (isLink_ColorId(i)) {
@@ -888,6 +903,10 @@ iBool hasSiteBanner_GmDocument(const iGmDocument *d) {
888 return (first->flags & siteBanner_GmRunFlag) != 0; 903 return (first->flags & siteBanner_GmRunFlag) != 0;
889} 904}
890 905
906const iArray *headings_GmDocument(const iGmDocument *d) {
907 return &d->headings;
908}
909
891iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { 910iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) {
892 const char * src = constBegin_String(&d->source); 911 const char * src = constBegin_String(&d->source);
893 const size_t startPos = (start ? start - src : 0); 912 const size_t startPos = (start ? start - src : 0);
@@ -934,6 +953,9 @@ const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) {
934const iGmRun *findRunAtLoc_GmDocument(const iGmDocument *d, const char *textCStr) { 953const iGmRun *findRunAtLoc_GmDocument(const iGmDocument *d, const char *textCStr) {
935 iConstForEach(Array, i, &d->layout) { 954 iConstForEach(Array, i, &d->layout) {
936 const iGmRun *run = i.value; 955 const iGmRun *run = i.value;
956 if (run->flags & decoration_GmRunFlag) {
957 continue;
958 }
937 if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) { 959 if (contains_Range(&run->text, textCStr) || run->text.start > textCStr /* went past */) {
938 return run; 960 return run;
939 } 961 }
diff --git a/src/gmdocument.h b/src/gmdocument.h
index 0c6dd998..fd5882bf 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -1,13 +1,16 @@
1#pragma once 1#pragma once
2 2
3#include "gmutil.h" 3#include "gmutil.h"
4
5#include <the_Foundation/array.h>
4#include <the_Foundation/object.h> 6#include <the_Foundation/object.h>
5#include <the_Foundation/rect.h> 7#include <the_Foundation/rect.h>
6#include <the_Foundation/string.h> 8#include <the_Foundation/string.h>
7#include <the_Foundation/time.h> 9#include <the_Foundation/time.h>
8
9#include <SDL_render.h> 10#include <SDL_render.h>
10 11
12iDeclareType(GmImageInfo)
13iDeclareType(GmHeading)
11iDeclareType(GmRun) 14iDeclareType(GmRun)
12 15
13typedef uint16_t iGmLinkId; 16typedef uint16_t iGmLinkId;
@@ -27,18 +30,22 @@ enum iGmLinkFlags {
27 visited_GmLinkFlag = iBit(14), /* in the history */ 30 visited_GmLinkFlag = iBit(14), /* in the history */
28}; 31};
29 32
30iDeclareType(GmImageInfo)
31
32struct Impl_GmImageInfo { 33struct Impl_GmImageInfo {
33 iInt2 size; 34 iInt2 size;
34 size_t numBytes; 35 size_t numBytes;
35 const char *mime; 36 const char *mime;
36}; 37};
37 38
39struct Impl_GmHeading {
40 iRangecc text;
41 int level; /* 0, 1, 2 */
42};
43
38enum iGmRunFlags { 44enum iGmRunFlags {
39 startOfLine_GmRunFlag = iBit(1), 45 decoration_GmRunFlag = iBit(1), /* not part of the source */
40 endOfLine_GmRunFlag = iBit(2), 46 startOfLine_GmRunFlag = iBit(2),
41 siteBanner_GmRunFlag = iBit(3), /* area reserved for the site banner */ 47 endOfLine_GmRunFlag = iBit(3),
48 siteBanner_GmRunFlag = iBit(4), /* area reserved for the site banner */
42}; 49};
43 50
44struct Impl_GmRun { 51struct Impl_GmRun {
@@ -74,9 +81,11 @@ void reset_GmDocument (iGmDocument *); /* free images */
74 81
75typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); 82typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *);
76 83
77void render_GmDocument (const iGmDocument *, iRangei visRangeY, iGmDocumentRenderFunc render, void *); 84void render_GmDocument (const iGmDocument *, iRangei visRangeY,
78iInt2 size_GmDocument (const iGmDocument *); 85 iGmDocumentRenderFunc render, void *);
79iBool hasSiteBanner_GmDocument(const iGmDocument *); 86iInt2 size_GmDocument (const iGmDocument *);
87iBool hasSiteBanner_GmDocument (const iGmDocument *);
88const iArray * headings_GmDocument (const iGmDocument *);
80 89
81iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); 90iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start);
82iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); 91iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before);
diff --git a/src/ui/color.h b/src/ui/color.h
index 8b5ef306..6444ad03 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -27,9 +27,9 @@ enum iColorId {
27 tmFirstParagraph_ColorId, 27 tmFirstParagraph_ColorId,
28 tmQuote_ColorId, 28 tmQuote_ColorId,
29 tmPreformatted_ColorId, 29 tmPreformatted_ColorId,
30 tmHeader1_ColorId, 30 tmHeading1_ColorId,
31 tmHeader2_ColorId, 31 tmHeading2_ColorId,
32 tmHeader3_ColorId, 32 tmHeading3_ColorId,
33 tmBannerBackground_ColorId, 33 tmBannerBackground_ColorId,
34 tmBannerTitle_ColorId, 34 tmBannerTitle_ColorId,
35 tmBannerIcon_ColorId, 35 tmBannerIcon_ColorId,
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index b088fa74..de2abf0d 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -513,6 +513,10 @@ const iString *url_DocumentWidget(const iDocumentWidget *d) {
513 return d->url; 513 return d->url;
514} 514}
515 515
516const iGmDocument *document_DocumentWidget(const iDocumentWidget *d) {
517 return d->doc;
518}
519
516void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { 520void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) {
517 if (cmpStringSc_String(d->url, url, &iCaseInsensitive)) { 521 if (cmpStringSc_String(d->url, url, &iCaseInsensitive)) {
518 set_String(d->url, url); 522 set_String(d->url, url);
@@ -583,8 +587,9 @@ static void scroll_DocumentWidget_(iDocumentWidget *d, int offset) {
583 refresh_Widget(as_Widget(d)); 587 refresh_Widget(as_Widget(d));
584} 588}
585 589
586static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY) { 590static void scrollTo_DocumentWidget_(iDocumentWidget *d, int documentY, iBool centered) {
587 d->scrollY = documentY - documentBounds_DocumentWidget_(d).size.y / 2; 591 d->scrollY = documentY - (centered ? documentBounds_DocumentWidget_(d).size.y / 2 :
592 lineHeight_Text(paragraph_FontId));
588 scroll_DocumentWidget_(d, 0); /* clamp it */ 593 scroll_DocumentWidget_(d, 0); /* clamp it */
589} 594}
590 595
@@ -781,7 +786,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
781 if (midLoc) { 786 if (midLoc) {
782 mid = findRunAtLoc_GmDocument(d->doc, midLoc); 787 mid = findRunAtLoc_GmDocument(d->doc, midLoc);
783 if (mid) { 788 if (mid) {
784 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y); 789 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue);
785 } 790 }
786 } 791 }
787 refresh_Widget(w); 792 refresh_Widget(w);
@@ -923,6 +928,14 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
923 arg_Command(cmd) * height_Rect(documentBounds_DocumentWidget_(d))); 928 arg_Command(cmd) * height_Rect(documentBounds_DocumentWidget_(d)));
924 return iTrue; 929 return iTrue;
925 } 930 }
931 else if (equal_Command(cmd, "document.goto") && document_App() == d) {
932 const char *loc = pointerLabel_Command(cmd, "loc");
933 const iGmRun *run = findRunAtLoc_GmDocument(d->doc, loc);
934 if (run) {
935 scrollTo_DocumentWidget_(d, run->visBounds.pos.y, iFalse);
936 }
937 return iTrue;
938 }
926 else if ((equal_Command(cmd, "find.next") || equal_Command(cmd, "find.prev")) && 939 else if ((equal_Command(cmd, "find.next") || equal_Command(cmd, "find.prev")) &&
927 document_App() == d) { 940 document_App() == d) {
928 const int dir = equal_Command(cmd, "find.next") ? +1 : -1; 941 const int dir = equal_Command(cmd, "find.next") ? +1 : -1;
@@ -943,7 +956,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
943 if (d->foundMark.start) { 956 if (d->foundMark.start) {
944 const iGmRun *found; 957 const iGmRun *found;
945 if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) { 958 if ((found = findRunAtLoc_GmDocument(d->doc, d->foundMark.start)) != NULL) {
946 scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y); 959 scrollTo_DocumentWidget_(d, mid_Rect(found->bounds).y, iTrue);
947 } 960 }
948 } 961 }
949 } 962 }
@@ -1353,7 +1366,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
1353 fillRect_Paint(&ctx.paint, bounds, tmBackground_ColorId); 1366 fillRect_Paint(&ctx.paint, bounds, tmBackground_ColorId);
1354 setClip_Paint(&ctx.paint, bounds); 1367 setClip_Paint(&ctx.paint, bounds);
1355 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx); 1368 render_GmDocument(d->doc, visibleRange_DocumentWidget_(d), drawRun_DrawContext_, &ctx);
1356 clearClip_Paint(&ctx.paint); 1369 unsetClip_Paint(&ctx.paint);
1357 draw_Widget(w); 1370 draw_Widget(w);
1358} 1371}
1359 1372
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h
index 85b9a710..1e63034b 100644
--- a/src/ui/documentwidget.h
+++ b/src/ui/documentwidget.h
@@ -2,16 +2,18 @@
2 2
3#include "widget.h" 3#include "widget.h"
4 4
5iDeclareType(GmDocument)
5iDeclareType(History) 6iDeclareType(History)
6 7
7iDeclareWidgetClass(DocumentWidget) 8iDeclareWidgetClass(DocumentWidget)
8iDeclareObjectConstruction(DocumentWidget) 9iDeclareObjectConstruction(DocumentWidget)
9 10
10iDocumentWidget *duplicate_DocumentWidget (const iDocumentWidget *); 11iDocumentWidget * duplicate_DocumentWidget (const iDocumentWidget *);
11iHistory * history_DocumentWidget (iDocumentWidget *); 12iHistory * history_DocumentWidget (iDocumentWidget *);
12 13
13const iString * url_DocumentWidget (const iDocumentWidget *); 14const iString * url_DocumentWidget (const iDocumentWidget *);
14iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *); 15iBool isRequestOngoing_DocumentWidget (const iDocumentWidget *);
16const iGmDocument * document_DocumentWidget (const iDocumentWidget *);
15 17
16void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); 18void setUrl_DocumentWidget (iDocumentWidget *, const iString *url);
17void setUrlFromCache_DocumentWidget (iDocumentWidget *d, const iString *url, iBool isFromCache); 19void setUrlFromCache_DocumentWidget (iDocumentWidget *d, const iString *url, iBool isFromCache);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index bfb71913..305b8c9b 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -335,7 +335,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
335 white_ColorId, 335 white_ColorId,
336 "%s", 336 "%s",
337 cstr_String(&text)); 337 cstr_String(&text));
338 clearClip_Paint(&p); 338 unsetClip_Paint(&p);
339 /* Cursor blinking. */ 339 /* Cursor blinking. */
340 if (isFocused && (time & 256)) { 340 if (isFocused && (time & 256)) {
341 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor); 341 const iInt2 prefixSize = advanceN_Text(d->font, cstr_String(&text), d->cursor);
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index 0ec1c5f8..0c9269d1 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -218,7 +218,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
218 else { 218 else {
219 drawCentered_Text(d->font, bounds, d->alignVisual, fg, cstr_String(&d->label)); 219 drawCentered_Text(d->font, bounds, d->alignVisual, fg, cstr_String(&d->label));
220 } 220 }
221 clearClip_Paint(&p); 221 unsetClip_Paint(&p);
222} 222}
223 223
224void updateSize_LabelWidget(iLabelWidget *d) { 224void updateSize_LabelWidget(iLabelWidget *d) {
diff --git a/src/ui/paint.c b/src/ui/paint.c
index 0a2e6cd3..85e75f15 100644
--- a/src/ui/paint.c
+++ b/src/ui/paint.c
@@ -18,7 +18,7 @@ void setClip_Paint(iPaint *d, iRect rect) {
18 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); 18 SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect);
19} 19}
20 20
21void clearClip_Paint(iPaint *d) { 21void unsetClip_Paint(iPaint *d) {
22 const SDL_Rect winRect = { 0, 0, d->dst->root->rect.size.x, d->dst->root->rect.size.y }; 22 const SDL_Rect winRect = { 0, 0, d->dst->root->rect.size.x, d->dst->root->rect.size.y };
23 SDL_RenderSetClipRect(renderer_Paint_(d), &winRect); 23 SDL_RenderSetClipRect(renderer_Paint_(d), &winRect);
24} 24}
diff --git a/src/ui/paint.h b/src/ui/paint.h
index 9535b142..90c5f504 100644
--- a/src/ui/paint.h
+++ b/src/ui/paint.h
@@ -14,7 +14,7 @@ struct Impl_Paint {
14void init_Paint (iPaint *); 14void init_Paint (iPaint *);
15 15
16void setClip_Paint (iPaint *, iRect rect); 16void setClip_Paint (iPaint *, iRect rect);
17void clearClip_Paint (iPaint *); 17void unsetClip_Paint (iPaint *);
18 18
19void drawRect_Paint (const iPaint *, iRect rect, int color); 19void drawRect_Paint (const iPaint *, iRect rect, int color);
20void drawRectThickness_Paint (const iPaint *, iRect rect, int thickness, int color); 20void drawRectThickness_Paint (const iPaint *, iRect rect, int thickness, int color);
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 8f1273bf..46b36434 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -1,57 +1,234 @@
1#include "sidebarwidget.h" 1#include "sidebarwidget.h"
2#include "labelwidget.h" 2#include "labelwidget.h"
3#include "scrollwidget.h" 3#include "scrollwidget.h"
4#include "documentwidget.h"
4#include "paint.h" 5#include "paint.h"
5#include "util.h" 6#include "util.h"
7#include "command.h"
8#include "../gmdocument.h"
9#include "app.h"
10
11#include <the_Foundation/array.h>
12
13iDeclareType(SidebarItem)
14
15struct Impl_SidebarItem {
16 int indent;
17 iChar icon;
18 iString label;
19 iString meta;
20 iString url;
21 size_t index;
22};
23
24void init_SidebarItem(iSidebarItem *d) {
25 d->indent = 0;
26 d->icon = 0;
27 d->index = 0;
28 init_String(&d->label);
29 init_String(&d->meta);
30 init_String(&d->url);
31}
32
33void deinit_SidebarItem(iSidebarItem *d) {
34 deinit_String(&d->url);
35 deinit_String(&d->meta);
36 deinit_String(&d->label);
37}
38
39iDefineTypeConstruction(SidebarItem)
40
41/*----------------------------------------------------------------------------------------------*/
6 42
7struct Impl_SidebarWidget { 43struct Impl_SidebarWidget {
8 iWidget widget; 44 iWidget widget;
9 enum iSidebarMode mode; 45 enum iSidebarMode mode;
10 iScrollWidget *scroll; 46 iScrollWidget *scroll;
47 int scrollY;
48 iLabelWidget *modeButtons[max_SidebarMode];
49 int itemHeight;
50 iArray items;
51 size_t hoverItem;
52 iClick click;
11}; 53};
12 54
13iDefineObjectConstruction(SidebarWidget) 55iDefineObjectConstruction(SidebarWidget)
14 56
57static void clearItems_SidebarWidget_(iSidebarWidget *d) {
58 iForEach(Array, i, &d->items) {
59 deinit_SidebarItem(i.value);
60 }
61 clear_Array(&d->items);
62}
63
64static void updateItems_SidebarWidget_(iSidebarWidget *d) {
65 clearItems_SidebarWidget_(d);
66 switch (d->mode) {
67 case documentOutline_SidebarMode: {
68 const iGmDocument *doc = document_DocumentWidget(document_App());
69 iConstForEach(Array, i, headings_GmDocument(doc)) {
70 const iGmHeading *head = i.value;
71 iSidebarItem item;
72 init_SidebarItem(&item);
73 item.index = index_ArrayConstIterator(&i);
74 setRange_String(&item.label, head->text);
75 item.indent = head->level * 4 * gap_UI;
76 pushBack_Array(&d->items, &item);
77 }
78 break;
79 }
80 case bookmarks_SidebarMode:
81 break;
82 case history_SidebarMode:
83 break;
84 default:
85 break;
86 }
87 refresh_Widget(as_Widget(d));
88}
89
90void setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) {
91 if (d->mode == mode) return;
92 d->mode = mode;
93 for (enum iSidebarMode i = 0; i < max_SidebarMode; i++) {
94 setFlags_Widget(as_Widget(d->modeButtons[i]), selected_WidgetFlag, i == d->mode);
95 }
96 const float heights[max_SidebarMode] = { 1.2f, 2, 3, 3 };
97 d->itemHeight = heights[mode] * lineHeight_Text(default_FontId);
98}
99
15void init_SidebarWidget(iSidebarWidget *d) { 100void init_SidebarWidget(iSidebarWidget *d) {
16 iWidget *w = as_Widget(d); 101 iWidget *w = as_Widget(d);
17 init_Widget(w); 102 init_Widget(w);
18 setFlags_Widget(w, resizeChildren_WidgetFlag, iTrue); 103 setBackgroundColor_Widget(w, none_ColorId);
19 d->mode = documentOutline_SidebarMode; 104 setFlags_Widget(w, hover_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue);
20 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget())); 105 d->scrollY = 0;
21 w->rect.size.x = 80 * gap_UI; 106 d->mode = -1;
107 w->rect.size.x = 100 * gap_UI;
108 init_Array(&d->items, sizeof(iSidebarItem));
109 d->hoverItem = iInvalidPos;
110 init_Click(&d->click, d, SDL_BUTTON_LEFT);
22 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue); 111 setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue);
23 iWidget *modeButtons = makeHDiv_Widget(); 112 const char *buttonLabels[max_SidebarMode] = {
24 setFlags_Widget(modeButtons, arrangeWidth_WidgetFlag | arrangeHeight_WidgetFlag, iTrue); 113 "\U0001f5b9 Outline",
25 addChild_Widget(w, iClob(modeButtons)); 114 "\U0001f588 Bookmarks",
26 addChildFlags_Widget( 115 "\U0001f553 History",
27 modeButtons, 116 "\U0001f464 Identities",
28 iClob(new_LabelWidget( 117 };
29 "\U0001f5b9 Outline", 0, 0, format_CStr("sidebar.mode arg:%d", documentOutline_SidebarMode))), frameless_WidgetFlag); 118 for (int i = 0; i < max_SidebarMode; i++) {
30 addChildFlags_Widget( 119 d->modeButtons[i] = addChildFlags_Widget(
31 modeButtons, 120 w,
32 iClob(new_LabelWidget( 121 iClob(new_LabelWidget(buttonLabels[i], 0, 0, format_CStr("sidebar.mode arg:%d", i))),
33 "\U0001f588 Bookmarks", 0, 0, format_CStr("sidebar.mode arg:%d", bookmarks_SidebarMode))), frameless_WidgetFlag); 122 frameless_WidgetFlag);
34 addChildFlags_Widget( 123 }
35 modeButtons, 124 addChild_Widget(w, iClob(d->scroll = new_ScrollWidget()));
36 iClob(new_LabelWidget( 125 setThumb_ScrollWidget(d->scroll, 0, 0);
37 "\U0001f553 History", 0, 0, format_CStr("sidebar.mode arg:%d", history_SidebarMode))), frameless_WidgetFlag); 126 setMode_SidebarWidget(d, documentOutline_SidebarMode);
38 addChildFlags_Widget(
39 modeButtons,
40 iClob(new_LabelWidget(
41 "\U0001f464 Identities", 0, 0, format_CStr("sidebar.mode arg:%d", identities_SidebarMode))), frameless_WidgetFlag);
42} 127}
43 128
44void deinit_SidebarWidget(iSidebarWidget *d) { 129void deinit_SidebarWidget(iSidebarWidget *d) {
45 iUnused(d); 130 clearItems_SidebarWidget_(d);
131 deinit_Array(&d->items);
132}
133
134static iRect contentBounds_SidebarWidget_(const iSidebarWidget *d) {
135 iRect bounds = bounds_Widget(constAs_Widget(d));
136 adjustEdges_Rect(&bounds, as_Widget(d->modeButtons[0])->rect.size.y + gap_UI, 0, 0, 0);
137 return bounds;
138}
139
140static size_t itemIndex_SidebarWidget_(const iSidebarWidget *d, iInt2 pos) {
141 const iRect bounds = contentBounds_SidebarWidget_(d);
142 pos.y -= top_Rect(bounds);
143 if (pos.y < 0) return iInvalidPos;
144 size_t index = pos.y / d->itemHeight;
145 if (index >= size_Array(&d->items)) return iInvalidPos;
146 return index;
147}
148
149static void itemClicked_SidebarWidget_(iSidebarWidget *d, size_t index) {
150 const iSidebarItem *item = constAt_Array(&d->items, index);
151 switch (d->mode) {
152 case documentOutline_SidebarMode: {
153 const iGmDocument *doc = document_DocumentWidget(document_App());
154 const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->index);
155 postCommandf_App("document.goto loc:%p", head->text.start);
156 break;
157 }
158 }
46} 159}
47 160
48static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { 161static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) {
49 iWidget *w = as_Widget(d); 162 iWidget *w = as_Widget(d);
163 /* Handle commands. */
164 if (ev->type == SDL_USEREVENT && ev->user.code == command_UserEventCode) {
165 const char *cmd = command_UserEvent(ev);
166 if (isCommand_Widget(w, ev, "sidebar.mode")) {
167 setMode_SidebarWidget(d, arg_Command(cmd));
168 updateItems_SidebarWidget_(d);
169 return iTrue;
170 }
171 else if (equal_Command(cmd, "tabs.changed") || equal_Command(cmd, "document.changed")) {
172 updateItems_SidebarWidget_(d);
173 }
174 }
175 if (ev->type == SDL_MOUSEMOTION) {
176 const iInt2 mouse = init_I2(ev->motion.x, ev->motion.y);
177 size_t hover = iInvalidPos;
178 if (contains_Widget(w, mouse)) {
179 hover = itemIndex_SidebarWidget_(d, mouse);
180
181 }
182 if (hover != d->hoverItem) {
183 d->hoverItem = hover;
184 refresh_Widget(w);
185 }
186 }
187 switch (processEvent_Click(&d->click, ev)) {
188 case started_ClickResult:
189 refresh_Widget(w);
190 break;
191 case finished_ClickResult:
192 if (contains_Rect(contentBounds_SidebarWidget_(d), pos_Click(&d->click)) &&
193 d->hoverItem != iInvalidSize) {
194 //printf("click:%zu\n", d->hoverItem); fflush(stdout);
195 itemClicked_SidebarWidget_(d, d->hoverItem);
196 }
197 refresh_Widget(w);
198 break;
199 default:
200 break;
201 }
50 return processEvent_Widget(w, ev); 202 return processEvent_Widget(w, ev);
51} 203}
52 204
53static void draw_SidebarWidget_(const iSidebarWidget *d) { 205static void draw_SidebarWidget_(const iSidebarWidget *d) {
54 const iWidget *w = constAs_Widget(d); 206 const iWidget *w = constAs_Widget(d);
207 const iRect bounds = contentBounds_SidebarWidget_(d);
208 const iBool isPressing = d->click.isActive && contains_Rect(bounds, pos_Click(&d->click));
209 iPaint p;
210 init_Paint(&p);
211 /* Draw the items. */ {
212 iInt2 pos = topLeft_Rect(bounds);
213 const int font = default_FontId;
214 iConstForEach(Array, i, &d->items) {
215 const iSidebarItem *item = i.value;
216 const iRect itemRect = { pos, init_I2(width_Rect(bounds), d->itemHeight) };
217 const iBool isHover = (d->hoverItem == index_ArrayConstIterator(&i));
218 if (isHover) {
219 fillRect_Paint(&p, itemRect, isPressing ? orange_ColorId : teal_ColorId);
220 }
221 setClip_Paint(&p, itemRect);
222 const int fg = isHover ? (isPressing ? black_ColorId : white_ColorId) : gray75_ColorId;
223 if (d->mode == documentOutline_SidebarMode) {
224 drawRange_Text(font, init_I2(pos.x + 3 * gap_UI + item->indent,
225 mid_Rect(itemRect).y - lineHeight_Text(font) / 2),
226 fg, range_String(&item->label));
227 }
228 unsetClip_Paint(&p);
229 pos.y += d->itemHeight;
230 }
231 }
55 draw_Widget(w); 232 draw_Widget(w);
56} 233}
57 234
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h
index 3fd956a4..80f22fca 100644
--- a/src/ui/sidebarwidget.h
+++ b/src/ui/sidebarwidget.h
@@ -3,10 +3,11 @@
3#include "widget.h" 3#include "widget.h"
4 4
5enum iSidebarMode { 5enum iSidebarMode {
6 documentOutline_SidebarMode,
6 bookmarks_SidebarMode, 7 bookmarks_SidebarMode,
7 history_SidebarMode, 8 history_SidebarMode,
8 documentOutline_SidebarMode,
9 identities_SidebarMode, 9 identities_SidebarMode,
10 max_SidebarMode
10}; 11};
11 12
12iDeclareWidgetClass(SidebarWidget) 13iDeclareWidgetClass(SidebarWidget)
diff --git a/src/ui/text.h b/src/ui/text.h
index 713ee1e7..864173e0 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -44,9 +44,9 @@ enum iFontId {
44 preformatted_FontId = monospace_FontId, 44 preformatted_FontId = monospace_FontId,
45 preformattedSmall_FontId = monospaceSmall_FontId, 45 preformattedSmall_FontId = monospaceSmall_FontId,
46 quote_FontId = italic_FontId, 46 quote_FontId = italic_FontId,
47 header1_FontId = hugeBold_FontId, 47 heading1_FontId = hugeBold_FontId,
48 header2_FontId = largeBold_FontId, 48 heading2_FontId = largeBold_FontId,
49 header3_FontId = medium_FontId, 49 heading3_FontId = medium_FontId,
50 banner_FontId = largeLight_FontId, 50 banner_FontId = largeLight_FontId,
51}; 51};
52 52