diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/color.c | 27 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 58 | ||||
-rw-r--r-- | src/ui/indicatorwidget.c | 66 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 20 | ||||
-rw-r--r-- | src/ui/mobile.c | 4 | ||||
-rw-r--r-- | src/ui/root.c | 31 | ||||
-rw-r--r-- | src/ui/root.h | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 5 | ||||
-rw-r--r-- | src/ui/text.c | 42 | ||||
-rw-r--r-- | src/ui/text.h | 2 | ||||
-rw-r--r-- | src/ui/touch.c | 5 | ||||
-rw-r--r-- | src/ui/window.c | 3 |
12 files changed, 166 insertions, 99 deletions
diff --git a/src/ui/color.c b/src/ui/color.c index 3c2f0339..824342ae 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -868,23 +868,16 @@ void ansiColors_Color(iRangecc escapeSequence, int fgDefault, int bgDefault, | |||
868 | case 97: | 868 | case 97: |
869 | fg = ansi8BitColors_[8 + arg - 90]; | 869 | fg = ansi8BitColors_[8 + arg - 90]; |
870 | break; | 870 | break; |
871 | } | 871 | case 100: |
872 | } | 872 | case 101: |
873 | /* Ensure legibility if only the foreground color is set. */ | 873 | case 102: |
874 | if (fg.a) { | 874 | case 103: |
875 | const iHSLColor themeBg = get_HSLColor(tmBackground_ColorId); | 875 | case 104: |
876 | const float bgLuminance = luma_Color(get_Color(tmBackground_ColorId)); | 876 | case 105: |
877 | if (bgLuminance > 0.4f) { | 877 | case 106: |
878 | float dim = (bgLuminance - 0.4f); | 878 | case 107: |
879 | fg.r *= 0.5f * dim; | 879 | bg = ansi8BitColors_[8 + arg - 100]; |
880 | fg.g *= 0.5f * dim; | 880 | break; |
881 | fg.b *= 0.5f * dim; | ||
882 | } | ||
883 | if (themeBg.sat > 0.15f && themeBg.lum >= 0.5f) { | ||
884 | iHSLColor fgHsl = hsl_Color(fg); | ||
885 | fgHsl.hue = themeBg.hue; | ||
886 | fgHsl.lum = themeBg.lum * 0.5f; | ||
887 | fg = rgb_HSLColor(fgHsl); | ||
888 | } | 881 | } |
889 | } | 882 | } |
890 | if (fg.a && fg_out) { | 883 | if (fg.a && fg_out) { |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6a535882..25559890 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -36,6 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | #include "gmdocument.h" | 36 | #include "gmdocument.h" |
37 | #include "gmrequest.h" | 37 | #include "gmrequest.h" |
38 | #include "gmutil.h" | 38 | #include "gmutil.h" |
39 | #include "gopher.h" | ||
39 | #include "history.h" | 40 | #include "history.h" |
40 | #include "indicatorwidget.h" | 41 | #include "indicatorwidget.h" |
41 | #include "inputwidget.h" | 42 | #include "inputwidget.h" |
@@ -921,6 +922,7 @@ static void documentRunsInvalidated_DocumentView_(iDocumentView *d) { | |||
921 | d->hoverPre = NULL; | 922 | d->hoverPre = NULL; |
922 | d->hoverAltPre = NULL; | 923 | d->hoverAltPre = NULL; |
923 | d->hoverLink = NULL; | 924 | d->hoverLink = NULL; |
925 | clear_PtrArray(&d->visibleMedia); | ||
924 | iZap(d->visibleRuns); | 926 | iZap(d->visibleRuns); |
925 | iZap(d->renderRuns); | 927 | iZap(d->renderRuns); |
926 | } | 928 | } |
@@ -2756,6 +2758,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
2756 | } | 2758 | } |
2757 | d->flags |= drawDownloadCounter_DocumentWidgetFlag; | 2759 | d->flags |= drawDownloadCounter_DocumentWidgetFlag; |
2758 | clear_PtrSet(d->view.invalidRuns); | 2760 | clear_PtrSet(d->view.invalidRuns); |
2761 | documentRunsInvalidated_DocumentWidget_(d); | ||
2759 | deinit_String(&str); | 2762 | deinit_String(&str); |
2760 | return; | 2763 | return; |
2761 | } | 2764 | } |
@@ -2899,10 +2902,14 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { | |||
2899 | add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str); | 2902 | add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str); |
2900 | } | 2903 | } |
2901 | /* Warnings related to page contents. */ | 2904 | /* Warnings related to page contents. */ |
2902 | const int dismissed = | 2905 | int dismissed = |
2903 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), | 2906 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), |
2904 | dismissWarnings_SiteSpecKey) | | 2907 | dismissWarnings_SiteSpecKey) | |
2905 | (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); | 2908 | (!prefs_App()->warnAboutMissingGlyphs ? missingGlyphs_GmDocumentWarning : 0); |
2909 | /* File pages don't allow dismissing warnings, so skip it. */ | ||
2910 | if (equalCase_Rangecc(urlScheme_String(d->mod.url), "file")) { | ||
2911 | dismissed |= ansiEscapes_GmDocumentWarning; | ||
2912 | } | ||
2906 | const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; | 2913 | const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; |
2907 | if (warnings & missingGlyphs_GmDocumentWarning) { | 2914 | if (warnings & missingGlyphs_GmDocumentWarning) { |
2908 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); | 2915 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); |
@@ -4068,14 +4075,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4068 | return iTrue; | 4075 | return iTrue; |
4069 | } | 4076 | } |
4070 | else if (equal_Command(cmd, "valueinput.cancelled") && | 4077 | else if (equal_Command(cmd, "valueinput.cancelled") && |
4071 | equal_Rangecc(range_Command(cmd, "id"), "document.input.submit") && document_App() == d) { | 4078 | equal_Rangecc(range_Command(cmd, "id"), "!document.input.submit") && document_App() == d) { |
4072 | postCommand_Root(get_Root(), "navigate.back"); | 4079 | postCommand_Root(get_Root(), "navigate.back"); |
4073 | return iTrue; | 4080 | return iTrue; |
4074 | } | 4081 | } |
4075 | else if (equalWidget_Command(cmd, w, "document.request.updated") && | 4082 | else if (equalWidget_Command(cmd, w, "document.request.updated") && |
4076 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { | 4083 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { |
4077 | // set_Block(&d->sourceContent, &lockResponse_GmRequest(d->request)->body); | ||
4078 | // unlockResponse_GmRequest(d->request); | ||
4079 | if (document_App() == d) { | 4084 | if (document_App() == d) { |
4080 | updateFetchProgress_DocumentWidget_(d); | 4085 | updateFetchProgress_DocumentWidget_(d); |
4081 | } | 4086 | } |
@@ -4301,6 +4306,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4301 | else if (equal_Command(cmd, "navigate.parent") && document_App() == d) { | 4306 | else if (equal_Command(cmd, "navigate.parent") && document_App() == d) { |
4302 | iUrl parts; | 4307 | iUrl parts; |
4303 | init_Url(&parts, d->mod.url); | 4308 | init_Url(&parts, d->mod.url); |
4309 | if (endsWith_Rangecc(parts.path, "/index.gmi")) { | ||
4310 | parts.path.end -= 9; /* This is the default index page. */ | ||
4311 | } | ||
4304 | /* Remove the last path segment. */ | 4312 | /* Remove the last path segment. */ |
4305 | if (size_Range(&parts.path) > 1) { | 4313 | if (size_Range(&parts.path) > 1) { |
4306 | if (parts.path.end[-1] == '/') { | 4314 | if (parts.path.end[-1] == '/') { |
@@ -4310,14 +4318,42 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4310 | if (parts.path.end[-1] == '/') break; | 4318 | if (parts.path.end[-1] == '/') break; |
4311 | parts.path.end--; | 4319 | parts.path.end--; |
4312 | } | 4320 | } |
4313 | postCommandf_Root(w->root, | 4321 | iString *parentUrl = collectNewRange_String((iRangecc){ constBegin_String(d->mod.url), |
4314 | "open url:%s", | 4322 | parts.path.end }); |
4315 | cstr_Rangecc((iRangecc){ constBegin_String(d->mod.url), parts.path.end })); | 4323 | /* Always go to a gophermap. */ |
4324 | setUrlItemType_Gopher(parentUrl, '1'); | ||
4325 | /* Hierarchical navigation doesn't make sense with Titan. */ | ||
4326 | if (startsWith_String(parentUrl, "titan://")) { | ||
4327 | /* We have no way of knowing if the corresponding URL is valid for Gemini, | ||
4328 | but let's try anyway. */ | ||
4329 | set_String(parentUrl, withScheme_String(parentUrl, "gemini")); | ||
4330 | stripUrlPort_String(parentUrl); | ||
4331 | } | ||
4332 | if (!cmpCase_String(parentUrl, "about:")) { | ||
4333 | setCStr_String(parentUrl, "about:about"); | ||
4334 | } | ||
4335 | postCommandf_Root(w->root, "open url:%s", cstr_String(parentUrl)); | ||
4316 | } | 4336 | } |
4317 | return iTrue; | 4337 | return iTrue; |
4318 | } | 4338 | } |
4319 | else if (equal_Command(cmd, "navigate.root") && document_App() == d) { | 4339 | else if (equal_Command(cmd, "navigate.root") && document_App() == d) { |
4320 | postCommandf_Root(w->root, "open url:%s/", cstr_Rangecc(urlRoot_String(d->mod.url))); | 4340 | iString *rootUrl = collectNewRange_String(urlRoot_String(d->mod.url)); |
4341 | /* Always go to a gophermap. */ | ||
4342 | setUrlItemType_Gopher(rootUrl, '1'); | ||
4343 | /* Hierarchical navigation doesn't make sense with Titan. */ | ||
4344 | if (startsWith_String(rootUrl, "titan://")) { | ||
4345 | /* We have no way of knowing if the corresponding URL is valid for Gemini, | ||
4346 | but let's try anyway. */ | ||
4347 | set_String(rootUrl, withScheme_String(rootUrl, "gemini")); | ||
4348 | stripUrlPort_String(rootUrl); | ||
4349 | } | ||
4350 | if (!cmpCase_String(rootUrl, "about:")) { | ||
4351 | setCStr_String(rootUrl, "about:about"); | ||
4352 | } | ||
4353 | else { | ||
4354 | appendCStr_String(rootUrl, "/"); | ||
4355 | } | ||
4356 | postCommandf_Root(w->root, "open url:%s", cstr_String(rootUrl)); | ||
4321 | return iTrue; | 4357 | return iTrue; |
4322 | } | 4358 | } |
4323 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { | 4359 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { |
@@ -4340,6 +4376,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4340 | return iTrue; | 4376 | return iTrue; |
4341 | } | 4377 | } |
4342 | else if (equal_Command(cmd, "scroll.top") && document_App() == d) { | 4378 | else if (equal_Command(cmd, "scroll.top") && document_App() == d) { |
4379 | if (argLabel_Command(cmd, "smooth")) { | ||
4380 | stopWidgetMomentum_Touch(w); | ||
4381 | smoothScroll_DocumentView_(&d->view, -pos_SmoothScroll(&d->view.scrollY), 500); | ||
4382 | d->view.scrollY.flags |= muchSofter_AnimFlag; | ||
4383 | return iTrue; | ||
4384 | } | ||
4343 | init_Anim(&d->view.scrollY.pos, 0); | 4385 | init_Anim(&d->view.scrollY.pos, 0); |
4344 | invalidate_VisBuf(d->view.visBuf); | 4386 | invalidate_VisBuf(d->view.visBuf); |
4345 | clampScroll_DocumentView_(&d->view); | 4387 | clampScroll_DocumentView_(&d->view); |
diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c index bc0bd0fa..e16550ff 100644 --- a/src/ui/indicatorwidget.c +++ b/src/ui/indicatorwidget.c | |||
@@ -28,32 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | 28 | ||
29 | #include <SDL_timer.h> | 29 | #include <SDL_timer.h> |
30 | 30 | ||
31 | static int timerId_; /* common timer for all indicators */ | 31 | struct Impl_IndicatorWidget { |
32 | static int animCount_; /* number of animating indicators */ | ||
33 | |||
34 | static uint32_t postRefresh_(uint32_t interval, void *context) { | ||
35 | iUnused(context); | ||
36 | postRefresh_App(); | ||
37 | return interval; | ||
38 | } | ||
39 | |||
40 | static void startTimer_(void) { | ||
41 | animCount_++; | ||
42 | if (!timerId_) { | ||
43 | timerId_ = SDL_AddTimer(1000 / 60, postRefresh_, NULL); | ||
44 | } | ||
45 | } | ||
46 | |||
47 | static void stopTimer_(void) { | ||
48 | iAssert(animCount_ > 0); | ||
49 | if (--animCount_ == 0) { | ||
50 | iAssert(timerId_); | ||
51 | SDL_RemoveTimer(timerId_); | ||
52 | timerId_ = 0; | ||
53 | } | ||
54 | } | ||
55 | |||
56 | struct Impl_IndicatorWidget{ | ||
57 | iWidget widget; | 32 | iWidget widget; |
58 | iAnim pos; | 33 | iAnim pos; |
59 | }; | 34 | }; |
@@ -64,6 +39,14 @@ iLocalDef iBool isActive_IndicatorWidget_(const iIndicatorWidget *d) { | |||
64 | return isSelected_Widget(d); | 39 | return isSelected_Widget(d); |
65 | } | 40 | } |
66 | 41 | ||
42 | static void animate_IndicatorWidget_(void *ptr) { | ||
43 | iIndicatorWidget *d = ptr; | ||
44 | if (!isFinished_Anim(&d->pos)) { | ||
45 | addTickerRoot_App(animate_IndicatorWidget_, d->widget.root, ptr); | ||
46 | } | ||
47 | postRefresh_App(); | ||
48 | } | ||
49 | |||
67 | static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) { | 50 | static void setActive_IndicatorWidget_(iIndicatorWidget *d, iBool set) { |
68 | setFlags_Widget(as_Widget(d), selected_WidgetFlag, set); | 51 | setFlags_Widget(as_Widget(d), selected_WidgetFlag, set); |
69 | } | 52 | } |
@@ -75,22 +58,8 @@ void init_IndicatorWidget(iIndicatorWidget *d) { | |||
75 | setFlags_Widget(w, unhittable_WidgetFlag, iTrue); | 58 | setFlags_Widget(w, unhittable_WidgetFlag, iTrue); |
76 | } | 59 | } |
77 | 60 | ||
78 | static void startTimer_IndicatorWidget_(iIndicatorWidget *d) { | ||
79 | if (!isActive_IndicatorWidget_(d)) { | ||
80 | startTimer_(); | ||
81 | setActive_IndicatorWidget_(d, iTrue); | ||
82 | } | ||
83 | } | ||
84 | |||
85 | static void stopTimer_IndicatorWidget_(iIndicatorWidget *d) { | ||
86 | if (isActive_IndicatorWidget_(d)) { | ||
87 | stopTimer_(); | ||
88 | setActive_IndicatorWidget_(d, iFalse); | ||
89 | } | ||
90 | } | ||
91 | |||
92 | void deinit_IndicatorWidget(iIndicatorWidget *d) { | 61 | void deinit_IndicatorWidget(iIndicatorWidget *d) { |
93 | stopTimer_IndicatorWidget_(d); | 62 | removeTicker_App(animate_IndicatorWidget_, d); |
94 | } | 63 | } |
95 | 64 | ||
96 | static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) { | 65 | static iBool isCompleted_IndicatorWidget_(const iIndicatorWidget *d) { |
@@ -116,12 +85,7 @@ void draw_IndicatorWidget_(const iIndicatorWidget *d) { | |||
116 | 85 | ||
117 | iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { | 86 | iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { |
118 | iWidget *w = &d->widget; | 87 | iWidget *w = &d->widget; |
119 | if (ev->type == SDL_USEREVENT && ev->user.code == refresh_UserEventCode) { | 88 | if (isCommand_SDLEvent(ev)) { |
120 | if (isFinished_Anim(&d->pos)) { | ||
121 | stopTimer_IndicatorWidget_(d); | ||
122 | } | ||
123 | } | ||
124 | else if (isCommand_SDLEvent(ev)) { | ||
125 | const char *cmd = command_UserEvent(ev); | 89 | const char *cmd = command_UserEvent(ev); |
126 | if (startsWith_CStr(cmd, "document.request.")) { | 90 | if (startsWith_CStr(cmd, "document.request.")) { |
127 | if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) { | 91 | if (pointerLabel_Command(cmd, "doc") == parent_Widget(w)) { |
@@ -130,23 +94,23 @@ iBool processEvent_IndicatorWidget_(iIndicatorWidget *d, const SDL_Event *ev) { | |||
130 | setValue_Anim(&d->pos, 0, 0); | 94 | setValue_Anim(&d->pos, 0, 0); |
131 | setValue_Anim(&d->pos, 0.75f, 4000); | 95 | setValue_Anim(&d->pos, 0.75f, 4000); |
132 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue); | 96 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iTrue); |
133 | startTimer_IndicatorWidget_(d); | 97 | animate_IndicatorWidget_(d); |
134 | } | 98 | } |
135 | else if (equal_Command(cmd, "finished")) { | 99 | else if (equal_Command(cmd, "finished")) { |
136 | if (value_Anim(&d->pos) > 0.01f) { | 100 | if (value_Anim(&d->pos) > 0.01f) { |
137 | setValue_Anim(&d->pos, 1.0f, 250); | 101 | setValue_Anim(&d->pos, 1.0f, 250); |
138 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse); | 102 | setFlags_Anim(&d->pos, easeOut_AnimFlag, iFalse); |
139 | startTimer_IndicatorWidget_(d); | 103 | animate_IndicatorWidget_(d); |
140 | } | 104 | } |
141 | else { | 105 | else { |
142 | setValue_Anim(&d->pos, 0, 0); | 106 | setValue_Anim(&d->pos, 0, 0); |
143 | stopTimer_IndicatorWidget_(d); | 107 | animate_IndicatorWidget_(d); |
144 | refresh_Widget(d); | 108 | refresh_Widget(d); |
145 | } | 109 | } |
146 | } | 110 | } |
147 | else if (equal_Command(cmd, "cancelled")) { | 111 | else if (equal_Command(cmd, "cancelled")) { |
148 | setValue_Anim(&d->pos, 0, 0); | 112 | setValue_Anim(&d->pos, 0, 0); |
149 | stopTimer_IndicatorWidget_(d); | 113 | animate_IndicatorWidget_(d); |
150 | refresh_Widget(d); | 114 | refresh_Widget(d); |
151 | } | 115 | } |
152 | } | 116 | } |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 9261da0c..aa55f3f0 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -742,15 +742,17 @@ static void startOrStopCursorTimer_InputWidget_(iInputWidget *d, int doStart) { | |||
742 | #else /* using a system-provided text control */ | 742 | #else /* using a system-provided text control */ |
743 | 743 | ||
744 | static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { | 744 | static void updateAllLinesAndResizeHeight_InputWidget_(iInputWidget *d) { |
745 | /* Rewrap the buffered text and resize accordingly. */ | 745 | if (width_Widget(d) >= minWidth_InputWidget_) { |
746 | iWrapText wt = wrap_InputWidget_(d, 0); | 746 | /* Rewrap the buffered text and resize accordingly. */ |
747 | /* TODO: Set max lines limit for WrapText. */ | 747 | iWrapText wt = wrap_InputWidget_(d, 0); |
748 | const int height = measure_WrapText(&wt, d->font).bounds.size.y; | 748 | /* TODO: Set max lines limit for WrapText. */ |
749 | /* We use this to store the number wrapped lines for determining widget height. */ | 749 | const int height = measure_WrapText(&wt, d->font).bounds.size.y; |
750 | d->visWrapLines.start = 0; | 750 | /* We use this to store the number wrapped lines for determining widget height. */ |
751 | d->visWrapLines.end = iMax(d->minWrapLines, | 751 | d->visWrapLines.start = 0; |
752 | iMin(d->maxWrapLines, height / lineHeight_Text(d->font))); | 752 | d->visWrapLines.end = iMax(d->minWrapLines, |
753 | updateMetrics_InputWidget_(d); | 753 | iMin(d->maxWrapLines, height / lineHeight_Text(d->font))); |
754 | updateMetrics_InputWidget_(d); | ||
755 | } | ||
754 | } | 756 | } |
755 | 757 | ||
756 | #endif | 758 | #endif |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index cf955423..aefeebc6 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -43,7 +43,7 @@ const iToolbarActionSpec toolbarActions_Mobile[max_ToolbarAction] = { | |||
43 | { home_Icon, "${menu.home}", "navigate.home" }, | 43 | { home_Icon, "${menu.home}", "navigate.home" }, |
44 | { upArrow_Icon, "${menu.parent}", "navigate.parent" }, | 44 | { upArrow_Icon, "${menu.parent}", "navigate.parent" }, |
45 | { reload_Icon, "${menu.reload}", "navigate.reload" }, | 45 | { reload_Icon, "${menu.reload}", "navigate.reload" }, |
46 | { openTab_Icon, "${menu.newtab}", "tabs.new" }, | 46 | { add_Icon, "${menu.newtab}", "tabs.new" }, |
47 | { close_Icon, "${menu.closetab}", "tabs.close" }, | 47 | { close_Icon, "${menu.closetab}", "tabs.close" }, |
48 | { bookmark_Icon, "${menu.page.bookmark}", "bookmark.add" }, | 48 | { bookmark_Icon, "${menu.page.bookmark}", "bookmark.add" }, |
49 | { globe_Icon, "${menu.page.translate}", "document.translate" }, | 49 | { globe_Icon, "${menu.page.translate}", "document.translate" }, |
@@ -940,7 +940,7 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
940 | } | 940 | } |
941 | const int maxOffset = isHorizPanel ? width_Widget(sheet) | 941 | const int maxOffset = isHorizPanel ? width_Widget(sheet) |
942 | : isPortraitPhone_App() ? height_Widget(sheet) | 942 | : isPortraitPhone_App() ? height_Widget(sheet) |
943 | : (12 * gap_UI); | 943 | : (6 * gap_UI); |
944 | if (isIncoming) { | 944 | if (isIncoming) { |
945 | setVisualOffset_Widget(sheet, maxOffset, 0, 0); | 945 | setVisualOffset_Widget(sheet, maxOffset, 0, 0); |
946 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | 946 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); |
diff --git a/src/ui/root.c b/src/ui/root.c index 5c4296cf..6e187313 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -703,6 +703,20 @@ void updateToolbarColors_Root(iRoot *d) { | |||
703 | #endif | 703 | #endif |
704 | } | 704 | } |
705 | 705 | ||
706 | void showOrHideNewTabButton_Root(iRoot *d) { | ||
707 | iWidget *tabs = findChild_Widget(d->widget, "doctabs"); | ||
708 | iWidget *newTabButton = findChild_Widget(tabs, "newtab"); | ||
709 | iBool hide = iFalse; | ||
710 | iForIndices(i, prefs_App()->navbarActions) { | ||
711 | if (prefs_App()->navbarActions[i] == newTab_ToolbarAction) { | ||
712 | hide = iTrue; | ||
713 | break; | ||
714 | } | ||
715 | } | ||
716 | setFlags_Widget(newTabButton, hidden_WidgetFlag, hide); | ||
717 | arrange_Widget(findChild_Widget(tabs, "tabs.buttons")); | ||
718 | } | ||
719 | |||
706 | void notifyVisualOffsetChange_Root(iRoot *d) { | 720 | void notifyVisualOffsetChange_Root(iRoot *d) { |
707 | if (d && (d->didAnimateVisualOffsets || d->didChangeArrangement)) { | 721 | if (d && (d->didAnimateVisualOffsets || d->didChangeArrangement)) { |
708 | iNotifyAudience(d, visualOffsetsChanged, RootVisualOffsetsChanged); | 722 | iNotifyAudience(d, visualOffsetsChanged, RootVisualOffsetsChanged); |
@@ -848,6 +862,7 @@ static void updateNavBarActions_(iWidget *navBar) { | |||
848 | } | 862 | } |
849 | iEndCollect(); | 863 | iEndCollect(); |
850 | } | 864 | } |
865 | showOrHideNewTabButton_Root(navBar->root); | ||
851 | } | 866 | } |
852 | 867 | ||
853 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | 868 | static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { |
@@ -1312,8 +1327,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1312 | #if defined (iPlatformApple) | 1327 | #if defined (iPlatformApple) |
1313 | addUnsplitButton_(navBar); | 1328 | addUnsplitButton_(navBar); |
1314 | #endif | 1329 | #endif |
1315 | iWidget *navBack; | 1330 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.action1"); |
1316 | setId_Widget(navBack = addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(backArrow_Icon, 0, 0, "navigate.back")), collapse_WidgetFlag), "navbar.action1"); | ||
1317 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.action2"); | 1331 | setId_Widget(addChildFlags_Widget(navBar, iClob(newIcon_LabelWidget(forwardArrow_Icon, 0, 0, "navigate.forward")), collapse_WidgetFlag), "navbar.action2"); |
1318 | /* Button for toggling the left sidebar. */ | 1332 | /* Button for toggling the left sidebar. */ |
1319 | setId_Widget(addChildFlags_Widget( | 1333 | setId_Widget(addChildFlags_Widget( |
@@ -1497,6 +1511,16 @@ void createUserInterface_Root(iRoot *d) { | |||
1497 | /* On PC platforms, the close buttons are generally on the top right. */ | 1511 | /* On PC platforms, the close buttons are generally on the top right. */ |
1498 | addUnsplitButton_(navBar); | 1512 | addUnsplitButton_(navBar); |
1499 | #endif | 1513 | #endif |
1514 | if (deviceType_App() == tablet_AppDeviceType) { | ||
1515 | /* Ensure that all navbar buttons match the height of the input field. | ||
1516 | This is required because touch input fields are given extra padding, | ||
1517 | making them taller than buttons by default. */ | ||
1518 | iForEach(ObjectList, i, children_Widget(navBar)) { | ||
1519 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
1520 | as_Widget(i.object)->sizeRef = as_Widget(url); | ||
1521 | } | ||
1522 | } | ||
1523 | } | ||
1500 | } | 1524 | } |
1501 | /* Tab bar. */ { | 1525 | /* Tab bar. */ { |
1502 | iWidget *mainStack = new_Widget(); | 1526 | iWidget *mainStack = new_Widget(); |
@@ -1517,7 +1541,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1517 | } | 1541 | } |
1518 | setId_Widget( | 1542 | setId_Widget( |
1519 | addChildFlags_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new")), | 1543 | addChildFlags_Widget(buttons, iClob(newIcon_LabelWidget(add_Icon, 0, 0, "tabs.new")), |
1520 | moveToParentRightEdge_WidgetFlag), | 1544 | moveToParentRightEdge_WidgetFlag | collapse_WidgetFlag), |
1521 | "newtab"); | 1545 | "newtab"); |
1522 | } | 1546 | } |
1523 | /* Sidebars. */ { | 1547 | /* Sidebars. */ { |
@@ -1528,6 +1552,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1528 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | 1552 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); |
1529 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); | 1553 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); |
1530 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); | 1554 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); |
1555 | setFlags_Widget(as_Widget(sidebar2), disabledWhenHidden_WidgetFlag, iTrue); | ||
1531 | } | 1556 | } |
1532 | else { | 1557 | else { |
1533 | /* Sidebar is a slide-over sheet. */ | 1558 | /* Sidebar is a slide-over sheet. */ |
diff --git a/src/ui/root.h b/src/ui/root.h index 7e831be3..a81ebdf7 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -43,6 +43,8 @@ void updatePadding_Root (iRoot *); /* TODO: is part of m | |||
43 | void dismissPortraitPhoneSidebars_Root (iRoot *); | 43 | void dismissPortraitPhoneSidebars_Root (iRoot *); |
44 | void showToolbar_Root (iRoot *, iBool show); | 44 | void showToolbar_Root (iRoot *, iBool show); |
45 | void updateToolbarColors_Root (iRoot *); | 45 | void updateToolbarColors_Root (iRoot *); |
46 | void showOrHideNewTabButton_Root (iRoot *); | ||
47 | |||
46 | void notifyVisualOffsetChange_Root (iRoot *); | 48 | void notifyVisualOffsetChange_Root (iRoot *); |
47 | 49 | ||
48 | iInt2 size_Root (const iRoot *); | 50 | iInt2 size_Root (const iRoot *); |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index f5beb785..16677f9e 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -286,7 +286,8 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
286 | iZap(on); | 286 | iZap(on); |
287 | size_t numItems = 0; | 287 | size_t numItems = 0; |
288 | isEmpty = iTrue; | 288 | isEmpty = iTrue; |
289 | iConstForEach(PtrArray, i, listEntries_Feeds()) { | 289 | const iPtrArray *feedEntries = listEntries_Feeds(); |
290 | iConstForEach(PtrArray, i, feedEntries) { | ||
290 | const iFeedEntry *entry = i.ptr; | 291 | const iFeedEntry *entry = i.ptr; |
291 | if (isHidden_FeedEntry(entry)) { | 292 | if (isHidden_FeedEntry(entry)) { |
292 | continue; /* A hidden entry. */ | 293 | continue; /* A hidden entry. */ |
@@ -350,7 +351,7 @@ static void updateItemsWithFlags_SidebarWidget_(iSidebarWidget *d, iBool keepAct | |||
350 | } | 351 | } |
351 | /* Actions. */ | 352 | /* Actions. */ |
352 | if (!isMobile) { | 353 | if (!isMobile) { |
353 | if (!keepActions && !isEmpty) { | 354 | if (!keepActions && !isEmpty_PtrArray(feedEntries)) { |
354 | addActionButton_SidebarWidget_(d, | 355 | addActionButton_SidebarWidget_(d, |
355 | check_Icon | 356 | check_Icon |
356 | " ${sidebar.action.feeds.markallread}", | 357 | " ${sidebar.action.feeds.markallread}", |
diff --git a/src/ui/text.c b/src/ui/text.c index 7bb418eb..200108ed 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -390,8 +390,12 @@ static void deinitCache_Text_(iText *d) { | |||
390 | SDL_DestroyTexture(d->cache); | 390 | SDL_DestroyTexture(d->cache); |
391 | } | 391 | } |
392 | 392 | ||
393 | iRegExp *makeAnsiEscapePattern_Text(void) { | 393 | iRegExp *makeAnsiEscapePattern_Text(iBool includeEscChar) { |
394 | return new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); | 394 | const char *pattern = "\x1b[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])"; |
395 | if (!includeEscChar) { | ||
396 | pattern++; | ||
397 | } | ||
398 | return new_RegExp(pattern, 0); | ||
395 | } | 399 | } |
396 | 400 | ||
397 | void init_Text(iText *d, SDL_Renderer *render) { | 401 | void init_Text(iText *d, SDL_Renderer *render) { |
@@ -399,7 +403,7 @@ void init_Text(iText *d, SDL_Renderer *render) { | |||
399 | activeText_ = d; | 403 | activeText_ = d; |
400 | init_Array(&d->fonts, sizeof(iFont)); | 404 | init_Array(&d->fonts, sizeof(iFont)); |
401 | d->contentFontSize = contentScale_Text_; | 405 | d->contentFontSize = contentScale_Text_; |
402 | d->ansiEscape = makeAnsiEscapePattern_Text(); | 406 | d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */); |
403 | d->baseFontId = -1; | 407 | d->baseFontId = -1; |
404 | d->baseFgColorId = -1; | 408 | d->baseFgColorId = -1; |
405 | d->missingGlyphs = iFalse; | 409 | d->missingGlyphs = iFalse; |
@@ -697,6 +701,34 @@ struct Impl_AttributedRun { | |||
697 | 701 | ||
698 | static iColor fgColor_AttributedRun_(const iAttributedRun *d) { | 702 | static iColor fgColor_AttributedRun_(const iAttributedRun *d) { |
699 | if (d->fgColor_.a) { | 703 | if (d->fgColor_.a) { |
704 | /* Ensure legibility if only the foreground color is set. */ | ||
705 | if (!d->bgColor_.a) { | ||
706 | iColor fg = d->fgColor_; | ||
707 | const iHSLColor themeBg = get_HSLColor(tmBackground_ColorId); | ||
708 | const float bgLuminance = luma_Color(get_Color(tmBackground_ColorId)); | ||
709 | /* TODO: Actually this should check if the FG is too close to the BG, and | ||
710 | either darken or brighten the FG. Now it only accounts for nearly black/white | ||
711 | backgrounds. */ | ||
712 | if (bgLuminance < 0.1f) { | ||
713 | /* Background is dark. Lighten the foreground. */ | ||
714 | iHSLColor fgHsl = hsl_Color(fg); | ||
715 | fgHsl.lum = iMax(0.2f, fgHsl.lum); | ||
716 | return rgb_HSLColor(fgHsl); | ||
717 | } | ||
718 | if (bgLuminance > 0.4f) { | ||
719 | float dim = (bgLuminance - 0.4f); | ||
720 | fg.r *= 1.0f * dim; | ||
721 | fg.g *= 1.0f * dim; | ||
722 | fg.b *= 1.0f * dim; | ||
723 | } | ||
724 | if (themeBg.sat > 0.15f && themeBg.lum >= 0.5f) { | ||
725 | iHSLColor fgHsl = hsl_Color(fg); | ||
726 | fgHsl.hue = themeBg.hue; | ||
727 | fgHsl.lum = themeBg.lum * 0.5f; | ||
728 | fg = rgb_HSLColor(fgHsl); | ||
729 | } | ||
730 | return fg; | ||
731 | } | ||
700 | return d->fgColor_; | 732 | return d->fgColor_; |
701 | } | 733 | } |
702 | if (d->attrib.fgColorId == none_ColorId) { | 734 | if (d->attrib.fgColorId == none_ColorId) { |
@@ -1559,7 +1591,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1559 | iAssert(xAdvance >= 0); | 1591 | iAssert(xAdvance >= 0); |
1560 | if (wrapMode == word_WrapTextMode) { | 1592 | if (wrapMode == word_WrapTextMode) { |
1561 | /* When word wrapping, only consider certain places breakable. */ | 1593 | /* When word wrapping, only consider certain places breakable. */ |
1562 | if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { | 1594 | if ((prevCh == '-' || prevCh == '/' || prevCh == '\\') && !isPunct_Char(ch)) { |
1563 | safeBreakPos = logPos; | 1595 | safeBreakPos = logPos; |
1564 | breakAdvance = wrapAdvance; | 1596 | breakAdvance = wrapAdvance; |
1565 | breakRunIndex = runIndex; | 1597 | breakRunIndex = runIndex; |
@@ -1960,6 +1992,7 @@ static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, | |||
1960 | } | 1992 | } |
1961 | 1993 | ||
1962 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1994 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1995 | *endPos = text.end; | ||
1963 | iWrapText wrap = { .mode = word_WrapTextMode, | 1996 | iWrapText wrap = { .mode = word_WrapTextMode, |
1964 | .text = text, | 1997 | .text = text, |
1965 | .maxWidth = width, | 1998 | .maxWidth = width, |
@@ -1974,6 +2007,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
1974 | *endPos = text.start; | 2007 | *endPos = text.start; |
1975 | return zero_I2(); | 2008 | return zero_I2(); |
1976 | } | 2009 | } |
2010 | *endPos = text.end; | ||
1977 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ | 2011 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ |
1978 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, | 2012 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, |
1979 | .text = text, | 2013 | .text = text, |
diff --git a/src/ui/text.h b/src/ui/text.h index c8bb6f85..b952df84 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -235,7 +235,7 @@ enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode }; | |||
235 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, | 235 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, |
236 | const iString *text); | 236 | const iString *text); |
237 | 237 | ||
238 | iRegExp * makeAnsiEscapePattern_Text (void); | 238 | iRegExp * makeAnsiEscapePattern_Text (iBool includeEscChar); |
239 | 239 | ||
240 | /*-----------------------------------------------------------------------------------------------*/ | 240 | /*-----------------------------------------------------------------------------------------------*/ |
241 | 241 | ||
diff --git a/src/ui/touch.c b/src/ui/touch.c index 20ccf7b8..a178a913 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -638,11 +638,14 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
638 | pixels.x = 0; | 638 | pixels.x = 0; |
639 | } | 639 | } |
640 | #if 0 | 640 | #if 0 |
641 | printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n", | 641 | static uint32_t lastTime = 0; |
642 | printf("%u :: %p (%s) py: %i wy: %f acc: %f edge: %d\n", | ||
643 | nowTime - lastTime, | ||
642 | touch->affinity, | 644 | touch->affinity, |
643 | class_Widget(touch->affinity)->name, | 645 | class_Widget(touch->affinity)->name, |
644 | pixels.y, y_F3(amount), y_F3(touch->accum), | 646 | pixels.y, y_F3(amount), y_F3(touch->accum), |
645 | touch->edge); | 647 | touch->edge); |
648 | lastTime = nowTime; | ||
646 | #endif | 649 | #endif |
647 | if (pixels.x || pixels.y) { | 650 | if (pixels.x || pixels.y) { |
648 | //setFocus_Widget(NULL); | 651 | //setFocus_Widget(NULL); |
diff --git a/src/ui/window.c b/src/ui/window.c index af36bb22..13abc5fa 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -1001,10 +1001,11 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
1001 | default: { | 1001 | default: { |
1002 | SDL_Event event = *ev; | 1002 | SDL_Event event = *ev; |
1003 | if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) { | 1003 | if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) { |
1004 | mw->isDrawFrozen = iFalse; | ||
1005 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { | 1004 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { |
1005 | mw->isDrawFrozen = iTrue; /* don't trigger a redraw now */ | ||
1006 | SDL_ShowWindow(d->win); | 1006 | SDL_ShowWindow(d->win); |
1007 | } | 1007 | } |
1008 | mw->isDrawFrozen = iFalse; | ||
1008 | draw_MainWindow(mw); /* don't show a frame of placeholder content */ | 1009 | draw_MainWindow(mw); /* don't show a frame of placeholder content */ |
1009 | postCommand_App("media.player.update"); /* in case a player needs updating */ | 1010 | postCommand_App("media.player.update"); /* in case a player needs updating */ |
1010 | return iTrue; | 1011 | return iTrue; |