summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/documentwidget.c226
-rw-r--r--src/ui/inputwidget.c5
-rw-r--r--src/ui/inputwidget.h1
-rw-r--r--src/ui/sidebarwidget.c2
-rw-r--r--src/ui/text.c80
-rw-r--r--src/ui/text.h10
-rw-r--r--src/ui/util.c10
-rw-r--r--src/ui/window.c16
8 files changed, 302 insertions, 48 deletions
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 351d1e62..0079465f 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -127,6 +127,18 @@ iDefineTypeConstruction(Model)
127 127
128/*----------------------------------------------------------------------------------------------*/ 128/*----------------------------------------------------------------------------------------------*/
129 129
130iDeclareType(OutlineItem)
131
132struct Impl_OutlineItem {
133 iRangecc text;
134 int font;
135 iRect rect;
136 int seenColor;
137 int sepColor;
138};
139
140/*----------------------------------------------------------------------------------------------*/
141
130static const int smoothSpeed_DocumentWidget_ = 120; /* unit: gap_Text per second */ 142static const int smoothSpeed_DocumentWidget_ = 120; /* unit: gap_Text per second */
131 143
132enum iRequestState { 144enum iRequestState {
@@ -161,6 +173,7 @@ struct Impl_DocumentWidget {
161 const iGmRun * contextLink; 173 const iGmRun * contextLink;
162 iBool noHoverWhileScrolling; 174 iBool noHoverWhileScrolling;
163 iBool showLinkNumbers; 175 iBool showLinkNumbers;
176 const iGmRun * lastVisibleRun;
164 iClick click; 177 iClick click;
165 float initNormScrollY; 178 float initNormScrollY;
166 int scrollY; 179 int scrollY;
@@ -170,6 +183,8 @@ struct Impl_DocumentWidget {
170 int smoothLastOffset; 183 int smoothLastOffset;
171 iBool smoothContinue; 184 iBool smoothContinue;
172 iAnim sideOpacity; 185 iAnim sideOpacity;
186 iAnim outlineOpacity;
187 iArray outline;
173 iWidget * menu; 188 iWidget * menu;
174 iVisBuf * visBuf; 189 iVisBuf * visBuf;
175 iPtrSet * invalidRuns; 190 iPtrSet * invalidRuns;
@@ -207,8 +222,10 @@ void init_DocumentWidget(iDocumentWidget *d) {
207 d->contextLink = NULL; 222 d->contextLink = NULL;
208 d->noHoverWhileScrolling = iFalse; 223 d->noHoverWhileScrolling = iFalse;
209 d->showLinkNumbers = iFalse; 224 d->showLinkNumbers = iFalse;
225 d->lastVisibleRun = NULL;
210 d->visBuf = new_VisBuf(); 226 d->visBuf = new_VisBuf();
211 d->invalidRuns = new_PtrSet(); 227 d->invalidRuns = new_PtrSet();
228 init_Array(&d->outline, sizeof(iOutlineItem));
212 init_Anim(&d->sideOpacity, 0); 229 init_Anim(&d->sideOpacity, 0);
213 init_String(&d->sourceMime); 230 init_String(&d->sourceMime);
214 init_Block(&d->sourceContent, 0); 231 init_Block(&d->sourceContent, 0);
@@ -227,6 +244,7 @@ void init_DocumentWidget(iDocumentWidget *d) {
227void deinit_DocumentWidget(iDocumentWidget *d) { 244void deinit_DocumentWidget(iDocumentWidget *d) {
228 delete_VisBuf(d->visBuf); 245 delete_VisBuf(d->visBuf);
229 delete_PtrSet(d->invalidRuns); 246 delete_PtrSet(d->invalidRuns);
247 deinit_Array(&d->outline);
230 iRelease(d->media); 248 iRelease(d->media);
231 iRelease(d->request); 249 iRelease(d->request);
232 deinit_Block(&d->sourceContent); 250 deinit_Block(&d->sourceContent);
@@ -314,6 +332,7 @@ static iRangei visibleRange_DocumentWidget_(const iDocumentWidget *d) {
314 332
315static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) { 333static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) {
316 iDocumentWidget *d = context; 334 iDocumentWidget *d = context;
335 d->lastVisibleRun = run;
317 if (run->linkId && linkFlags_GmDocument(d->doc, run->linkId) & supportedProtocol_GmLinkFlag) { 336 if (run->linkId && linkFlags_GmDocument(d->doc, run->linkId) & supportedProtocol_GmLinkFlag) {
318 pushBack_PtrArray(&d->visibleLinks, run); 337 pushBack_PtrArray(&d->visibleLinks, run);
319 } 338 }
@@ -379,7 +398,7 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
379 398
380static void animate_DocumentWidget_(void *ticker) { 399static void animate_DocumentWidget_(void *ticker) {
381 iDocumentWidget *d = ticker; 400 iDocumentWidget *d = ticker;
382 if (!isFinished_Anim(&d->sideOpacity)) { 401 if (!isFinished_Anim(&d->sideOpacity) || !isFinished_Anim(&d->outlineOpacity)) {
383 addTicker_App(animate_DocumentWidget_, d); 402 addTicker_App(animate_DocumentWidget_, d);
384 } 403 }
385} 404}
@@ -390,7 +409,20 @@ static void updateSideOpacity_DocumentWidget_(iDocumentWidget *d) {
390 if (banner && bottom_Rect(banner->visBounds) < d->scrollY) { 409 if (banner && bottom_Rect(banner->visBounds) < d->scrollY) {
391 opacity = 1.0f; 410 opacity = 1.0f;
392 } 411 }
393 setValue_Anim(&d->sideOpacity, opacity, opacity < 0.5f ? 166 : 333); 412 setValue_Anim(&d->sideOpacity, opacity, opacity < 0.5f ? 100 : 200);
413 animate_DocumentWidget_(d);
414}
415
416static void updateOutlineOpacity_DocumentWidget_(iDocumentWidget *d) {
417 float opacity = 0.0f;
418 if (isEmpty_Array(&d->outline)) {
419 setValue_Anim(&d->outlineOpacity, 0.0f, 0);
420 return;
421 }
422 if (contains_Widget(constAs_Widget(d->scroll), mouseCoord_Window(get_Window()))) {
423 opacity = 1.0f;
424 }
425 setValue_Anim(&d->outlineOpacity, opacity, opacity > 0.5f? 166 : 333);
394 animate_DocumentWidget_(d); 426 animate_DocumentWidget_(d);
395} 427}
396 428
@@ -506,16 +538,77 @@ static void invalidate_DocumentWidget_(iDocumentWidget *d) {
506 clear_PtrSet(d->invalidRuns); 538 clear_PtrSet(d->invalidRuns);
507} 539}
508 540
541static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */
542static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */
543static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */
544
545static int outlineWidth_DocumentWidget_(const iDocumentWidget *d) {
546 const iWidget *w = constAs_Widget(d);
547 const iRect bounds = bounds_Widget(w);
548 const int docWidth = documentWidth_DocumentWidget_(d);
549 int width =
550 (width_Rect(bounds) - docWidth) / 2 - gap_Text * d->pageMargin - gap_UI * d->pageMargin
551 - 2 * outlinePadding_DocumentWidget_ * gap_UI;
552 if (width < outlineMinWidth_DocumentWdiget_ * gap_UI) {
553 return outlineMinWidth_DocumentWdiget_ * gap_UI;
554 }
555 return iMin(width, outlineMaxWidth_DocumentWidget_ * gap_UI);
556}
557
558static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) {
559 return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc))
560 : range_String(d->titleUser);
561}
562
563static void updateOutline_DocumentWidget_(iDocumentWidget *d) {
564 iWidget *w = as_Widget(d);
565 int outWidth = outlineWidth_DocumentWidget_(d);
566 clear_Array(&d->outline);
567 if (outWidth == 0 || d->state != ready_RequestState) {
568 return;
569 }
570 if (size_GmDocument(d->doc).y < height_Rect(bounds_Widget(w)) * 2) {
571 return; /* Too short */
572 }
573 iInt2 pos = zero_I2();
574// const iRangecc topText = urlHost_String(d->mod.url);
575// iInt2 size = advanceWrapRange_Text(uiContent_FontId, outWidth, topText);
576// pushBack_Array(&d->outline, &(iOutlineItem){ topText, uiContent_FontId, (iRect){ pos, size },
577// tmBannerTitle_ColorId, none_ColorId });
578// pos.y += size.y;
579 iInt2 size;
580 iConstForEach(Array, i, headings_GmDocument(d->doc)) {
581 const iGmHeading *head = i.value;
582 const int indent = head->level * 5 * gap_UI;
583 size = advanceWrapRange_Text(uiLabel_FontId, outWidth - indent, head->text);
584 if (head->level == 0) {
585 pos.y += gap_UI * 1.5f;
586 }
587 pushBack_Array(&d->outline,
588 &(iOutlineItem){ head->text,
589 uiLabel_FontId,
590 (iRect){ addX_I2(pos, indent), size },
591 head->level == 0 ? tmHeading1_ColorId
592 : head->level == 1 ? tmHeading2_ColorId
593 : tmHeading3_ColorId,
594 head->level == 0 ? tmQuoteIcon_ColorId : none_ColorId });
595 pos.y += size.y;
596 }
597}
598
509static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { 599static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) {
510 setUrl_GmDocument(d->doc, d->mod.url); 600 setUrl_GmDocument(d->doc, d->mod.url);
511 setSource_GmDocument( 601 setSource_GmDocument(
512 d->doc, source, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d)); 602 d->doc, source, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d));
513 d->foundMark = iNullRange; 603 d->foundMark = iNullRange;
514 d->selectMark = iNullRange; 604 d->selectMark = iNullRange;
515 d->hoverLink = NULL; 605 d->hoverLink = NULL;
516 d->contextLink = NULL; 606 d->contextLink = NULL;
607 d->lastVisibleRun = NULL;
608 setValue_Anim(&d->outlineOpacity, 0.0f, 0);
517 updateWindowTitle_DocumentWidget_(d); 609 updateWindowTitle_DocumentWidget_(d);
518 updateVisible_DocumentWidget_(d); 610 updateVisible_DocumentWidget_(d);
611 updateOutline_DocumentWidget_(d);
519 invalidate_DocumentWidget_(d); 612 invalidate_DocumentWidget_(d);
520 refresh_Widget(as_Widget(d)); 613 refresh_Widget(as_Widget(d));
521} 614}
@@ -751,6 +844,7 @@ static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) {
751 updateDocument_DocumentWidget_(d, resp); 844 updateDocument_DocumentWidget_(d, resp);
752 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y; 845 d->scrollY = d->initNormScrollY * size_GmDocument(d->doc).y;
753 d->state = ready_RequestState; 846 d->state = ready_RequestState;
847 updateOutline_DocumentWidget_(d);
754 updateVisible_DocumentWidget_(d); 848 updateVisible_DocumentWidget_(d);
755 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 849 postCommandf_App("document.changed doc:%p url:%s", d, cstr_String(d->mod.url));
756 return iTrue; 850 return iTrue;
@@ -1069,6 +1163,7 @@ static void allocVisBuffer_DocumentWidget_(const iDocumentWidget *d) {
1069void updateSize_DocumentWidget(iDocumentWidget *d) { 1163void updateSize_DocumentWidget(iDocumentWidget *d) {
1070 setWidth_GmDocument( 1164 setWidth_GmDocument(
1071 d->doc, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d)); 1165 d->doc, documentWidth_DocumentWidget_(d), forceBreakWidth_DocumentWidget_(d));
1166 updateOutline_DocumentWidget_(d);
1072 updateVisible_DocumentWidget_(d); 1167 updateVisible_DocumentWidget_(d);
1073 invalidate_DocumentWidget_(d); 1168 invalidate_DocumentWidget_(d);
1074} 1169}
@@ -1087,6 +1182,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1087 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue); 1182 scrollTo_DocumentWidget_(d, mid_Rect(mid->bounds).y, iTrue);
1088 } 1183 }
1089 } 1184 }
1185 updateOutline_DocumentWidget_(d);
1090 invalidate_DocumentWidget_(d); 1186 invalidate_DocumentWidget_(d);
1091 dealloc_VisBuf(d->visBuf); 1187 dealloc_VisBuf(d->visBuf);
1092 refresh_Widget(w); 1188 refresh_Widget(w);
@@ -1109,6 +1205,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1109 updateSize_DocumentWidget(d); 1205 updateSize_DocumentWidget(d);
1110 updateFetchProgress_DocumentWidget_(d); 1206 updateFetchProgress_DocumentWidget_(d);
1111 } 1207 }
1208 updateOutlineOpacity_DocumentWidget_(d);
1112 updateWindowTitle_DocumentWidget_(d); 1209 updateWindowTitle_DocumentWidget_(d);
1113 allocVisBuffer_DocumentWidget_(d); 1210 allocVisBuffer_DocumentWidget_(d);
1114 return iFalse; 1211 return iFalse;
@@ -1213,6 +1310,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1213 } 1310 }
1214 iReleasePtr(&d->request); 1311 iReleasePtr(&d->request);
1215 updateVisible_DocumentWidget_(d); 1312 updateVisible_DocumentWidget_(d);
1313 updateOutline_DocumentWidget_(d);
1216 postCommandf_App("document.changed url:%s", cstr_String(d->mod.url)); 1314 postCommandf_App("document.changed url:%s", cstr_String(d->mod.url));
1217 return iFalse; 1315 return iFalse;
1218 } 1316 }
@@ -1559,6 +1657,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1559 else { 1657 else {
1560 updateHover_DocumentWidget_(d, init_I2(ev->motion.x, ev->motion.y)); 1658 updateHover_DocumentWidget_(d, init_I2(ev->motion.x, ev->motion.y));
1561 } 1659 }
1660 updateOutlineOpacity_DocumentWidget_(d);
1562 } 1661 }
1563 if (ev->type == SDL_MOUSEBUTTONDOWN) { 1662 if (ev->type == SDL_MOUSEBUTTONDOWN) {
1564 if (ev->button.button == SDL_BUTTON_X1) { 1663 if (ev->button.button == SDL_BUTTON_X1) {
@@ -1728,11 +1827,6 @@ struct Impl_DrawContext {
1728 iBool showLinkNumbers; 1827 iBool showLinkNumbers;
1729}; 1828};
1730 1829
1731static iRangecc bannerText_DocumentWidget_(const iDocumentWidget *d) {
1732 return isEmpty_String(d->titleUser) ? range_String(bannerText_GmDocument(d->doc))
1733 : range_String(d->titleUser);
1734}
1735
1736static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, 1830static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color,
1737 iRangecc mark, iBool *isInside) { 1831 iRangecc mark, iBool *isInside) {
1738 if (mark.start > mark.end) { 1832 if (mark.start > mark.end) {
@@ -1964,6 +2058,15 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1964// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId); 2058// drawRect_Paint(&d->paint, (iRect){ visPos, run->visBounds.size }, red_ColorId);
1965} 2059}
1966 2060
2061static void drawSideRect_(iPaint *p, iRect rect, int thickness) {
2062 if (equal_Color(get_Color(tmBannerBackground_ColorId), get_Color(tmBackground_ColorId))) {
2063 drawRectThickness_Paint(p, rect, thickness, tmBannerIcon_ColorId);
2064 }
2065 else {
2066 fillRect_Paint(p, rect, tmBannerBackground_ColorId);
2067 }
2068}
2069
1967static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { 2070static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
1968 const iWidget *w = constAs_Widget(d); 2071 const iWidget *w = constAs_Widget(d);
1969 const iRect bounds = bounds_Widget(w); 2072 const iRect bounds = bounds_Widget(w);
@@ -1973,17 +2076,19 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
1973 float opacity = value_Anim(&d->sideOpacity); 2076 float opacity = value_Anim(&d->sideOpacity);
1974 const int minBannerSize = lineHeight_Text(banner_FontId) * 2; 2077 const int minBannerSize = lineHeight_Text(banner_FontId) * 2;
1975 const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin; 2078 const int avail = left_Rect(docBounds) - left_Rect(bounds) - 2 * margin;
2079 iPaint p;
2080 init_Paint(&p);
2081 setClip_Paint(&p, bounds);
1976 if (avail > minBannerSize) { 2082 if (avail > minBannerSize) {
1977 if (banner && opacity > 0) { 2083 if (banner && opacity > 0) {
1978 setOpacity_Text(opacity); 2084 setOpacity_Text(opacity);
1979 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); 2085 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
1980 const iChar icon = siteIcon_GmDocument(d->doc); 2086 const iChar icon = siteIcon_GmDocument(d->doc);
1981 iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) }; 2087 iRect rect = { add_I2(topLeft_Rect(bounds), init1_I2(margin)), init1_I2(minBannerSize) };
1982 iPaint p;
1983 init_Paint(&p);
1984 p.alpha = opacity * 255; 2088 p.alpha = opacity * 255;
1985 int offset = iMax(0, bottom_Rect(banner->visBounds) - d->scrollY); 2089 //int offset = iMax(0, bottom_Rect(banner->visBounds) - d->scrollY);
1986 rect.pos.y += offset; 2090 rect.pos.y += height_Rect(bounds) / 2 - rect.size.y / 2 - (banner ? banner->visBounds.size.y / 2 : 0); // offset;
2091 drawSideRect_(&p, rect, gap_UI / 2);
1987 if (equal_Color(get_Color(tmBannerBackground_ColorId), get_Color(tmBackground_ColorId))) { 2092 if (equal_Color(get_Color(tmBannerBackground_ColorId), get_Color(tmBackground_ColorId))) {
1988 drawRectThickness_Paint(&p, rect, gap_UI / 2, tmBannerIcon_ColorId); 2093 drawRectThickness_Paint(&p, rect, gap_UI / 2, tmBannerIcon_ColorId);
1989 } 2094 }
@@ -1993,28 +2098,103 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) {
1993 iString str; 2098 iString str;
1994 initUnicodeN_String(&str, &icon, 1); 2099 initUnicodeN_String(&str, &icon, 1);
1995 drawCentered_Text(banner_FontId, rect, iTrue, tmBannerIcon_ColorId, "%s", cstr_String(&str)); 2100 drawCentered_Text(banner_FontId, rect, iTrue, tmBannerIcon_ColorId, "%s", cstr_String(&str));
2101#if 0
1996 if (avail >= minBannerSize * 2) { 2102 if (avail >= minBannerSize * 2) {
1997 const char *endp; 2103 const char *endp;
1998 iRangecc text = bannerText_DocumentWidget_(d); 2104 iRangecc text = bannerText_DocumentWidget_(d);
1999 iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text); 2105 iInt2 pos = addY_I2(bottomLeft_Rect(rect), gap_Text);
2000 const int font = banner_FontId; 2106 const int font = heading3_FontId;
2001 while (!isEmpty_Range(&text)) { 2107 while (!isEmpty_Range(&text)) {
2002 tryAdvance_Text(font, text, avail - 2 * margin, &endp); 2108 tryAdvance_Text(font, text, avail - 2 * margin, &endp);
2003 drawRange_Text(font, pos, tmBannerSideTitle_ColorId, (iRangecc){ text.start, endp }); 2109 drawRange_Text(
2110 font, pos, tmBannerTitle_ColorId, (iRangecc){ text.start, endp });
2111// drawRange_Text(font,
2112// add_I2(pos, init1_I2(-gap_UI / 4)),
2113// tmBackground_ColorId,
2114// (iRangecc){ text.start, endp });
2004 text.start = endp; 2115 text.start = endp;
2005 pos.y += lineHeight_Text(font); 2116 pos.y += lineHeight_Text(font);
2006 } 2117 }
2007 } 2118 }
2119#endif
2008 setOpacity_Text(1.0f); 2120 setOpacity_Text(1.0f);
2009 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); 2121 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
2010 } 2122 }
2011 /* Update date. */
2012 drawString_Text(default_FontId,
2013 add_I2(bottomLeft_Rect(bounds),
2014 init_I2(margin, -margin + -2 * lineHeight_Text(default_FontId))),
2015 tmQuoteIcon_ColorId,
2016 collect_String(format_Time(&d->sourceTime, "Received\n%H:%M %b %d, %Y")));
2017 } 2123 }
2124 /* Reception timestamp. */
2125 if (isValid_Time(&d->sourceTime)) {
2126 const int font = uiLabel_FontId;
2127 const iString *recv =
2128 collect_String(format_Time(&d->sourceTime, "Received at %I:%M %p\non %b %d, %Y"));
2129 const iInt2 size = advanceRange_Text(font, range_String(recv));
2130 if (size.x <= avail) {
2131 drawString_Text(font,
2132 add_I2(bottomLeft_Rect(bounds),
2133 init_I2(margin,
2134 -margin + -size.y +
2135 iMax(0, scrollMax_DocumentWidget_(d) - d->scrollY))),
2136 tmQuoteIcon_ColorId,
2137 recv);
2138 }
2139 }
2140 /* Outline on the right side. */
2141 const float outlineOpacity = value_Anim(&d->outlineOpacity);
2142 if (!isEmpty_Array(&d->outline) && outlineOpacity > 0.0f) {
2143// const int font = uiLabel_FontId;
2144 //iRect outlineRect = initCorners_Rect(topRight_Rect(docBounds), bottomRight_Rect(bounds));
2145 //const int excess = width_Rect(outlineRect) - 75 * gap_UI;
2146// if (excess > 0) {
2147// adjustEdges_Rect(&outlineRect, 0, 0, 0, excess);
2148// }
2149// const int margin = gap_UI * d->pageMargin;
2150 const int innerWidth = outlineWidth_DocumentWidget_(d);
2151 const int outWidth = innerWidth + 2 * outlinePadding_DocumentWidget_ * gap_UI;
2152 const int topMargin = 0;//d->pageMargin * gap_UI; // + (banner ? banner->visBounds.size.y : 0);
2153 const int bottomMargin = 3 * gap_UI; //d->pageMargin * gap_UI;
2154 iInt2 pos = add_I2(topRight_Rect(bounds), init_I2(-outWidth - width_Widget(d->scroll), topMargin));
2155// const int lineWidth = avail - margin;
2156// pos.y = drawRangeWrap_Text(uiContent_FontId, pos, lineWidth, tmBannerIcon_ColorId,
2157// bannerText_DocumentWidget_(d));
2158// pos.y += gap_UI;
2159 const int scrollMax = scrollMax_DocumentWidget_(d);
2160 const int outHeight = bottom_Rect(((const iOutlineItem *) constBack_Array(&d->outline))->rect);
2161 const int oversize = outHeight - height_Rect(bounds) + topMargin + bottomMargin;
2162 const int scroll =
2163 (oversize > 0 && scrollMax > 0 ? oversize * d->scrollY / scrollMax_DocumentWidget_(d)
2164 : 0);
2165 /* Center short outlines vertically. */
2166 if (oversize < 0) {
2167 pos.y -= oversize / 2;// + (banner ? banner->visBounds.size.y / 2 : 0);
2168 }
2169 pos.y -= scroll;
2170 setOpacity_Text(outlineOpacity);
2171 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND);
2172 p.alpha = outlineOpacity * 255;
2173 drawSideRect_(
2174 &p,
2175 (iRect){ addY_I2(pos, -outlinePadding_DocumentWidget_ * gap_UI / 2),
2176 init_I2(outWidth, outHeight + outlinePadding_DocumentWidget_ * gap_UI * 1.5f) },
2177 1);
2178 iConstForEach(Array, i, &d->outline) {
2179 const iOutlineItem *item = i.value;
2180 iInt2 visPos = addX_I2(add_I2(pos, item->rect.pos), outlinePadding_DocumentWidget_ * gap_UI);
2181// visPos.y -= scroll;
2182// if (item->sepColor != none_ColorId) {
2183// drawHLine_Paint(&p, addY_I2(visPos, -gap_UI), outWidth, item->sepColor);
2184// }
2185// drawRect_Paint(&p, (iRect){ visPos, item->rect.size }, red_ColorId);
2186 const iBool isVisible = d->lastVisibleRun && d->lastVisibleRun->text.start >= item->text.start;
2187 drawWrapRange_Text(item->font,
2188 visPos,
2189 innerWidth - left_Rect(item->rect),
2190 index_ArrayConstIterator(&i) == 0 || isVisible ? item->seenColor
2191 : tmQuoteIcon_ColorId,
2192 item->text);
2193 }
2194 setOpacity_Text(1.0f);
2195 SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE);
2196 }
2197 unsetClip_Paint(&p);
2018} 2198}
2019 2199
2020static void draw_DocumentWidget_(const iDocumentWidget *d) { 2200static void draw_DocumentWidget_(const iDocumentWidget *d) {
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index 2d6d84dd..d29548f1 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -204,6 +204,11 @@ static uint32_t cursorTimer_(uint32_t interval, void *w) {
204 return interval; 204 return interval;
205} 205}
206 206
207void selectAll_InputWidget(iInputWidget *d) {
208 d->mark = (iRanges){ 0, size_Array(&d->text) };
209 refresh_Widget(as_Widget(d));
210}
211
207void begin_InputWidget(iInputWidget *d) { 212void begin_InputWidget(iInputWidget *d) {
208 iWidget *w = as_Widget(d); 213 iWidget *w = as_Widget(d);
209 if (flags_Widget(w) & selected_WidgetFlag) { 214 if (flags_Widget(w) & selected_WidgetFlag) {
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index ced7f968..f146ea00 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -43,6 +43,7 @@ void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus)
43void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); 43void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits);
44void begin_InputWidget (iInputWidget *); 44void begin_InputWidget (iInputWidget *);
45void end_InputWidget (iInputWidget *, iBool accept); 45void end_InputWidget (iInputWidget *, iBool accept);
46void selectAll_InputWidget (iInputWidget *);
46 47
47const iString * text_InputWidget (const iInputWidget *); 48const iString * text_InputWidget (const iInputWidget *);
48 49
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 1cd8cae9..8bf64b5c 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -116,7 +116,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
116 iSidebarItem *item = new_SidebarItem(); 116 iSidebarItem *item = new_SidebarItem();
117 item->id = index_ArrayConstIterator(&i); 117 item->id = index_ArrayConstIterator(&i);
118 setRange_String(&item->label, head->text); 118 setRange_String(&item->label, head->text);
119 item->indent = head->level * 4 * gap_UI; 119 item->indent = head->level * 5 * gap_UI;
120 addItem_ListWidget(d->list, item); 120 addItem_ListWidget(d->list, item);
121 iRelease(item); 121 iRelease(item);
122 } 122 }
diff --git a/src/ui/text.c b/src/ui/text.c
index b2110bb6..23524808 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -142,16 +142,17 @@ struct Impl_CacheRow {
142}; 142};
143 143
144struct Impl_Text { 144struct Impl_Text {
145 float contentFontSize; 145 enum iTextFont contentFont;
146 iFont fonts[max_FontId]; 146 float contentFontSize;
147 SDL_Renderer *render; 147 iFont fonts[max_FontId];
148 SDL_Texture * cache; 148 SDL_Renderer * render;
149 iInt2 cacheSize; 149 SDL_Texture * cache;
150 int cacheRowAllocStep; 150 iInt2 cacheSize;
151 int cacheBottom; 151 int cacheRowAllocStep;
152 iArray cacheRows; 152 int cacheBottom;
153 SDL_Palette * grayscale; 153 iArray cacheRows;
154 iRegExp * ansiEscape; 154 SDL_Palette * grayscale;
155 iRegExp * ansiEscape;
155}; 156};
156 157
157static iText text_; 158static iText text_;
@@ -159,6 +160,16 @@ static iText text_;
159static void initFonts_Text_(iText *d) { 160static void initFonts_Text_(iText *d) {
160 const float textSize = fontSize_UI * d->contentFontSize; 161 const float textSize = fontSize_UI * d->contentFontSize;
161 const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f; 162 const float monoSize = fontSize_UI * d->contentFontSize / contentScale_Text_ * 0.866f;
163 const iBlock *regularFont = &fontNunitoRegular_Embedded;
164 const iBlock *italicFont = &fontNunitoLightItalic_Embedded;
165 const iBlock *boldFont = &fontNunitoExtraBold_Embedded;
166 const iBlock *lightFont = &fontNunitoExtraLight_Embedded;
167 if (d->contentFont == firaSans_TextFont) {
168 regularFont = &fontFiraSansRegular_Embedded;
169 italicFont = &fontFiraSansItalic_Embedded;
170 boldFont = &fontFiraSansBold_Embedded;
171 lightFont = &fontFiraSansLight_Embedded;
172 }
162 const struct { 173 const struct {
163 const iBlock *ttf; 174 const iBlock *ttf;
164 int size; 175 int size;
@@ -168,17 +179,17 @@ static void initFonts_Text_(iText *d) {
168 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, 179 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId },
169 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId }, 180 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId },
170 /* content fonts */ 181 /* content fonts */
171 { &fontNunitoRegular_Embedded, textSize, symbols_FontId }, 182 { regularFont, textSize, symbols_FontId },
172 { &fontFiraMonoRegular_Embedded, monoSize, monospaceSymbols_FontId }, 183 { &fontFiraMonoRegular_Embedded, monoSize, monospaceSymbols_FontId },
173 { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, monospaceSmallSymbols_FontId }, 184 { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, monospaceSmallSymbols_FontId },
174 { &fontNunitoRegular_Embedded, textSize * 1.200f, mediumSymbols_FontId }, 185 { regularFont, textSize * 1.200f, mediumSymbols_FontId },
175 { &fontNunitoRegular_Embedded, textSize * 1.333f, bigSymbols_FontId }, 186 { regularFont, textSize * 1.333f, bigSymbols_FontId },
176 { &fontNunitoLightItalic_Embedded, textSize, symbols_FontId }, 187 { italicFont, textSize, symbols_FontId },
177 { &fontNunitoExtraBold_Embedded, textSize, symbols_FontId }, 188 { boldFont, textSize, symbols_FontId },
178 { &fontNunitoExtraBold_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 189 { boldFont, textSize * 1.333f, mediumSymbols_FontId },
179 { &fontNunitoExtraBold_Embedded, textSize * 1.666f, largeSymbols_FontId }, 190 { boldFont, textSize * 1.666f, largeSymbols_FontId },
180 { &fontNunitoExtraBold_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 191 { boldFont, textSize * 2.000f, hugeSymbols_FontId },
181 { &fontNunitoExtraLight_Embedded, textSize * 1.666f, largeSymbols_FontId }, 192 { lightFont, textSize * 1.666f, largeSymbols_FontId },
182 /* symbol fonts */ 193 /* symbol fonts */
183 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId }, 194 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId },
184 { &fontSymbola_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, 195 { &fontSymbola_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId },
@@ -278,6 +289,7 @@ static void deinitCache_Text_(iText *d) {
278 289
279void init_Text(SDL_Renderer *render) { 290void init_Text(SDL_Renderer *render) {
280 iText *d = &text_; 291 iText *d = &text_;
292 d->contentFont = nunito_TextFont;
281 d->contentFontSize = contentScale_Text_; 293 d->contentFontSize = contentScale_Text_;
282 d->ansiEscape = new_RegExp("\\[([0-9;]+)m", 0); 294 d->ansiEscape = new_RegExp("\\[([0-9;]+)m", 0);
283 d->render = render; 295 d->render = render;
@@ -306,6 +318,13 @@ void setOpacity_Text(float opacity) {
306 SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 318 SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
307} 319}
308 320
321void setContentFont_Text(enum iTextFont font) {
322 if (text_.contentFont != font) {
323 text_.contentFont = font;
324 resetFonts_Text();
325 }
326}
327
309void setContentFontSize_Text(float fontSizeFactor) { 328void setContentFontSize_Text(float fontSizeFactor) {
310 fontSizeFactor *= contentScale_Text_; 329 fontSizeFactor *= contentScale_Text_;
311 iAssert(fontSizeFactor > 0); 330 iAssert(fontSizeFactor > 0);
@@ -766,6 +785,29 @@ void drawRange_Text(int fontId, iInt2 pos, int color, iRangecc text) {
766 draw_Text_(fontId, pos, color, text); 785 draw_Text_(fontId, pos, color, text);
767} 786}
768 787
788iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) {
789 iInt2 size = zero_I2();
790 const char *endp;
791 while (!isEmpty_Range(&text)) {
792 iInt2 line = tryAdvance_Text(fontId, text, maxWidth, &endp);
793 text.start = endp;
794 size.x = iMax(size.x, line.x);
795 size.y += line.y;
796 }
797 return size;
798}
799
800int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc text) {
801 const char *endp;
802 while (!isEmpty_Range(&text)) {
803 tryAdvance_Text(fontId, text, maxWidth, &endp);
804 drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp });
805 text.start = endp;
806 pos.y += lineHeight_Text(fontId);
807 }
808 return pos.y;
809}
810
769void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, const char *format, ...) { 811void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, const char *format, ...) {
770 iBlock chars; 812 iBlock chars;
771 init_Block(&chars, 0); { 813 init_Block(&chars, 0); {
diff --git a/src/ui/text.h b/src/ui/text.h
index 71c2ba43..fa7b9402 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -105,13 +105,19 @@ iLocalDef iBool isVariationSelector_Char(iChar ch) {
105 105
106#define variationSelectorEmoji_Char ((iChar) 0xfe0f) 106#define variationSelectorEmoji_Char ((iChar) 0xfe0f)
107 107
108enum iTextFont {
109 nunito_TextFont,
110 firaSans_TextFont,
111};
112
108extern int gap_Text; /* affected by content font size */ 113extern int gap_Text; /* affected by content font size */
109 114
110void init_Text (SDL_Renderer *); 115void init_Text (SDL_Renderer *);
111void deinit_Text (void); 116void deinit_Text (void);
112 117
118void setContentFont_Text (enum iTextFont font);
113void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ 119void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */
114void resetFonts_Text (void); 120void resetFonts_Text (void);
115 121
116int lineHeight_Text (int fontId); 122int lineHeight_Text (int fontId);
117iInt2 measure_Text (int fontId, const char *text); 123iInt2 measure_Text (int fontId, const char *text);
@@ -120,6 +126,7 @@ iRect visualBounds_Text (int fontId, iRangecc text);
120iInt2 advance_Text (int fontId, const char *text); 126iInt2 advance_Text (int fontId, const char *text);
121iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ 127iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */
122iInt2 advanceRange_Text (int fontId, iRangecc text); 128iInt2 advanceRange_Text (int fontId, iRangecc text);
129iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text);
123 130
124iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); 131iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos);
125iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); 132iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos);
@@ -137,6 +144,7 @@ void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment a
137void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); 144void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...);
138void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); 145void drawString_Text (int fontId, iInt2 pos, int color, const iString *text);
139void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); 146void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text);
147int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */
140 148
141SDL_Texture * glyphCache_Text (void); 149SDL_Texture * glyphCache_Text (void);
142 150
diff --git a/src/ui/util.c b/src/ui/util.c
index 5d283742..27bac834 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
36 36
37#include <the_Foundation/math.h> 37#include <the_Foundation/math.h>
38#include <the_Foundation/path.h> 38#include <the_Foundation/path.h>
39#include <SDL_timer.h>
39 40
40iBool isCommand_SDLEvent(const SDL_Event *d) { 41iBool isCommand_SDLEvent(const SDL_Event *d) {
41 return d->type == SDL_USEREVENT && d->user.code == command_UserEventCode; 42 return d->type == SDL_USEREVENT && d->user.code == command_UserEventCode;
@@ -140,7 +141,7 @@ void init_Anim(iAnim *d, float value) {
140 141
141void setValue_Anim(iAnim *d, float to, uint32_t span) { 142void setValue_Anim(iAnim *d, float to, uint32_t span) {
142 if (fabsf(to - d->to) > 0.00001f) { 143 if (fabsf(to - d->to) > 0.00001f) {
143 const uint32_t now = frameTime_Window(get_Window()); 144 const uint32_t now = SDL_GetTicks();
144 d->from = value_Anim(d); 145 d->from = value_Anim(d);
145 d->to = to; 146 d->to = to;
146 d->when = now; 147 d->when = now;
@@ -918,6 +919,13 @@ iWidget *makePreferences_Widget(void) {
918 } 919 }
919 /* Layout. */ { 920 /* Layout. */ {
920 appendTwoColumnPage_(tabs, "Layout", '2', &headings, &values); 921 appendTwoColumnPage_(tabs, "Layout", '2', &headings, &values);
922 addChild_Widget(headings, iClob(makeHeading_Widget("Font:")));
923 iWidget *fonts = new_Widget();
924 /* Fonts. */ {
925 addRadioButton_(fonts, "prefs.font.0", "Nunito", "font.set arg:0");
926 addRadioButton_(fonts, "prefs.font.1", "Fira Sans", "font.set arg:1");
927 }
928 addChildFlags_Widget(values, iClob(fonts), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
921 addChild_Widget(headings, iClob(makeHeading_Widget("Line width:"))); 929 addChild_Widget(headings, iClob(makeHeading_Widget("Line width:")));
922 iWidget *widths = new_Widget(); 930 iWidget *widths = new_Widget();
923 /* Line widths. */ { 931 /* Line widths. */ {
diff --git a/src/ui/window.c b/src/ui/window.c
index 19432691..b1fd3a07 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -94,7 +94,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
94/* TODO: Submenus wouldn't hurt here. */ 94/* TODO: Submenus wouldn't hurt here. */
95static const iMenuItem navMenuItems[] = { 95static const iMenuItem navMenuItems[] = {
96 { "New Tab", 't', KMOD_PRIMARY, "tabs.new" }, 96 { "New Tab", 't', KMOD_PRIMARY, "tabs.new" },
97 { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, 97 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
98 { "---", 0, 0, NULL }, 98 { "---", 0, 0, NULL },
99 { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, 99 { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" },
100 { "---", 0, 0, NULL }, 100 { "---", 0, 0, NULL },
@@ -118,7 +118,7 @@ static const iMenuItem navMenuItems[] = {
118/* Using native menus. */ 118/* Using native menus. */
119static const iMenuItem fileMenuItems[] = { 119static const iMenuItem fileMenuItems[] = {
120 { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" }, 120 { "New Tab", SDLK_t, KMOD_PRIMARY, "tabs.new" },
121 { "Open Location...", SDLK_l, KMOD_PRIMARY, "focus.set id:url" }, 121 { "Open Location...", SDLK_l, KMOD_PRIMARY, "navigate.focus" },
122 { "---", 0, 0, NULL }, 122 { "---", 0, 0, NULL },
123 { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" }, 123 { "Save to Downloads", SDLK_s, KMOD_PRIMARY, "document.save" },
124}; 124};
@@ -212,6 +212,16 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
212 postCommand_Widget(navBar, "layout.changed id:navbar"); 212 postCommand_Widget(navBar, "layout.changed id:navbar");
213 return iFalse; 213 return iFalse;
214 } 214 }
215 else if (equal_Command(cmd, "navigate.focus")) {
216 iWidget *url = findChild_Widget(navBar, "url");
217 if (focus_Widget() != url) {
218 setFocus_Widget(findChild_Widget(navBar, "url"));
219 }
220 else {
221 selectAll_InputWidget((iInputWidget *) url);
222 }
223 return iTrue;
224 }
215 else if (equal_Command(cmd, "input.edited")) { 225 else if (equal_Command(cmd, "input.edited")) {
216 iAnyObject *url = findChild_Widget(navBar, "url"); 226 iAnyObject *url = findChild_Widget(navBar, "url");
217 if (pointer_Command(cmd) == url) { 227 if (pointer_Command(cmd) == url) {
@@ -459,7 +469,7 @@ static void setupUserInterface_Window(iWindow *d) {
459 addAction_Widget(d->root, prevTab_KeyShortcut, "tabs.prev"); 469 addAction_Widget(d->root, prevTab_KeyShortcut, "tabs.prev");
460 addAction_Widget(d->root, nextTab_KeyShortcut, "tabs.next"); 470 addAction_Widget(d->root, nextTab_KeyShortcut, "tabs.next");
461#if !defined (iHaveNativeMenus) 471#if !defined (iHaveNativeMenus)
462 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "focus.set id:url"); 472 addAction_Widget(d->root, 'l', KMOD_PRIMARY, "navigate.focus");
463 addAction_Widget(d->root, 'f', KMOD_PRIMARY, "focus.set id:find.input"); 473 addAction_Widget(d->root, 'f', KMOD_PRIMARY, "focus.set id:find.input");
464 addAction_Widget(d->root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1"); 474 addAction_Widget(d->root, '1', KMOD_PRIMARY, "sidebar.mode arg:0 toggle:1");
465 addAction_Widget(d->root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1"); 475 addAction_Widget(d->root, '2', KMOD_PRIMARY, "sidebar.mode arg:1 toggle:1");