summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--README.md6
-rw-r--r--res/about/version.gmi12
-rw-r--r--res/fi.skyjake.Lagrange.appdata.xml2
-rw-r--r--res/lang/fi.binbin24879 -> 24893 bytes
-rw-r--r--res/lang/ie.binbin24133 -> 24247 bytes
-rw-r--r--res/lang/sr.binbin35978 -> 36801 bytes
-rw-r--r--res/lang/tok.binbin22702 -> 22683 bytes
-rw-r--r--src/app.c1
-rw-r--r--src/gmcerts.h10
-rw-r--r--src/ui/documentwidget.c113
-rw-r--r--src/ui/inputwidget.c128
-rw-r--r--src/ui/inputwidget.h3
-rw-r--r--src/ui/root.c9
-rw-r--r--src/ui/uploadwidget.c2
-rw-r--r--src/ui/visbuf.c8
16 files changed, 174 insertions, 122 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index aac03046..c9037986 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -18,7 +18,7 @@
18cmake_minimum_required (VERSION 3.9) 18cmake_minimum_required (VERSION 3.9)
19 19
20project (Lagrange 20project (Lagrange
21 VERSION 1.6.2 21 VERSION 1.6.3
22 DESCRIPTION "A Beautiful Gemini Client" 22 DESCRIPTION "A Beautiful Gemini Client"
23 LANGUAGES C 23 LANGUAGES C
24) 24)
diff --git a/README.md b/README.md
index 3aa3d4e8..3d9df1db 100644
--- a/README.md
+++ b/README.md
@@ -49,16 +49,16 @@ The required tools are a C11 compiler (e.g., Clang or GCC), CMake and `pkg-confi
49 49
50### Unicode text rendering 50### Unicode text rendering
51 51
52Lagrange relies on the [HarfBuzz](https://harfbuzz.github.io) and [GNU FriBidi](https://github.com/fribidi/fribidi/) libraries for handling complex scripts and bidirectional text. This repository includes these two libraries as submodules. By default, HarfBuzz and GNU FriBidi will be compiled as part of the app, without any additional dependencies. This allows the app to be built on systems where these libraries are not readily available. 52Lagrange relies on the [HarfBuzz](https://harfbuzz.github.io) and [GNU FriBidi](https://github.com/fribidi/fribidi/) libraries for handling complex scripts and bidirectional text. This repository includes these two libraries as submodules. By default, if HarfBuzz and GNU FriBidi are not available on the system, they will be compiled as part of the app without any additional dependencies.
53 53
54Note that compiling these libraries has the following requirements: 54Note that compiling these libraries has the following requirements:
55 55
56* HarfBuzz requires a C++ compiler. 56* HarfBuzz requires a C++ compiler.
57* GNU FriBidi cannot be compiled with CMake; you need to have [Meson](https://mesonbuild.com) and [Ninja](https://ninja-build.org). 57* GNU FriBidi cannot be compiled with CMake; you need to have [Meson](https://mesonbuild.com) and [Ninja](https://ninja-build.org).
58 58
59If these requirements cannot be met, or you would prefer the use the system-provided HarfBuzz and GNU FriBidi, the build options can be changed. See the table below for `ENABLE_HARFBUZZ_MINIMAL` and `ENABLE_FRIBIDI_BUILD` (set both to **NO**). Note that a system-provided HarfBuzz likely has dependencies to other libraries, such as FreeType and GLib. 59If these requirements cannot be met, or you would prefer the use the system-provided HarfBuzz and GNU FriBidi, please refer to the list of build options below: `ENABLE_HARFBUZZ_MINIMAL` and `ENABLE_FRIBIDI_BUILD` should both be set to **NO**. Note that a system-provided HarfBuzz likely has dependencies to other libraries, such as FreeType and GLib.
60 60
61You also may disable HarfBuzz and/or GNU FriBidi entirely. The old text renderer that supports only non-complex left-to-right scripts is then used. 61You also may disable HarfBuzz and/or GNU FriBidi entirely. The old text renderer that only supports non-complex left-to-right scripts is then used.
62 62
63### Installing to a custom directory 63### Installing to a custom directory
64 64
diff --git a/res/about/version.gmi b/res/about/version.gmi
index c06868c3..c45a8853 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,16 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 1.6.3
10* Select all text in an input field using Shift+Ctrl+A (macOS: ⌘A).
11* Input fields do not lose focus when the window becomes inactive, making it easier to resume input afterwards.
12* Fixed delay after switching to split view mode.
13* Fixed what gets drawn in an empty tab, before a document is available for rendering (e.g., after switching to split view mode).
14* Fixed highlighting the domain name in URL input fields.
15* Fixed hiding the Gemini URL scheme in input fields when the window is narrow.
16* Fixed the line break key modifier inadvertently affecting URL input fields, where line breaks are not allowed.
17* Fixed the line break key modifier affecting the upload dialog's text field.
18
9## 1.6.2 19## 1.6.2
10* Added `--tab-url` to print currently active tab's URL. 20* Added `--tab-url` to print currently active tab's URL.
11* Upload dialog expands to full window height when the entered text is long. 21* Upload dialog expands to full window height when the entered text is long.
@@ -16,7 +26,7 @@
16* Fixed incorrect behavior in input fields when typing or deleting text while holding down the Shift key. 26* Fixed incorrect behavior in input fields when typing or deleting text while holding down the Shift key.
17* Fixed crash in Upload dialog if server responds with a redirect. 27* Fixed crash in Upload dialog if server responds with a redirect.
18* Fixed buffered graphics (UI, fonts) getting lost under rare circumstances. 28* Fixed buffered graphics (UI, fonts) getting lost under rare circumstances.
19* Fixed drawing of wrapped text when the app is compiled with HarfBuzz. 29* Fixed drawing of wrapped text when the app is compiled without HarfBuzz.
20* macOS: Fixed UI not updating when system dark mode is toggled while the window is hidden. 30* macOS: Fixed UI not updating when system dark mode is toggled while the window is hidden.
21 31
22## 1.6.1 32## 1.6.1
diff --git a/res/fi.skyjake.Lagrange.appdata.xml b/res/fi.skyjake.Lagrange.appdata.xml
index 247d2f0b..1db2717c 100644
--- a/res/fi.skyjake.Lagrange.appdata.xml
+++ b/res/fi.skyjake.Lagrange.appdata.xml
@@ -70,8 +70,6 @@
70 redirect.</li> 70 redirect.</li>
71 <li>Fixed buffered graphics (UI, fonts) getting lost under rare 71 <li>Fixed buffered graphics (UI, fonts) getting lost under rare
72 circumstances.</li> 72 circumstances.</li>
73 <li>Fixed drawing of wrapped text when the app is compiled with
74 HarfBuzz.</li>
75 <li>macOS: Fixed UI not updating when system dark mode is toggled 73 <li>macOS: Fixed UI not updating when system dark mode is toggled
76 while the window is hidden.</li> 74 while the window is hidden.</li>
77 </ul> 75 </ul>
diff --git a/res/lang/fi.bin b/res/lang/fi.bin
index 7b7b6c28..2f1bb18f 100644
--- a/res/lang/fi.bin
+++ b/res/lang/fi.bin
Binary files differ
diff --git a/res/lang/ie.bin b/res/lang/ie.bin
index 97c02a90..a8bce39d 100644
--- a/res/lang/ie.bin
+++ b/res/lang/ie.bin
Binary files differ
diff --git a/res/lang/sr.bin b/res/lang/sr.bin
index 4a299323..df8e532b 100644
--- a/res/lang/sr.bin
+++ b/res/lang/sr.bin
Binary files differ
diff --git a/res/lang/tok.bin b/res/lang/tok.bin
index 4cd4212d..f2b59891 100644
--- a/res/lang/tok.bin
+++ b/res/lang/tok.bin
Binary files differ
diff --git a/src/app.c b/src/app.c
index 8318eee4..bcd06722 100644
--- a/src/app.c
+++ b/src/app.c
@@ -1992,6 +1992,7 @@ iBool handleCommand_App(const char *cmd) {
1992 (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); 1992 (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1);
1993 const char *url = suffixPtr_Command(cmd, "url"); 1993 const char *url = suffixPtr_Command(cmd, "url");
1994 setCStr_String(get_Window()->pendingSplitUrl, url ? url : ""); 1994 setCStr_String(get_Window()->pendingSplitUrl, url ? url : "");
1995 postRefresh_App();
1995 return iTrue; 1996 return iTrue;
1996 } 1997 }
1997 else if (equal_Command(cmd, "window.retain")) { 1998 else if (equal_Command(cmd, "window.retain")) {
diff --git a/src/gmcerts.h b/src/gmcerts.h
index 1a9480f7..02a41c14 100644
--- a/src/gmcerts.h
+++ b/src/gmcerts.h
@@ -60,9 +60,10 @@ iDeclareTypeConstructionArgs(GmCerts, const char *saveDir)
60 60
61typedef iBool (*iGmCertsIdentityFilterFunc)(void *context, const iGmIdentity *); 61typedef iBool (*iGmCertsIdentityFilterFunc)(void *context, const iGmIdentity *);
62 62
63iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, uint16_t port, const iTlsCertificate *cert); 63iBool checkTrust_GmCerts (iGmCerts *, iRangecc domain, uint16_t port,
64void setTrusted_GmCerts (iGmCerts *, iRangecc domain, uint16_t port, const iBlock *fingerprint, 64 const iTlsCertificate *cert);
65 const iDate *validUntil); 65void setTrusted_GmCerts (iGmCerts *, iRangecc domain, uint16_t port,
66 const iBlock *fingerprint, const iDate *validUntil);
66iTime domainValidUntil_GmCerts(const iGmCerts *, iRangecc domain, uint16_t port); 67iTime domainValidUntil_GmCerts(const iGmCerts *, iRangecc domain, uint16_t port);
67 68
68/** 69/**
@@ -81,7 +82,8 @@ iGmIdentity * newIdentity_GmCerts (iGmCerts *, int flags, iDate validU
81 const iString *userId, const iString *domain, 82 const iString *userId, const iString *domain,
82 const iString *org, const iString *country); 83 const iString *org, const iString *country);
83 84
84void importIdentity_GmCerts (iGmCerts *, iTlsCertificate *cert, const iString *notes); /* takes ownership */ 85void importIdentity_GmCerts (iGmCerts *, iTlsCertificate *cert,
86 const iString *notes); /* takes ownership */
85void deleteIdentity_GmCerts (iGmCerts *, iGmIdentity *identity); 87void deleteIdentity_GmCerts (iGmCerts *, iGmIdentity *identity);
86void saveIdentities_GmCerts (const iGmCerts *); 88void saveIdentities_GmCerts (const iGmCerts *);
87 89
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index e1be27c2..703d1d13 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -493,7 +493,7 @@ static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
493 const iPrefs * prefs = prefs_App(); 493 const iPrefs * prefs = prefs_App();
494 const int minWidth = 50 * gap_UI; /* lines must fit a word at least */ 494 const int minWidth = 50 * gap_UI; /* lines must fit a word at least */
495 const float adjust = iClamp((float) bounds.size.x / gap_UI / 11 - 12, 495 const float adjust = iClamp((float) bounds.size.x / gap_UI / 11 - 12,
496 -2.0f, 10.0f); /* adapt to width */ 496 -1.0f, 10.0f); /* adapt to width */
497 //printf("%f\n", adjust); fflush(stdout); 497 //printf("%f\n", adjust); fflush(stdout);
498 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2), 498 return iMini(iMax(minWidth, bounds.size.x - gap_UI * (d->pageMargin + adjust) * 2),
499 fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100); 499 fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100);
@@ -4796,64 +4796,67 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) {
4796 }; 4796 };
4797 init_Paint(&ctx.paint); 4797 init_Paint(&ctx.paint);
4798 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); 4798 render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */);
4799 setClip_Paint(&ctx.paint, clipBounds); 4799 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY);
4800 int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); 4800 const iBool isDocEmpty = size_GmDocument(d->doc).y == 0;
4801 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
4802 /* Text markers. */
4803 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; 4801 const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0;
4804 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { 4802 if (!isDocEmpty) {
4805 SDL_Renderer *render = renderer_Window(get_Window()); 4803 setClip_Paint(&ctx.paint, clipBounds);
4806 ctx.firstMarkRect = zero_Rect(); 4804 draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds));
4807 ctx.lastMarkRect = zero_Rect(); 4805 /* Text markers. */
4808 SDL_SetRenderDrawBlendMode(render, 4806 if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) {
4809 isDark_ColorTheme(colorTheme_App()) ? SDL_BLENDMODE_ADD 4807 SDL_Renderer *render = renderer_Window(get_Window());
4810 : SDL_BLENDMODE_BLEND); 4808 ctx.firstMarkRect = zero_Rect();
4811 ctx.viewPos = topLeft_Rect(docBounds); 4809 ctx.lastMarkRect = zero_Rect();
4812 /* Marker starting outside the visible range? */ 4810 SDL_SetRenderDrawBlendMode(render,
4813 if (d->visibleRuns.start) { 4811 isDark_ColorTheme(colorTheme_App()) ? SDL_BLENDMODE_ADD
4814 if (!isEmpty_Range(&d->selectMark) && 4812 : SDL_BLENDMODE_BLEND);
4815 d->selectMark.start < d->visibleRuns.start->text.start && 4813 ctx.viewPos = topLeft_Rect(docBounds);
4816 d->selectMark.end > d->visibleRuns.start->text.start) { 4814 /* Marker starting outside the visible range? */
4817 ctx.inSelectMark = iTrue; 4815 if (d->visibleRuns.start) {
4816 if (!isEmpty_Range(&d->selectMark) &&
4817 d->selectMark.start < d->visibleRuns.start->text.start &&
4818 d->selectMark.end > d->visibleRuns.start->text.start) {
4819 ctx.inSelectMark = iTrue;
4820 }
4821 if (isEmpty_Range(&d->foundMark) &&
4822 d->foundMark.start < d->visibleRuns.start->text.start &&
4823 d->foundMark.end > d->visibleRuns.start->text.start) {
4824 ctx.inFoundMark = iTrue;
4825 }
4818 } 4826 }
4819 if (isEmpty_Range(&d->foundMark) && 4827 render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx);
4820 d->foundMark.start < d->visibleRuns.start->text.start && 4828 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
4821 d->foundMark.end > d->visibleRuns.start->text.start) { 4829 /* Selection range pins. */
4822 ctx.inFoundMark = iTrue; 4830 if (isTouchSelecting) {
4831 drawPin_(&ctx.paint, ctx.firstMarkRect, 0);
4832 drawPin_(&ctx.paint, ctx.lastMarkRect, 1);
4823 } 4833 }
4824 } 4834 }
4825 render_GmDocument(d->doc, vis, drawMark_DrawContext_, &ctx); 4835 drawMedia_DocumentWidget_(d, &ctx.paint);
4826 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); 4836 /* Fill the top and bottom, in case the document is short. */
4827 /* Selection range pins. */ 4837 if (yTop > top_Rect(bounds)) {
4828 if (isTouchSelecting) { 4838 fillRect_Paint(&ctx.paint,
4829 drawPin_(&ctx.paint, ctx.firstMarkRect, 0); 4839 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) },
4830 drawPin_(&ctx.paint, ctx.lastMarkRect, 1); 4840 hasSiteBanner_GmDocument(d->doc) ? tmBannerBackground_ColorId
4831 } 4841 : tmBackground_ColorId);
4832 } 4842 }
4833 drawMedia_DocumentWidget_(d, &ctx.paint); 4843 const int yBottom = yTop + size_GmDocument(d->doc).y + 1;
4834 /* Fill the top and bottom, in case the document is short. */ 4844 if (yBottom < bottom_Rect(bounds)) {
4835 if (yTop > top_Rect(bounds)) { 4845 fillRect_Paint(&ctx.paint,
4836 fillRect_Paint(&ctx.paint, 4846 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom),
4837 (iRect){ bounds.pos, init_I2(bounds.size.x, yTop - top_Rect(bounds)) }, 4847 tmBackground_ColorId);
4838 hasSiteBanner_GmDocument(d->doc) ? tmBannerBackground_ColorId 4848 }
4839 : tmBackground_ColorId); 4849 unsetClip_Paint(&ctx.paint);
4840 } 4850 drawSideElements_DocumentWidget_(d);
4841 const int yBottom = yTop + size_GmDocument(d->doc).y + 1; 4851 if (prefs_App()->hoverLink && d->hoverLink) {
4842 if (yBottom < bottom_Rect(bounds)) { 4852 const int font = uiLabel_FontId;
4843 fillRect_Paint(&ctx.paint, 4853 const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId));
4844 init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), 4854 const iInt2 size = measureRange_Text(font, linkUrl).bounds.size;
4845 tmBackground_ColorId); 4855 const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y),
4846 } 4856 addX_I2(size, 2 * gap_UI) };
4847 unsetClip_Paint(&ctx.paint); 4857 fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId);
4848 drawSideElements_DocumentWidget_(d); 4858 drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl);
4849 if (prefs_App()->hoverLink && d->hoverLink) { 4859 }
4850 const int font = uiLabel_FontId;
4851 const iRangecc linkUrl = range_String(linkUrl_GmDocument(d->doc, d->hoverLink->linkId));
4852 const iInt2 size = measureRange_Text(font, linkUrl).bounds.size;
4853 const iRect linkRect = { addY_I2(bottomLeft_Rect(bounds), -size.y),
4854 addX_I2(size, 2 * gap_UI) };
4855 fillRect_Paint(&ctx.paint, linkRect, tmBackground_ColorId);
4856 drawRange_Text(font, addX_I2(topLeft_Rect(linkRect), gap_UI), tmParagraph_ColorId, linkUrl);
4857 } 4860 }
4858 if (colorTheme_App() == pureWhite_ColorTheme) { 4861 if (colorTheme_App() == pureWhite_ColorTheme) {
4859 drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId); 4862 drawHLine_Paint(&ctx.paint, topLeft_Rect(bounds), width_Rect(bounds), uiSeparator_ColorId);
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index bd6927a6..9f233345 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -188,9 +188,9 @@ enum iInputWidgetFlag {
188 markWords_InputWidgetFlag = iBit(8), 188 markWords_InputWidgetFlag = iBit(8),
189 needUpdateBuffer_InputWidgetFlag = iBit(9), 189 needUpdateBuffer_InputWidgetFlag = iBit(9),
190 enterKeyEnabled_InputWidgetFlag = iBit(10), 190 enterKeyEnabled_InputWidgetFlag = iBit(10),
191 enterKeyInsertsLineFeed_InputWidgetFlag 191 lineBreaksEnabled_InputWidgetFlag= iBit(11),
192 = iBit(11),
193 needBackup_InputWidgetFlag = iBit(12), 192 needBackup_InputWidgetFlag = iBit(12),
193 useReturnKeyBehavior_InputWidgetFlag = iBit(13),
194}; 194};
195 195
196/*----------------------------------------------------------------------------------------------*/ 196/*----------------------------------------------------------------------------------------------*/
@@ -567,6 +567,28 @@ static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) {
567 } 567 }
568} 568}
569 569
570static uint32_t cursorTimer_(uint32_t interval, void *w) {
571 iInputWidget *d = w;
572 if (d->cursorVis > 1) {
573 d->cursorVis--;
574 }
575 else {
576 d->cursorVis ^= 1;
577 }
578 refresh_Widget(w);
579 return interval;
580}
581
582static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, iBool doStart) {
583 if (doStart && !d->timer) {
584 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d);
585 }
586 else if (!doStart && d->timer) {
587 SDL_RemoveTimer(d->timer);
588 d->timer = 0;
589 }
590}
591
570void init_InputWidget(iInputWidget *d, size_t maxLen) { 592void init_InputWidget(iInputWidget *d, size_t maxLen) {
571 iWidget *w = &d->widget; 593 iWidget *w = &d->widget;
572 init_Widget(w); 594 init_Widget(w);
@@ -588,10 +610,11 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) {
588 d->cursor = zero_I2(); 610 d->cursor = zero_I2();
589 d->prevCursor = zero_I2(); 611 d->prevCursor = zero_I2();
590 d->lastUpdateWidth = 0; 612 d->lastUpdateWidth = 0;
591 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; 613 d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag |
592 if (deviceType_App() != desktop_AppDeviceType) { 614 lineBreaksEnabled_InputWidgetFlag | useReturnKeyBehavior_InputWidgetFlag;
593 d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; 615 // if (deviceType_App() != desktop_AppDeviceType) {
594 } 616 // d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag;
617 // }
595 iZap(d->mark); 618 iZap(d->mark);
596 setMaxLen_InputWidget(d, maxLen); 619 setMaxLen_InputWidget(d, maxLen);
597 d->visWrapLines.start = 0; 620 d->visWrapLines.start = 0;
@@ -627,9 +650,7 @@ void deinit_InputWidget(iInputWidget *d) {
627 delete_TextBuf(d->buffered); 650 delete_TextBuf(d->buffered);
628 clearUndo_InputWidget_(d); 651 clearUndo_InputWidget_(d);
629 deinit_Array(&d->undoStack); 652 deinit_Array(&d->undoStack);
630 if (d->timer) { 653 startOrStopCursorTimer_InputWidget_(d, iFalse);
631 SDL_RemoveTimer(d->timer);
632 }
633 deinit_String(&d->srcHint); 654 deinit_String(&d->srcHint);
634 deinit_String(&d->hint); 655 deinit_String(&d->hint);
635 deinit_String(&d->oldText); 656 deinit_String(&d->oldText);
@@ -670,7 +691,6 @@ void setMode_InputWidget(iInputWidget *d, enum iInputMode mode) {
670 d->mode = mode; 691 d->mode = mode;
671} 692}
672 693
673#if 0
674static void restoreDefaultScheme_(iString *url) { 694static void restoreDefaultScheme_(iString *url) {
675 iUrl parts; 695 iUrl parts;
676 init_Url(&parts, url); 696 init_Url(&parts, url);
@@ -685,17 +705,14 @@ static const iString *omitDefaultScheme_(iString *url) {
685 } 705 }
686 return url; 706 return url;
687} 707}
688#endif
689 708
690const iString *text_InputWidget(const iInputWidget *d) { 709const iString *text_InputWidget(const iInputWidget *d) {
691 if (d) { 710 if (d) {
692 iString *text = collect_String(text_InputWidget_(d)); 711 iString *text = collect_String(text_InputWidget_(d));
693#if 0
694 if (d->inFlags & isUrl_InputWidgetFlag) { 712 if (d->inFlags & isUrl_InputWidgetFlag) {
695 /* Add the "gemini" scheme back if one is omitted. */ 713 /* Add the "gemini" scheme back if one is omitted. */
696 restoreDefaultScheme_(text); 714 restoreDefaultScheme_(text);
697 } 715 }
698#endif
699 return text; 716 return text;
700 } 717 }
701 return collectNew_String(); 718 return collectNew_String();
@@ -732,14 +749,18 @@ void setValidator_InputWidget(iInputWidget *d, iInputWidgetValidatorFunc validat
732 d->validatorContext = context; 749 d->validatorContext = context;
733} 750}
734 751
735void setEnterInsertsLF_InputWidget(iInputWidget *d, iBool enterInsertsLF) { 752void setLineBreaksEnabled_InputWidget(iInputWidget *d, iBool lineBreaksEnabled) {
736 iChangeFlags(d->inFlags, enterKeyInsertsLineFeed_InputWidgetFlag, enterInsertsLF); 753 iChangeFlags(d->inFlags, lineBreaksEnabled_InputWidgetFlag, lineBreaksEnabled);
737} 754}
738 755
739void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) { 756void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) {
740 iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled); 757 iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled);
741} 758}
742 759
760void setUseReturnKeyBehavior_InputWidget(iInputWidget *d, iBool useReturnKeyBehavior) {
761 iChangeFlags(d->inFlags, useReturnKeyBehavior_InputWidgetFlag, useReturnKeyBehavior);
762}
763
743void setHint_InputWidget(iInputWidget *d, const char *hintText) { 764void setHint_InputWidget(iInputWidget *d, const char *hintText) {
744 /* Keep original for retranslations. */ 765 /* Keep original for retranslations. */
745 setCStr_String(&d->srcHint, hintText); 766 setCStr_String(&d->srcHint, hintText);
@@ -795,6 +816,22 @@ static void updateBuffered_InputWidget_(iInputWidget *d) {
795 for (int i = visRange.start; i < visRange.end; i++) { 816 for (int i = visRange.start; i < visRange.end; i++) {
796 append_String(visText, &line_InputWidget_(d, i)->text); 817 append_String(visText, &line_InputWidget_(d, i)->text);
797 } 818 }
819 if (d->inFlags & isUrl_InputWidgetFlag) {
820 /* Highlight the host name. */
821 iUrl parts;
822 init_Url(&parts, visText);
823 if (!isEmpty_Range(&parts.host)) {
824 const char *cstr = cstr_String(visText);
825 insertData_Block(&visText->chars,
826 parts.host.end - cstr,
827 restore_ColorEscape,
828 strlen(restore_ColorEscape));
829 insertData_Block(&visText->chars,
830 parts.host.start - cstr,
831 uiTextStrong_ColorEscape,
832 strlen(uiTextStrong_ColorEscape));
833 }
834 }
798 iWrapText wt = wrap_InputWidget_(d, 0); 835 iWrapText wt = wrap_InputWidget_(d, 0);
799 wt.text = range_String(visText); 836 wt.text = range_String(visText);
800 const int fg = uiInputText_ColorId; 837 const int fg = uiInputText_ColorId;
@@ -828,12 +865,10 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
828 punyEncodeUrlHost_String(enc); 865 punyEncodeUrlHost_String(enc);
829 text = enc; 866 text = enc;
830 } 867 }
831#if 0
832 /* Omit the default (Gemini) scheme if there isn't much space. */ 868 /* Omit the default (Gemini) scheme if there isn't much space. */
833 if (isNarrow_Root(as_Widget(d)->root)) { 869 if (isNarrow_Root(as_Widget(d)->root)) {
834 text = omitDefaultScheme_(collect_String(copy_String(text))); 870 text = omitDefaultScheme_(collect_String(copy_String(text)));
835 } 871 }
836#endif
837 } 872 }
838 clearUndo_InputWidget_(d); 873 clearUndo_InputWidget_(d);
839 iString *nfcText = collect_String(copy_String(text)); 874 iString *nfcText = collect_String(copy_String(text));
@@ -848,10 +883,6 @@ void setText_InputWidget(iInputWidget *d, const iString *text) {
848 if (!isFocused_Widget(d)) { 883 if (!isFocused_Widget(d)) {
849 iZap(d->mark); 884 iZap(d->mark);
850 } 885 }
851// else {
852// d->cursor.y = iMin(d->cursor.y, (int) size_Array(&d->lines) - 1);
853// d->cursor.x = iMin(d->cursor.x, size_String(&cursorLine_InputWidget_(d)->text));
854// }
855 if (!isFocused_Widget(d)) { 886 if (!isFocused_Widget(d)) {
856 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 887 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
857 } 888 }
@@ -866,18 +897,6 @@ void setTextCStr_InputWidget(iInputWidget *d, const char *cstr) {
866 delete_String(str); 897 delete_String(str);
867} 898}
868 899
869static uint32_t cursorTimer_(uint32_t interval, void *w) {
870 iInputWidget *d = w;
871 if (d->cursorVis > 1) {
872 d->cursorVis--;
873 }
874 else {
875 d->cursorVis ^= 1;
876 }
877 refresh_Widget(w);
878 return interval;
879}
880
881static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) { 900static size_t cursorToIndex_InputWidget_(const iInputWidget *d, iInt2 pos) {
882 if (pos.y < 0) { 901 if (pos.y < 0) {
883 return 0; 902 return 0;
@@ -930,7 +949,7 @@ void begin_InputWidget(iInputWidget *d) {
930 setFlags_Widget(w, selected_WidgetFlag, iTrue); 949 setFlags_Widget(w, selected_WidgetFlag, iTrue);
931 showCursor_InputWidget_(d); 950 showCursor_InputWidget_(d);
932 refresh_Widget(w); 951 refresh_Widget(w);
933 d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); 952 startOrStopCursorTimer_InputWidget_(d, iTrue);
934 d->inFlags &= ~enterPressed_InputWidgetFlag; 953 d->inFlags &= ~enterPressed_InputWidgetFlag;
935 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) { 954 if (d->inFlags & selectAllOnFocus_InputWidgetFlag) {
936 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; 955 d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end };
@@ -955,8 +974,7 @@ void end_InputWidget(iInputWidget *d, iBool accept) {
955 splitToLines_(&d->oldText, &d->lines); 974 splitToLines_(&d->oldText, &d->lines);
956 } 975 }
957 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 976 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
958 SDL_RemoveTimer(d->timer); 977 startOrStopCursorTimer_InputWidget_(d, iFalse);
959 d->timer = 0;
960 SDL_StopTextInput(); 978 SDL_StopTextInput();
961 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); 979 setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse);
962 const char *id = cstr_String(id_Widget(as_Widget(d))); 980 const char *id = cstr_String(id_Widget(as_Widget(d)));
@@ -1383,17 +1401,29 @@ static iBool isArrowUpDownConsumed_InputWidget_(const iInputWidget *d) {
1383 return d->maxWrapLines > 1; 1401 return d->maxWrapLines > 1;
1384} 1402}
1385 1403
1404static iBool checkLineBreakMods_InputWidget_(const iInputWidget *d, int mods) {
1405 if (d->inFlags & useReturnKeyBehavior_InputWidgetFlag) {
1406 return mods == lineBreakKeyMod_ReturnKeyBehavior(prefs_App()->returnKey);
1407 }
1408 return mods == 0;
1409}
1410
1411static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) {
1412 if (d->inFlags & useReturnKeyBehavior_InputWidgetFlag) {
1413 return mods == acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey);
1414 }
1415 return mods == 0;
1416}
1417
1386static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { 1418static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1387 iWidget *w = as_Widget(d); 1419 iWidget *w = as_Widget(d);
1388 /* Resize according to width immediately. */ 1420 /* Resize according to width immediately. */
1389 if (d->lastUpdateWidth != w->rect.size.x) { 1421 if (d->lastUpdateWidth != w->rect.size.x) {
1390 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1422 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1391#if 0
1392 if (d->inFlags & isUrl_InputWidgetFlag) { 1423 if (d->inFlags & isUrl_InputWidgetFlag) {
1393 /* Restore/omit the default scheme if necessary. */ 1424 /* Restore/omit the default scheme if necessary. */
1394 setText_InputWidget(d, text_InputWidget(d)); 1425 setText_InputWidget(d, text_InputWidget(d));
1395 } 1426 }
1396#endif
1397 updateAllLinesAndResizeHeight_InputWidget_(d); 1427 updateAllLinesAndResizeHeight_InputWidget_(d);
1398 d->lastUpdateWidth = w->rect.size.x; 1428 d->lastUpdateWidth = w->rect.size.x;
1399 } 1429 }
@@ -1401,6 +1431,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1401 begin_InputWidget(d); 1431 begin_InputWidget(d);
1402 return iFalse; 1432 return iFalse;
1403 } 1433 }
1434 else if (isEditing_InputWidget_(d) && (isCommand_UserEvent(ev, "window.focus.lost") ||
1435 isCommand_UserEvent(ev, "window.focus.gained"))) {
1436 startOrStopCursorTimer_InputWidget_(d, isCommand_UserEvent(ev, "window.focus.gained"));
1437 d->cursorVis = 1;
1438 refresh_Widget(d);
1439 return iFalse;
1440 }
1404 else if (isCommand_UserEvent(ev, "keyroot.changed")) { 1441 else if (isCommand_UserEvent(ev, "keyroot.changed")) {
1405 d->inFlags |= needUpdateBuffer_InputWidgetFlag; 1442 d->inFlags |= needUpdateBuffer_InputWidgetFlag;
1406 } 1443 }
@@ -1614,10 +1651,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1614 return iTrue; 1651 return iTrue;
1615 case SDLK_RETURN: 1652 case SDLK_RETURN:
1616 case SDLK_KP_ENTER: 1653 case SDLK_KP_ENTER:
1617 if (~d->inFlags & isSensitive_InputWidgetFlag && d->maxLen == 0) { 1654 if (~d->inFlags & isSensitive_InputWidgetFlag &&
1618 if (mods == lineBreakKeyMod_ReturnKeyBehavior(prefs_App()->returnKey) || 1655 ~d->inFlags & isUrl_InputWidgetFlag &&
1619 (~d->inFlags & isUrl_InputWidgetFlag && 1656 d->inFlags & lineBreaksEnabled_InputWidgetFlag && d->maxLen == 0) {
1620 d->inFlags & enterKeyInsertsLineFeed_InputWidgetFlag)) { 1657 if (checkLineBreakMods_InputWidget_(d, mods)) {
1621 pushUndo_InputWidget_(d); 1658 pushUndo_InputWidget_(d);
1622 deleteMarked_InputWidget_(d); 1659 deleteMarked_InputWidget_(d);
1623 insertChar_InputWidget_(d, '\n'); 1660 insertChar_InputWidget_(d, '\n');
@@ -1626,7 +1663,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1626 } 1663 }
1627 } 1664 }
1628 if (d->inFlags & enterKeyEnabled_InputWidgetFlag && 1665 if (d->inFlags & enterKeyEnabled_InputWidgetFlag &&
1629 mods == acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey)) { 1666 (checkAcceptMods_InputWidget_(d, mods) ||
1667 (~d->inFlags & lineBreaksEnabled_InputWidgetFlag))) {
1630 d->inFlags |= enterPressed_InputWidgetFlag; 1668 d->inFlags |= enterPressed_InputWidgetFlag;
1631 setFocus_Widget(NULL); 1669 setFocus_Widget(NULL);
1632 return iTrue; 1670 return iTrue;
@@ -1728,6 +1766,9 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1728 case SDLK_a: 1766 case SDLK_a:
1729#if defined (iPlatformApple) 1767#if defined (iPlatformApple)
1730 if (mods == KMOD_PRIMARY) { 1768 if (mods == KMOD_PRIMARY) {
1769#else
1770 if (mods == (KMOD_PRIMARY | KMOD_SHIFT)) {
1771#endif
1731 selectAll_InputWidget(d); 1772 selectAll_InputWidget(d);
1732 d->mark.start = 0; 1773 d->mark.start = 0;
1733 d->mark.end = cursorToIndex_InputWidget_(d, curMax); 1774 d->mark.end = cursorToIndex_InputWidget_(d, curMax);
@@ -1736,7 +1777,6 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) {
1736 refresh_Widget(w); 1777 refresh_Widget(w);
1737 return iTrue; 1778 return iTrue;
1738 } 1779 }
1739#endif
1740 /* fall through for Emacs-style Home/End */ 1780 /* fall through for Emacs-style Home/End */
1741 case SDLK_e: 1781 case SDLK_e:
1742 if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) { 1782 if (mods == KMOD_CTRL || mods == (KMOD_CTRL | KMOD_SHIFT)) {
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h
index a94291ed..0d327ca6 100644
--- a/src/ui/inputwidget.h
+++ b/src/ui/inputwidget.h
@@ -50,8 +50,9 @@ void setFont_InputWidget (iInputWidget *, int fontId);
50void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ 50void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */
51void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines); 51void setLineLimits_InputWidget (iInputWidget *, int minLines, int maxLines);
52void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); 52void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context);
53void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); 53void setLineBreaksEnabled_InputWidget(iInputWidget *, iBool lineBreaksEnabled);
54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); 54void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled);
55void setUseReturnKeyBehavior_InputWidget(iInputWidget *, iBool useReturnKeyBehavior);
55void setBackupFileName_InputWidget (iInputWidget *, const char *fileName); 56void setBackupFileName_InputWidget (iInputWidget *, const char *fileName);
56void begin_InputWidget (iInputWidget *); 57void begin_InputWidget (iInputWidget *);
57void end_InputWidget (iInputWidget *, iBool accept); 58void end_InputWidget (iInputWidget *, iBool accept);
diff --git a/src/ui/root.c b/src/ui/root.c
index 9d92c44e..a8b9f998 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -358,9 +358,9 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) {
358 return iTrue; 358 return iTrue;
359 } 359 }
360 else if (equal_Command(cmd, "window.focus.lost")) { 360 else if (equal_Command(cmd, "window.focus.lost")) {
361#if !defined (iPlatformMobile) /* apps don't share input focus on mobile */ 361//#if !defined (iPlatformMobile) /* apps don't share input focus on mobile */
362 setFocus_Widget(NULL); 362// setFocus_Widget(NULL);
363#endif 363//#endif
364 setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId); 364 setTextColor_LabelWidget(findWidget_App("winbar.app"), uiAnnotation_ColorId);
365 setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId); 365 setTextColor_LabelWidget(findWidget_App("winbar.title"), uiAnnotation_ColorId);
366 return iFalse; 366 return iFalse;
@@ -1077,6 +1077,7 @@ void createUserInterface_Root(iRoot *d) {
1077 setSelectAllOnFocus_InputWidget(url, iTrue); 1077 setSelectAllOnFocus_InputWidget(url, iTrue);
1078 setId_Widget(as_Widget(url), "url"); 1078 setId_Widget(as_Widget(url), "url");
1079 setLineLimits_InputWidget(url, 1, 1); /* just one line while not focused */ 1079 setLineLimits_InputWidget(url, 1, 1); /* just one line while not focused */
1080 setLineBreaksEnabled_InputWidget(url, iFalse);
1080 setUrlContent_InputWidget(url, iTrue); 1081 setUrlContent_InputWidget(url, iTrue);
1081 setNotifyEdits_InputWidget(url, iTrue); 1082 setNotifyEdits_InputWidget(url, iTrue);
1082 setTextCStr_InputWidget(url, "gemini://"); 1083 setTextCStr_InputWidget(url, "gemini://");
@@ -1272,7 +1273,7 @@ void createUserInterface_Root(iRoot *d) {
1272 setHint_InputWidget(input, "${hint.findtext}"); 1273 setHint_InputWidget(input, "${hint.findtext}");
1273 setSelectAllOnFocus_InputWidget(input, iTrue); 1274 setSelectAllOnFocus_InputWidget(input, iTrue);
1274 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ 1275 setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */
1275 setEnterInsertsLF_InputWidget(input, iFalse); 1276 setLineBreaksEnabled_InputWidget(input, iFalse);
1276 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), 1277 setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag),
1277 "find.input"); 1278 "find.input");
1278 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); 1279 addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next")));
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c
index 7bfa73bd..57b6b6b7 100644
--- a/src/ui/uploadwidget.c
+++ b/src/ui/uploadwidget.c
@@ -116,8 +116,8 @@ void init_UploadWidget(iUploadWidget *d) {
116 setId_Widget(as_Widget(d->input), "upload.text"); 116 setId_Widget(as_Widget(d->input), "upload.text");
117 setFont_InputWidget(d->input, monospace_FontId); 117 setFont_InputWidget(d->input, monospace_FontId);
118 setLineLimits_InputWidget(d->input, 7, 20); 118 setLineLimits_InputWidget(d->input, 7, 20);
119 setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */
119 setHint_InputWidget(d->input, "${hint.upload.text}"); 120 setHint_InputWidget(d->input, "${hint.upload.text}");
120 setEnterInsertsLF_InputWidget(d->input, iTrue);
121 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); 121 setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1));
122 addChild_Widget(page, iClob(d->input)); 122 addChild_Widget(page, iClob(d->input));
123 appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); 123 appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0);
diff --git a/src/ui/visbuf.c b/src/ui/visbuf.c
index 88a5fd60..503d0a2f 100644
--- a/src/ui/visbuf.c
+++ b/src/ui/visbuf.c
@@ -58,18 +58,14 @@ void alloc_VisBuf(iVisBuf *d, const iInt2 size, int granularity) {
58 if (tex->texture) { 58 if (tex->texture) {
59 SDL_DestroyTexture(tex->texture); 59 SDL_DestroyTexture(tex->texture);
60 } 60 }
61 SDL_Renderer *rend = renderer_Window(get_Window());
61 tex->texture = 62 tex->texture =
62 SDL_CreateTexture(renderer_Window(get_Window()), 63 SDL_CreateTexture(rend,
63 SDL_PIXELFORMAT_RGBA8888, 64 SDL_PIXELFORMAT_RGBA8888,
64 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, 65 SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET,
65 texSize.x, 66 texSize.x,
66 texSize.y); 67 texSize.y);
67 SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE); 68 SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_NONE);
68// tex->origin = i * texSize.y;
69// iZap(tex->validRange);
70// if (d->invalidUserData) {
71// d->invalidUserData(i, d->buffers[i].user);
72// }
73 } 69 }
74 invalidate_VisBuf(d); 70 invalidate_VisBuf(d);
75 } 71 }