diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-25 18:32:54 +0300 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2020-09-25 18:32:54 +0300 |
commit | 84d3089735bd81a9d628acf416b481f2535765cf (patch) | |
tree | 762303b237cb4d34909a175a2b62bf2ec74d23c6 | |
parent | 8700c039dc04b4c9f22584d4e901ed372442b0f4 (diff) |
Document side elements; hover outline
-rw-r--r-- | res/about/help.gmi | 14 | ||||
-rw-r--r-- | res/about/version.gmi | 6 | ||||
-rw-r--r-- | src/app.c | 12 | ||||
-rw-r--r-- | src/gmdocument.h | 2 | ||||
-rw-r--r-- | src/prefs.c | 1 | ||||
-rw-r--r-- | src/prefs.h | 6 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 226 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 5 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 1 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 2 | ||||
-rw-r--r-- | src/ui/text.c | 80 | ||||
-rw-r--r-- | src/ui/text.h | 10 | ||||
-rw-r--r-- | src/ui/util.c | 10 | ||||
-rw-r--r-- | src/ui/window.c | 16 |
14 files changed, 338 insertions, 53 deletions
diff --git a/res/about/help.gmi b/res/about/help.gmi index ca5e164f..1059dd68 100644 --- a/res/about/help.gmi +++ b/res/about/help.gmi | |||
@@ -132,7 +132,19 @@ In addition to a title, bookmarks can have tags. Some tags have a special meanin | |||
132 | 132 | ||
133 | Gemini uses TLS client certificates for manual user/session identification purposes. This is analogous to logging into a web site, except you are in full control of the information. The term "Identity" is used in Lagrange to refer to client certificates. | 133 | Gemini uses TLS client certificates for manual user/session identification purposes. This is analogous to logging into a web site, except you are in full control of the information. The term "Identity" is used in Lagrange to refer to client certificates. |
134 | 134 | ||
135 | Lagrange can easily create a new identity. The shortcut for this is ${SHIFT+}${CTRL+}N. Consider any information you enter in the certificate as public; only the Common Name is required and will appear as the issuer and subject of the certificate. | 135 | The Identities sidebar tab shows all identities known to Lagrange. Press ${CTRL+}3 to show it. This includes identities created in Lagrange and any identities based on imported X.509 certificates. |
136 | |||
137 | ### Creating a new identity | ||
138 | |||
139 | Click on the 👤 button in the navigation bar and select "New Identity...". The shortcut for this is ${SHIFT+}${CTRL+}N. | ||
140 | |||
141 | Consider any information you enter in the certificate as public; only the Common Name is required and will appear as the issuer and subject of the certificate. | ||
142 | |||
143 | ### Using an identity | ||
144 | |||
145 | You'll need to select an identity when you encounter this error message: | ||
146 | |||
147 | ... | ||
136 | 148 | ||
137 | Clicking on an identity in the sidebar will toggle it on/off for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. | 149 | Clicking on an identity in the sidebar will toggle it on/off for the currently open URL. On subsequent page loads, the certificate will then be sent to the server when the URL or any URL under it is fetched. You can click on the 👤 button in the navigation bar to see which identity is being used for the current page. |
138 | 150 | ||
diff --git a/res/about/version.gmi b/res/about/version.gmi index 94ddae60..2df2f9b4 100644 --- a/res/about/version.gmi +++ b/res/about/version.gmi | |||
@@ -7,8 +7,12 @@ | |||
7 | # Release notes | 7 | # Release notes |
8 | 8 | ||
9 | ## 0.3 | 9 | ## 0.3 |
10 | * Added "Open Link in Background Tab" to link context menu. | 10 | * Added preferences for font and page layout. |
11 | * Added preference for color saturation. | ||
11 | * Added tabs in Preferences for better grouping. | 12 | * Added tabs in Preferences for better grouping. |
13 | * Page outline appears when mouse is hovering over the scrollbar. | ||
14 | * Site icon appears on the left when the window is wide enough. | ||
15 | * Added "Open Link in Background Tab" to link context menu. | ||
12 | * Slightly smaller first paragraph font size. | 16 | * Slightly smaller first paragraph font size. |
13 | * Improved text selection behavior when starting on empty space. | 17 | * Improved text selection behavior when starting on empty space. |
14 | * Fixed: Centered popups do not remain centered when window is resized. | 18 | * Fixed: Centered popups do not remain centered when window is resized. |
@@ -165,6 +165,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
165 | } | 165 | } |
166 | appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar)); | 166 | appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar)); |
167 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); | 167 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); |
168 | appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); | ||
168 | appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent); | 169 | appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent); |
169 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); | 170 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); |
170 | appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph); | 171 | appendFormat_String(str, "prefs.biglede.changed arg:%d\n", d->prefs.bigFirstParagraph); |
@@ -810,6 +811,14 @@ iBool handleCommand_App(const char *cmd) { | |||
810 | SDL_MaximizeWindow(d->window->win); | 811 | SDL_MaximizeWindow(d->window->win); |
811 | return iTrue; | 812 | return iTrue; |
812 | } | 813 | } |
814 | else if (equal_Command(cmd, "font.set")) { | ||
815 | setFreezeDraw_Window(get_Window(), iTrue); | ||
816 | d->prefs.font = arg_Command(cmd); | ||
817 | setContentFont_Text(d->prefs.font); | ||
818 | postCommand_App("font.changed"); | ||
819 | postCommand_App("window.unfreeze"); | ||
820 | return iTrue; | ||
821 | } | ||
813 | else if (equal_Command(cmd, "zoom.set")) { | 822 | else if (equal_Command(cmd, "zoom.set")) { |
814 | setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ | 823 | setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ |
815 | d->prefs.zoomPercent = arg_Command(cmd); | 824 | d->prefs.zoomPercent = arg_Command(cmd); |
@@ -977,6 +986,9 @@ iBool handleCommand_App(const char *cmd) { | |||
977 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); | 986 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); |
978 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), | 987 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), |
979 | collectNewFormat_String("%g", uiScale_Window(d->window))); | 988 | collectNewFormat_String("%g", uiScale_Window(d->window))); |
989 | setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), | ||
990 | selected_WidgetFlag, | ||
991 | iTrue); | ||
980 | setFlags_Widget( | 992 | setFlags_Widget( |
981 | findChild_Widget(dlg, format_CStr("prefs.linewidth.%d", d->prefs.lineWidth)), | 993 | findChild_Widget(dlg, format_CStr("prefs.linewidth.%d", d->prefs.lineWidth)), |
982 | selected_WidgetFlag, | 994 | selected_WidgetFlag, |
diff --git a/src/gmdocument.h b/src/gmdocument.h index ec47258a..891ac6f2 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -118,7 +118,7 @@ iInt2 size_GmDocument (const iGmDocument *); | |||
118 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); | 118 | const iGmRun * siteBanner_GmDocument (const iGmDocument *); |
119 | iBool hasSiteBanner_GmDocument (const iGmDocument *); | 119 | iBool hasSiteBanner_GmDocument (const iGmDocument *); |
120 | const iString * bannerText_GmDocument (const iGmDocument *); | 120 | const iString * bannerText_GmDocument (const iGmDocument *); |
121 | const iArray * headings_GmDocument (const iGmDocument *); | 121 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ |
122 | const iString * source_GmDocument (const iGmDocument *); | 122 | const iString * source_GmDocument (const iGmDocument *); |
123 | 123 | ||
124 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); | 124 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
diff --git a/src/prefs.c b/src/prefs.c index d773acbd..9b77b4f2 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -6,6 +6,7 @@ void init_Prefs(iPrefs *d) { | |||
6 | d->retainWindowSize = iTrue; | 6 | d->retainWindowSize = iTrue; |
7 | d->zoomPercent = 100; | 7 | d->zoomPercent = 100; |
8 | d->forceLineWrap = iFalse; | 8 | d->forceLineWrap = iFalse; |
9 | d->font = nunito_TextFont; | ||
9 | d->lineWidth = 40; | 10 | d->lineWidth = 40; |
10 | d->bigFirstParagraph = iTrue; | 11 | d->bigFirstParagraph = iTrue; |
11 | d->docThemeDark = colorfulDark_GmDocumentTheme; | 12 | d->docThemeDark = colorfulDark_GmDocumentTheme; |
diff --git a/src/prefs.h b/src/prefs.h index d4e300ec..42ccff85 100644 --- a/src/prefs.h +++ b/src/prefs.h | |||
@@ -1,8 +1,9 @@ | |||
1 | #pragma once | 1 | #pragma once |
2 | #include <the_Foundation/string.h> | 2 | #include <the_Foundation/string.h> |
3 | 3 | ||
4 | #include "ui/color.h" | ||
5 | #include "gmdocument.h" | 4 | #include "gmdocument.h" |
5 | #include "ui/color.h" | ||
6 | #include "ui/text.h" | ||
6 | 7 | ||
7 | /* User preferences */ | 8 | /* User preferences */ |
8 | 9 | ||
@@ -10,7 +11,7 @@ iDeclareType(Prefs) | |||
10 | 11 | ||
11 | struct Impl_Prefs { | 12 | struct Impl_Prefs { |
12 | iBool retainWindowSize; | 13 | iBool retainWindowSize; |
13 | float uiScale; | 14 | float uiScale; |
14 | int zoomPercent; | 15 | int zoomPercent; |
15 | iBool useSystemTheme; | 16 | iBool useSystemTheme; |
16 | enum iColorTheme theme; | 17 | enum iColorTheme theme; |
@@ -18,6 +19,7 @@ struct Impl_Prefs { | |||
18 | iString httpProxy; | 19 | iString httpProxy; |
19 | iString downloadDir; | 20 | iString downloadDir; |
20 | /* Content */ | 21 | /* Content */ |
22 | enum iTextFont font; | ||
21 | int lineWidth; | 23 | int lineWidth; |
22 | iBool bigFirstParagraph; | 24 | iBool bigFirstParagraph; |
23 | iBool forceLineWrap; | 25 | iBool forceLineWrap; |
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 | ||
130 | iDeclareType(OutlineItem) | ||
131 | |||
132 | struct Impl_OutlineItem { | ||
133 | iRangecc text; | ||
134 | int font; | ||
135 | iRect rect; | ||
136 | int seenColor; | ||
137 | int sepColor; | ||
138 | }; | ||
139 | |||
140 | /*----------------------------------------------------------------------------------------------*/ | ||
141 | |||
130 | static const int smoothSpeed_DocumentWidget_ = 120; /* unit: gap_Text per second */ | 142 | static const int smoothSpeed_DocumentWidget_ = 120; /* unit: gap_Text per second */ |
131 | 143 | ||
132 | enum iRequestState { | 144 | enum 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) { | |||
227 | void deinit_DocumentWidget(iDocumentWidget *d) { | 244 | void 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 | ||
315 | static void addVisibleLink_DocumentWidget_(void *context, const iGmRun *run) { | 333 | static 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 | ||
380 | static void animate_DocumentWidget_(void *ticker) { | 399 | static 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 | |||
416 | static 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 | ||
541 | static const int outlineMinWidth_DocumentWdiget_ = 45; /* times gap_UI */ | ||
542 | static const int outlineMaxWidth_DocumentWidget_ = 65; /* times gap_UI */ | ||
543 | static const int outlinePadding_DocumentWidget_ = 3; /* times gap_UI */ | ||
544 | |||
545 | static 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 | |||
558 | static 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 | |||
563 | static 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 | |||
509 | static void setSource_DocumentWidget_(iDocumentWidget *d, const iString *source) { | 599 | static 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) { | |||
1069 | void updateSize_DocumentWidget(iDocumentWidget *d) { | 1163 | void 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 | ||
1731 | static 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 | |||
1736 | static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iColorId color, | 1830 | static 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 | ||
2061 | static 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 | |||
1967 | static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | 2070 | static 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 | ||
2020 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 2200 | static 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 | ||
207 | void selectAll_InputWidget(iInputWidget *d) { | ||
208 | d->mark = (iRanges){ 0, size_Array(&d->text) }; | ||
209 | refresh_Widget(as_Widget(d)); | ||
210 | } | ||
211 | |||
207 | void begin_InputWidget(iInputWidget *d) { | 212 | void 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) | |||
43 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); | 43 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); |
44 | void begin_InputWidget (iInputWidget *); | 44 | void begin_InputWidget (iInputWidget *); |
45 | void end_InputWidget (iInputWidget *, iBool accept); | 45 | void end_InputWidget (iInputWidget *, iBool accept); |
46 | void selectAll_InputWidget (iInputWidget *); | ||
46 | 47 | ||
47 | const iString * text_InputWidget (const iInputWidget *); | 48 | const 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 | ||
144 | struct Impl_Text { | 144 | struct 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 | ||
157 | static iText text_; | 158 | static iText text_; |
@@ -159,6 +160,16 @@ static iText text_; | |||
159 | static void initFonts_Text_(iText *d) { | 160 | static 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 | ||
279 | void init_Text(SDL_Renderer *render) { | 290 | void 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 | ||
321 | void setContentFont_Text(enum iTextFont font) { | ||
322 | if (text_.contentFont != font) { | ||
323 | text_.contentFont = font; | ||
324 | resetFonts_Text(); | ||
325 | } | ||
326 | } | ||
327 | |||
309 | void setContentFontSize_Text(float fontSizeFactor) { | 328 | void 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 | ||
788 | iInt2 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 | |||
800 | int 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 | |||
769 | void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, const char *format, ...) { | 811 | void 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 | ||
108 | enum iTextFont { | ||
109 | nunito_TextFont, | ||
110 | firaSans_TextFont, | ||
111 | }; | ||
112 | |||
108 | extern int gap_Text; /* affected by content font size */ | 113 | extern int gap_Text; /* affected by content font size */ |
109 | 114 | ||
110 | void init_Text (SDL_Renderer *); | 115 | void init_Text (SDL_Renderer *); |
111 | void deinit_Text (void); | 116 | void deinit_Text (void); |
112 | 117 | ||
118 | void setContentFont_Text (enum iTextFont font); | ||
113 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ | 119 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ |
114 | void resetFonts_Text (void); | 120 | void resetFonts_Text (void); |
115 | 121 | ||
116 | int lineHeight_Text (int fontId); | 122 | int lineHeight_Text (int fontId); |
117 | iInt2 measure_Text (int fontId, const char *text); | 123 | iInt2 measure_Text (int fontId, const char *text); |
@@ -120,6 +126,7 @@ iRect visualBounds_Text (int fontId, iRangecc text); | |||
120 | iInt2 advance_Text (int fontId, const char *text); | 126 | iInt2 advance_Text (int fontId, const char *text); |
121 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ | 127 | iInt2 advanceN_Text (int fontId, const char *text, size_t n); /* `n` in characters */ |
122 | iInt2 advanceRange_Text (int fontId, iRangecc text); | 128 | iInt2 advanceRange_Text (int fontId, iRangecc text); |
129 | iInt2 advanceWrapRange_Text (int fontId, int maxWidth, iRangecc text); | ||
123 | 130 | ||
124 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); | 131 | iInt2 tryAdvance_Text (int fontId, iRangecc text, int width, const char **endPos); |
125 | iInt2 tryAdvanceNoWrap_Text (int fontId, iRangecc text, int width, const char **endPos); | 132 | iInt2 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 | |||
137 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); | 144 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); |
138 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); | 145 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); |
139 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); | 146 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); |
147 | int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ | ||
140 | 148 | ||
141 | SDL_Texture * glyphCache_Text (void); | 149 | SDL_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 | ||
40 | iBool isCommand_SDLEvent(const SDL_Event *d) { | 41 | iBool 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 | ||
141 | void setValue_Anim(iAnim *d, float to, uint32_t span) { | 142 | void 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. */ |
95 | static const iMenuItem navMenuItems[] = { | 95 | static 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. */ |
119 | static const iMenuItem fileMenuItems[] = { | 119 | static 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"); |