diff options
author | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-02-06 15:07:17 +0200 |
---|---|---|
committer | Jaakko Keränen <jaakko.keranen@iki.fi> | 2022-02-06 15:07:17 +0200 |
commit | 076e34aa8d1e57cbf3142abdc91c8e88d21b75d6 (patch) | |
tree | 3ee91315ee1a083b74d84637af28252f039fa084 /src | |
parent | edb260848a42de2e1af44cab93dbf267d20e1f6f (diff) | |
parent | 53ab74c3a25521faa6f41b40b1969a3b8e8ad027 (diff) |
Merge branch 'dev' into work/v1.11
# Conflicts:
# CMakeLists.txt
# res/fonts/SmolEmoji-Regular.ttf
# res/lang/gl.bin
# src/app.c
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 32 | ||||
-rw-r--r-- | src/feeds.c | 7 | ||||
-rw-r--r-- | src/gmdocument.c | 86 | ||||
-rw-r--r-- | src/gmrequest.c | 6 | ||||
-rw-r--r-- | src/gmutil.c | 62 | ||||
-rw-r--r-- | src/gmutil.h | 2 | ||||
-rw-r--r-- | src/gopher.c | 10 | ||||
-rw-r--r-- | src/gopher.h | 2 | ||||
-rw-r--r-- | src/ios.m | 32 | ||||
-rw-r--r-- | src/macos.m | 13 | ||||
-rw-r--r-- | src/prefs.c | 4 | ||||
-rw-r--r-- | src/resources.c | 4 | ||||
-rw-r--r-- | src/resources.h | 2 | ||||
-rw-r--r-- | src/ui/color.c | 27 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 57 | ||||
-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/paint.c | 6 | ||||
-rw-r--r-- | src/ui/root.c | 31 | ||||
-rw-r--r-- | src/ui/root.h | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 57 | ||||
-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 |
26 files changed, 372 insertions, 212 deletions
@@ -124,7 +124,7 @@ struct Impl_App { | |||
124 | iMimeHooks * mimehooks; | 124 | iMimeHooks * mimehooks; |
125 | iGmCerts * certs; | 125 | iGmCerts * certs; |
126 | iVisited * visited; | 126 | iVisited * visited; |
127 | iBookmarks * bookmarks; | 127 | iBookmarks * bookmarks; |
128 | iMainWindow *window; | 128 | iMainWindow *window; |
129 | iPtrArray popupWindows; | 129 | iPtrArray popupWindows; |
130 | iSortedArray tickers; /* per-frame callbacks, used for animations */ | 130 | iSortedArray tickers; /* per-frame callbacks, used for animations */ |
@@ -333,7 +333,9 @@ static const char *dataDir_App_(void) { | |||
333 | 333 | ||
334 | static const char *downloadDir_App_(void) { | 334 | static const char *downloadDir_App_(void) { |
335 | #if defined (iPlatformAndroidMobile) | 335 | #if defined (iPlatformAndroidMobile) |
336 | return concatPath_CStr(SDL_AndroidGetInternalStoragePath(), "Downloads"); | 336 | const char *dir = concatPath_CStr(SDL_AndroidGetExternalStoragePath(), "Downloads"); |
337 | makeDirs_Path(collectNewCStr_String(dir)); | ||
338 | return dir; | ||
337 | #endif | 339 | #endif |
338 | #if defined (iPlatformLinux) || defined (iPlatformOther) | 340 | #if defined (iPlatformLinux) || defined (iPlatformOther) |
339 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ | 341 | /* Parse user-dirs.dirs using the `xdg-user-dir` tool. */ |
@@ -759,7 +761,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
759 | d->isSuspended = iFalse; | 761 | d->isSuspended = iFalse; |
760 | d->tempFilesPendingDeletion = new_StringSet(); | 762 | d->tempFilesPendingDeletion = new_StringSet(); |
761 | init_CommandLine(&d->args, argc, argv); | 763 | init_CommandLine(&d->args, argc, argv); |
762 | /* Where was the app started from? We ask SDL first because the command line alone | 764 | /* Where was the app started from? We ask SDL first because the command line alone |
763 | cannot be relied on (behavior differs depending on OS). */ { | 765 | cannot be relied on (behavior differs depending on OS). */ { |
764 | char *exec = SDL_GetBasePath(); | 766 | char *exec = SDL_GetBasePath(); |
765 | if (exec) { | 767 | if (exec) { |
@@ -1289,9 +1291,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1289 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ | 1291 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ |
1290 | SDL_Event ev; | 1292 | SDL_Event ev; |
1291 | iBool gotEvents = iFalse; | 1293 | iBool gotEvents = iFalse; |
1294 | iBool gotRefresh = iFalse; | ||
1292 | iPtrArray windows; | 1295 | iPtrArray windows; |
1293 | init_PtrArray(&windows); | 1296 | init_PtrArray(&windows); |
1294 | while (nextEvent_App_(d, eventMode, &ev)) { | 1297 | while (nextEvent_App_(d, gotRefresh ? postedEventsOnly_AppEventMode : eventMode, &ev)) { |
1295 | #if defined (iPlatformAppleMobile) | 1298 | #if defined (iPlatformAppleMobile) |
1296 | if (processEvent_iOS(&ev)) { | 1299 | if (processEvent_iOS(&ev)) { |
1297 | continue; | 1300 | continue; |
@@ -1314,9 +1317,9 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1314 | d->isSuspended = iFalse; | 1317 | d->isSuspended = iFalse; |
1315 | break; | 1318 | break; |
1316 | case SDL_APP_DIDENTERFOREGROUND: | 1319 | case SDL_APP_DIDENTERFOREGROUND: |
1317 | gotEvents = iTrue; | ||
1318 | d->warmupFrames = 5; | 1320 | d->warmupFrames = 5; |
1319 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1321 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1322 | gotEvents = iTrue; | ||
1320 | d->isIdling = iFalse; | 1323 | d->isIdling = iFalse; |
1321 | d->lastEventTime = SDL_GetTicks(); | 1324 | d->lastEventTime = SDL_GetTicks(); |
1322 | #endif | 1325 | #endif |
@@ -1366,6 +1369,10 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1366 | iRelease(ev.user.data1); | 1369 | iRelease(ev.user.data1); |
1367 | continue; | 1370 | continue; |
1368 | } | 1371 | } |
1372 | if (ev.type == SDL_USEREVENT && ev.user.code == refresh_UserEventCode) { | ||
1373 | gotRefresh = iTrue; | ||
1374 | continue; | ||
1375 | } | ||
1369 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1376 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1370 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { | 1377 | if (ev.type == SDL_USEREVENT && ev.user.code == asleep_UserEventCode) { |
1371 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && | 1378 | if (SDL_GetTicks() - d->lastEventTime > idleThreshold_App_ && |
@@ -1384,8 +1391,8 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1384 | // fflush(stdout); | 1391 | // fflush(stdout); |
1385 | } | 1392 | } |
1386 | d->isIdling = iFalse; | 1393 | d->isIdling = iFalse; |
1387 | #endif | ||
1388 | gotEvents = iTrue; | 1394 | gotEvents = iTrue; |
1395 | #endif | ||
1389 | /* Keyboard modifier mapping. */ | 1396 | /* Keyboard modifier mapping. */ |
1390 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { | 1397 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { |
1391 | /* Track Caps Lock state as a modifier. */ | 1398 | /* Track Caps Lock state as a modifier. */ |
@@ -1395,6 +1402,16 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1395 | ev.key.keysym.mod = mapMods_Keys(ev.key.keysym.mod & ~KMOD_CAPS); | 1402 | ev.key.keysym.mod = mapMods_Keys(ev.key.keysym.mod & ~KMOD_CAPS); |
1396 | } | 1403 | } |
1397 | #if defined (iPlatformAndroidMobile) | 1404 | #if defined (iPlatformAndroidMobile) |
1405 | /* Use the system Back button to close panels, if they're open. */ | ||
1406 | if (ev.type == SDL_KEYDOWN && ev.key.keysym.sym == SDLK_AC_BACK) { | ||
1407 | SDL_UserEvent panelBackCmd = { .type = SDL_USEREVENT, | ||
1408 | .code = command_UserEventCode, | ||
1409 | .data1 = iDupStr("panel.close"), | ||
1410 | .data2 = d->window->base.keyRoot }; | ||
1411 | if (dispatchEvent_Window(&d->window->base, (SDL_Event *) &panelBackCmd)) { | ||
1412 | continue; /* Was handled by someone. */ | ||
1413 | } | ||
1414 | } | ||
1398 | /* Ignore all mouse events; just use touch. */ | 1415 | /* Ignore all mouse events; just use touch. */ |
1399 | if (ev.type == SDL_MOUSEBUTTONDOWN || | 1416 | if (ev.type == SDL_MOUSEBUTTONDOWN || |
1400 | ev.type == SDL_MOUSEBUTTONUP || | 1417 | ev.type == SDL_MOUSEBUTTONUP || |
@@ -2078,7 +2095,6 @@ iDocumentWidget *document_Command(const char *cmd) { | |||
2078 | } | 2095 | } |
2079 | 2096 | ||
2080 | iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) { | 2097 | iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) { |
2081 | //iApp *d = &app_; | ||
2082 | iWidget *tabs = findWidget_Root("doctabs"); | 2098 | iWidget *tabs = findWidget_Root("doctabs"); |
2083 | setFlags_Widget(tabs, hidden_WidgetFlag, iFalse); | 2099 | setFlags_Widget(tabs, hidden_WidgetFlag, iFalse); |
2084 | iWidget *newTabButton = findChild_Widget(tabs, "newtab"); | 2100 | iWidget *newTabButton = findChild_Widget(tabs, "newtab"); |
@@ -2094,6 +2110,7 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe | |||
2094 | iRelease(doc); /* now owned by the tabs */ | 2110 | iRelease(doc); /* now owned by the tabs */ |
2095 | addTabCloseButton_Widget(tabs, as_Widget(doc), "tabs.close"); | 2111 | addTabCloseButton_Widget(tabs, as_Widget(doc), "tabs.close"); |
2096 | addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton)); | 2112 | addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton)); |
2113 | showOrHideNewTabButton_Root(tabs->root); | ||
2097 | if (switchToNew) { | 2114 | if (switchToNew) { |
2098 | postCommandf_App("tabs.switch page:%p", doc); | 2115 | postCommandf_App("tabs.switch page:%p", doc); |
2099 | } | 2116 | } |
@@ -2876,6 +2893,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2876 | return iTrue; | 2893 | return iTrue; |
2877 | } | 2894 | } |
2878 | iDocumentWidget *doc = document_Command(cmd); | 2895 | iDocumentWidget *doc = document_Command(cmd); |
2896 | iAssert(doc); | ||
2879 | iDocumentWidget *origin = doc; | 2897 | iDocumentWidget *origin = doc; |
2880 | if (hasLabel_Command(cmd, "origin")) { | 2898 | if (hasLabel_Command(cmd, "origin")) { |
2881 | iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin")); | 2899 | iDocumentWidget *cmdOrig = findWidget_App(cstr_Command(cmd, "origin")); |
diff --git a/src/feeds.c b/src/feeds.c index a8cbf47a..7b679dc1 100644 --- a/src/feeds.c +++ b/src/feeds.c | |||
@@ -275,7 +275,8 @@ static void save_Feeds_(iFeeds *d) { | |||
275 | if (open_File(f, write_FileMode | text_FileMode)) { | 275 | if (open_File(f, write_FileMode | text_FileMode)) { |
276 | lock_Mutex(d->mtx); | 276 | lock_Mutex(d->mtx); |
277 | iString *str = new_String(); | 277 | iString *str = new_String(); |
278 | format_String(str, "%llu\n# Feeds\n", integralSeconds_Time(&d->lastRefreshedAt)); | 278 | format_String(str, "%llu\n# Feeds\n", (unsigned long long) |
279 | integralSeconds_Time(&d->lastRefreshedAt)); | ||
279 | write_File(f, utf8_String(str)); | 280 | write_File(f, utf8_String(str)); |
280 | /* Index of feeds for IDs. */ { | 281 | /* Index of feeds for IDs. */ { |
281 | iConstForEach(PtrArray, i, listSubscriptions_()) { | 282 | iConstForEach(PtrArray, i, listSubscriptions_()) { |
@@ -296,8 +297,8 @@ static void save_Feeds_(iFeeds *d) { | |||
296 | } | 297 | } |
297 | format_String(str, "%x\n%llu\n%llu\n%s\n%s\n", | 298 | format_String(str, "%x\n%llu\n%llu\n%s\n%s\n", |
298 | entry->bookmarkId, | 299 | entry->bookmarkId, |
299 | integralSeconds_Time(&entry->posted), | 300 | (unsigned long long) integralSeconds_Time(&entry->posted), |
300 | integralSeconds_Time(&entry->discovered), | 301 | (unsigned long long) integralSeconds_Time(&entry->discovered), |
301 | cstr_String(&entry->url), | 302 | cstr_String(&entry->url), |
302 | cstr_String(&entry->title)); | 303 | cstr_String(&entry->title)); |
303 | write_File(f, utf8_String(str)); | 304 | write_File(f, utf8_String(str)); |
diff --git a/src/gmdocument.c b/src/gmdocument.c index 19230392..b5e71e21 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -547,9 +547,11 @@ static void clear_RunTypesetter_(iRunTypesetter *d) { | |||
547 | clear_Array(&d->layout); | 547 | clear_Array(&d->layout); |
548 | } | 548 | } |
549 | 549 | ||
550 | static void commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { | 550 | static size_t commit_RunTypesetter_(iRunTypesetter *d, iGmDocument *doc) { |
551 | const size_t n = size_Array(&d->layout); | ||
551 | pushBackN_Array(&doc->layout, constData_Array(&d->layout), size_Array(&d->layout)); | 552 | pushBackN_Array(&doc->layout, constData_Array(&d->layout), size_Array(&d->layout)); |
552 | clear_RunTypesetter_(d); | 553 | clear_RunTypesetter_(d); |
554 | return n; | ||
553 | } | 555 | } |
554 | 556 | ||
555 | static const int maxLedeLines_ = 10; | 557 | static const int maxLedeLines_ = 10; |
@@ -611,6 +613,10 @@ static iBool typesetOneLine_RunTypesetter_(iWrapText *wrap, iRangecc wrapRange, | |||
611 | } | 613 | } |
612 | 614 | ||
613 | static void doLayout_GmDocument_(iGmDocument *d) { | 615 | static void doLayout_GmDocument_(iGmDocument *d) { |
616 | static iRegExp *ansiPattern_; | ||
617 | if (!ansiPattern_) { | ||
618 | ansiPattern_ = makeAnsiEscapePattern_Text(iTrue /* with ESC */); | ||
619 | } | ||
614 | const iPrefs *prefs = prefs_App(); | 620 | const iPrefs *prefs = prefs_App(); |
615 | const iBool isMono = isForcedMonospace_GmDocument_(d); | 621 | const iBool isMono = isForcedMonospace_GmDocument_(d); |
616 | const iBool isGopher = isGopher_GmDocument_(d); | 622 | const iBool isGopher = isGopher_GmDocument_(d); |
@@ -618,8 +624,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
618 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; | 624 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; |
619 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; | 625 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; |
620 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); | 626 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); |
621 | // const iBool isDarkBg = isDark_GmDocumentTheme( | 627 | |
622 | // isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); | ||
623 | initTheme_GmDocument_(d); | 628 | initTheme_GmDocument_(d); |
624 | d->isLayoutInvalidated = iFalse; | 629 | d->isLayoutInvalidated = iFalse; |
625 | /* TODO: Collect these parameters into a GmTheme. */ | 630 | /* TODO: Collect these parameters into a GmTheme. */ |
@@ -657,7 +662,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
657 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ | 662 | const iArray *oldPreMeta = collect_Array(copy_Array(&d->preMeta)); /* remember fold states */ |
658 | clear_Array(&d->preMeta); | 663 | clear_Array(&d->preMeta); |
659 | clear_String(&d->title); | 664 | clear_String(&d->title); |
660 | // clear_String(&d->bannerText); | ||
661 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { | 665 | if (d->size.x <= 0 || isEmpty_String(&d->source)) { |
662 | return; | 666 | return; |
663 | } | 667 | } |
@@ -671,7 +675,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
671 | int preFont = preformatted_FontId; | 675 | int preFont = preformatted_FontId; |
672 | uint16_t preId = 0; | 676 | uint16_t preId = 0; |
673 | iBool enableIndents = iFalse; | 677 | iBool enableIndents = iFalse; |
674 | // iBool addSiteBanner = d->bannerType != none_GmDocumentBanner; | ||
675 | const iBool isNormalized = isNormalized_GmDocument_(d); | 678 | const iBool isNormalized = isNormalized_GmDocument_(d); |
676 | enum iGmLineType prevType = text_GmLineType; | 679 | enum iGmLineType prevType = text_GmLineType; |
677 | enum iGmLineType prevNonBlankType = text_GmLineType; | 680 | enum iGmLineType prevNonBlankType = text_GmLineType; |
@@ -755,7 +758,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
755 | if (d->format == gemini_SourceFormat && | 758 | if (d->format == gemini_SourceFormat && |
756 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { | 759 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { |
757 | isPreformat = iFalse; | 760 | isPreformat = iFalse; |
758 | // addSiteBanner = iFalse; /* overrides the banner */ | ||
759 | continue; | 761 | continue; |
760 | } | 762 | } |
761 | run.mediaType = max_MediaType; /* preformatted block */ | 763 | run.mediaType = max_MediaType; /* preformatted block */ |
@@ -763,28 +765,6 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
763 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); | 765 | run.font = (d->format == plainText_SourceFormat ? plainText_FontId : preFont); |
764 | indent = indents[type]; | 766 | indent = indents[type]; |
765 | } | 767 | } |
766 | #if 0 | ||
767 | if (addSiteBanner) { | ||
768 | addSiteBanner = iFalse; | ||
769 | const iRangecc bannerText = urlHost_String(&d->url); | ||
770 | if (!isEmpty_Range(&bannerText)) { | ||
771 | setRange_String(&d->bannerText, bannerText); | ||
772 | iGmRun banner = { .flags = decoration_GmRunFlag | siteBanner_GmRunFlag }; | ||
773 | banner.bounds = zero_Rect(); | ||
774 | banner.visBounds = init_Rect(0, 0, d->size.x, lineHeight_Text(banner_FontId) * 2); | ||
775 | if (d->bannerType == certificateWarning_GmDocumentBanner) { | ||
776 | banner.visBounds.size.y += iMaxi(6000 * lineHeight_Text(uiLabel_FontId) / | ||
777 | d->size.x, lineHeight_Text(uiLabel_FontId) * 5); | ||
778 | } | ||
779 | banner.text = bannerText; | ||
780 | banner.font = banner_FontId; | ||
781 | banner.color = tmBannerTitle_ColorId; | ||
782 | pushBack_Array(&d->layout, &banner); | ||
783 | pos.y += height_Rect(banner.visBounds) + | ||
784 | 1.5f * lineHeight_Text(paragraph_FontId) * prefs->lineSpacing; | ||
785 | } | ||
786 | } | ||
787 | #endif | ||
788 | /* Empty lines don't produce text runs. */ | 768 | /* Empty lines don't produce text runs. */ |
789 | if (isEmpty_Range(&line)) { | 769 | if (isEmpty_Range(&line)) { |
790 | if (type == quote_GmLineType && !prefs->quoteIcon) { | 770 | if (type == quote_GmLineType && !prefs->quoteIcon) { |
@@ -865,6 +845,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
865 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && | 845 | if ((type == heading1_GmLineType || type == heading2_GmLineType) && |
866 | isEmpty_String(&d->title)) { | 846 | isEmpty_String(&d->title)) { |
867 | setRange_String(&d->title, line); | 847 | setRange_String(&d->title, line); |
848 | /* Get rid of ANSI escapes. */ | ||
849 | replaceRegExp_String(&d->title, ansiPattern_, "", NULL, NULL); | ||
868 | } | 850 | } |
869 | /* List bullet. */ | 851 | /* List bullet. */ |
870 | if (type == bullet_GmLineType) { | 852 | if (type == bullet_GmLineType) { |
@@ -964,6 +946,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
964 | } | 946 | } |
965 | } | 947 | } |
966 | iAssert(!isEmpty_Range(&line)); /* must have something at this point */ | 948 | iAssert(!isEmpty_Range(&line)); /* must have something at this point */ |
949 | size_t numRunsAdded = 0; | ||
967 | /* Typeset the paragraph. */ { | 950 | /* Typeset the paragraph. */ { |
968 | iRunTypesetter rts; | 951 | iRunTypesetter rts; |
969 | init_RunTypesetter_(&rts); | 952 | init_RunTypesetter_(&rts); |
@@ -1036,7 +1019,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
1036 | : 1.0f); | 1019 | : 1.0f); |
1037 | } | 1020 | } |
1038 | } | 1021 | } |
1039 | commit_RunTypesetter_(&rts, d); | 1022 | numRunsAdded = commit_RunTypesetter_(&rts, d); |
1040 | break; | 1023 | break; |
1041 | } | 1024 | } |
1042 | /* Try again... */ | 1025 | /* Try again... */ |
@@ -1050,6 +1033,11 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
1050 | deinit_RunTypesetter_(&rts); | 1033 | deinit_RunTypesetter_(&rts); |
1051 | } | 1034 | } |
1052 | /* Flag the end of line, too. */ | 1035 | /* Flag the end of line, too. */ |
1036 | if (numRunsAdded == 0) { | ||
1037 | pos.y += lineHeight_Text(run.font) * prefs->lineSpacing; | ||
1038 | followsBlank = iTrue; | ||
1039 | continue; | ||
1040 | } | ||
1053 | iGmRun *lastRun = back_Array(&d->layout); | 1041 | iGmRun *lastRun = back_Array(&d->layout); |
1054 | lastRun->flags |= endOfLine_GmRunFlag; | 1042 | lastRun->flags |= endOfLine_GmRunFlag; |
1055 | if (lastRun->linkId && lastRun->flags & startOfLine_GmRunFlag) { | 1043 | if (lastRun->linkId && lastRun->flags & startOfLine_GmRunFlag) { |
@@ -1301,7 +1289,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1301 | 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606, | 1289 | 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606, |
1302 | 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728, | 1290 | 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728, |
1303 | 0x272a, 0x272f, 0x2731, 0x2738, 0x273a, 0x273e, 0x2740, 0x2742, 0x2744, 0x2748, | 1291 | 0x272a, 0x272f, 0x2731, 0x2738, 0x273a, 0x273e, 0x2740, 0x2742, 0x2744, 0x2748, |
1304 | 0x274a, 0x2751, 0x2756, 0x2766, 0x27bd, 0x27c1, 0x27d0, 0x2b19, 0x1f300, 0x1f303, | 1292 | 0x274a, 0x2318, 0x2756, 0x2766, 0x27bd, 0x27c1, 0x27d0, 0x2b19, 0x1f300, 0x1f303, |
1305 | 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, | 1293 | 0x1f306, 0x1f308, 0x1f30a, 0x1f319, 0x1f31f, 0x1f320, 0x1f340, 0x1f4cd, 0x1f4e1, 0x1f531, |
1306 | 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, | 1294 | 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, |
1307 | }; | 1295 | }; |
@@ -1951,44 +1939,6 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { | |||
1951 | } | 1939 | } |
1952 | } | 1940 | } |
1953 | 1941 | ||
1954 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | ||
1955 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
1956 | void *context) { | ||
1957 | iRegExpMatch m; | ||
1958 | iString result; | ||
1959 | int numMatches = 0; | ||
1960 | const char *pos = constBegin_String(d); | ||
1961 | init_RegExpMatch(&m); | ||
1962 | init_String(&result); | ||
1963 | while (matchString_RegExp(regexp, d, &m)) { | ||
1964 | appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); | ||
1965 | /* Replace any capture group back-references. */ | ||
1966 | for (const char *ch = replacement; *ch; ch++) { | ||
1967 | if (*ch == '\\') { | ||
1968 | ch++; | ||
1969 | if (*ch == '\\') { | ||
1970 | appendCStr_String(&result, "\\"); | ||
1971 | } | ||
1972 | else if (*ch >= '0' && *ch <= '9') { | ||
1973 | appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); | ||
1974 | } | ||
1975 | } | ||
1976 | else { | ||
1977 | appendData_Block(&result.chars, ch, 1); | ||
1978 | } | ||
1979 | } | ||
1980 | if (matchHandler) { | ||
1981 | matchHandler(context, &m); | ||
1982 | } | ||
1983 | pos = end_RegExpMatch(&m); | ||
1984 | numMatches++; | ||
1985 | } | ||
1986 | appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); | ||
1987 | set_String(d, &result); | ||
1988 | deinit_String(&result); | ||
1989 | return numMatches; | ||
1990 | } | ||
1991 | |||
1992 | iDeclareType(PendingLink) | 1942 | iDeclareType(PendingLink) |
1993 | struct Impl_PendingLink { | 1943 | struct Impl_PendingLink { |
1994 | iString *url; | 1944 | iString *url; |
diff --git a/src/gmrequest.c b/src/gmrequest.c index 3d5a4aef..82c232e1 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -358,6 +358,12 @@ static const iBlock *aboutPageSource_(iRangecc path, iRangecc query) { | |||
358 | if (equalCase_Rangecc(path, "version")) { | 358 | if (equalCase_Rangecc(path, "version")) { |
359 | return &blobVersion_Resources; | 359 | return &blobVersion_Resources; |
360 | } | 360 | } |
361 | if (equalCase_Rangecc(path, "version-1.5")) { | ||
362 | return &blobVersion_1_5_Resources; | ||
363 | } | ||
364 | if (equalCase_Rangecc(path, "version-0.13")) { | ||
365 | return &blobVersion_0_13_Resources; | ||
366 | } | ||
361 | if (equalCase_Rangecc(path, "debug")) { | 367 | if (equalCase_Rangecc(path, "debug")) { |
362 | return utf8_String(debugInfo_App()); | 368 | return utf8_String(debugInfo_App()); |
363 | } | 369 | } |
diff --git a/src/gmutil.c b/src/gmutil.c index 98e4d4d6..e59e6649 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -131,6 +131,16 @@ static iRangecc prevPathSeg_(const char *end, const char *start) { | |||
131 | return seg; | 131 | return seg; |
132 | } | 132 | } |
133 | 133 | ||
134 | void stripUrlPort_String(iString *d) { | ||
135 | iUrl parts; | ||
136 | init_Url(&parts, d); | ||
137 | if (!isEmpty_Range(&parts.port)) { | ||
138 | /* Always preceded by a colon. */ | ||
139 | remove_Block(&d->chars, parts.port.start - 1 - constBegin_String(d), | ||
140 | size_Range(&parts.port) + 1); | ||
141 | } | ||
142 | } | ||
143 | |||
134 | void stripDefaultUrlPort_String(iString *d) { | 144 | void stripDefaultUrlPort_String(iString *d) { |
135 | iUrl parts; | 145 | iUrl parts; |
136 | init_Url(&parts, d); | 146 | init_Url(&parts, d); |
@@ -248,6 +258,9 @@ iRangecc urlRoot_String(const iString *d) { | |||
248 | else { | 258 | else { |
249 | iUrl parts; | 259 | iUrl parts; |
250 | init_Url(&parts, d); | 260 | init_Url(&parts, d); |
261 | if (equalCase_Rangecc(parts.scheme, "about")) { | ||
262 | return (iRangecc){ constBegin_String(d), parts.path.start }; | ||
263 | } | ||
251 | rootEnd = parts.path.start; | 264 | rootEnd = parts.path.start; |
252 | } | 265 | } |
253 | return (iRangecc){ constBegin_String(d), rootEnd }; | 266 | return (iRangecc){ constBegin_String(d), rootEnd }; |
@@ -681,6 +694,17 @@ const iString *withSpacesEncoded_String(const iString *d) { | |||
681 | return d; | 694 | return d; |
682 | } | 695 | } |
683 | 696 | ||
697 | const iString *withScheme_String(const iString *d, const char *scheme) { | ||
698 | iUrl parts; | ||
699 | init_Url(&parts, d); | ||
700 | if (!equalCase_Rangecc(parts.scheme, scheme)) { | ||
701 | iString *repl = collectNewCStr_String(scheme); | ||
702 | appendRange_String(repl, (iRangecc){ parts.scheme.end, constEnd_String(d) }); | ||
703 | return repl; | ||
704 | } | ||
705 | return d; | ||
706 | } | ||
707 | |||
684 | const iString *canonicalUrl_String(const iString *d) { | 708 | const iString *canonicalUrl_String(const iString *d) { |
685 | /* The "canonical" form, used for internal storage and comparisons, is: | 709 | /* The "canonical" form, used for internal storage and comparisons, is: |
686 | - all non-reserved characters decoded (i.e., it's an IRI) | 710 | - all non-reserved characters decoded (i.e., it's an IRI) |
@@ -880,3 +904,41 @@ const iGmError *get_GmError(enum iGmStatusCode code) { | |||
880 | iAssert(errors_[0].code == unknownStatusCode_GmStatusCode); | 904 | iAssert(errors_[0].code == unknownStatusCode_GmStatusCode); |
881 | return &errors_[0].err; /* unknown */ | 905 | return &errors_[0].err; /* unknown */ |
882 | } | 906 | } |
907 | |||
908 | int replaceRegExp_String(iString *d, const iRegExp *regexp, const char *replacement, | ||
909 | void (*matchHandler)(void *, const iRegExpMatch *), | ||
910 | void *context) { | ||
911 | iRegExpMatch m; | ||
912 | iString result; | ||
913 | int numMatches = 0; | ||
914 | const char *pos = constBegin_String(d); | ||
915 | init_RegExpMatch(&m); | ||
916 | init_String(&result); | ||
917 | while (matchString_RegExp(regexp, d, &m)) { | ||
918 | appendRange_String(&result, (iRangecc){ pos, begin_RegExpMatch(&m) }); | ||
919 | /* Replace any capture group back-references. */ | ||
920 | for (const char *ch = replacement; *ch; ch++) { | ||
921 | if (*ch == '\\') { | ||
922 | ch++; | ||
923 | if (*ch == '\\') { | ||
924 | appendCStr_String(&result, "\\"); | ||
925 | } | ||
926 | else if (*ch >= '0' && *ch <= '9') { | ||
927 | appendRange_String(&result, capturedRange_RegExpMatch(&m, *ch - '0')); | ||
928 | } | ||
929 | } | ||
930 | else { | ||
931 | appendData_Block(&result.chars, ch, 1); | ||
932 | } | ||
933 | } | ||
934 | if (matchHandler) { | ||
935 | matchHandler(context, &m); | ||
936 | } | ||
937 | pos = end_RegExpMatch(&m); | ||
938 | numMatches++; | ||
939 | } | ||
940 | appendRange_String(&result, (iRangecc){ pos, constEnd_String(d) }); | ||
941 | set_String(d, &result); | ||
942 | deinit_String(&result); | ||
943 | return numMatches; | ||
944 | } | ||
diff --git a/src/gmutil.h b/src/gmutil.h index 15bb7b2e..1594afc4 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -127,6 +127,7 @@ iBool isKnownScheme_Rangecc (iRangecc scheme); /* any URI scheme */ | |||
127 | iBool isKnownUrlScheme_Rangecc(iRangecc scheme); /* URL schemes only */ | 127 | iBool isKnownUrlScheme_Rangecc(iRangecc scheme); /* URL schemes only */ |
128 | void punyEncodeDomain_Rangecc(iRangecc domain, iString *encoded_out); | 128 | void punyEncodeDomain_Rangecc(iRangecc domain, iString *encoded_out); |
129 | void punyEncodeUrlHost_String(iString *absoluteUrl); | 129 | void punyEncodeUrlHost_String(iString *absoluteUrl); |
130 | void stripUrlPort_String (iString *); | ||
130 | void stripDefaultUrlPort_String(iString *); | 131 | void stripDefaultUrlPort_String(iString *); |
131 | const iString * urlFragmentStripped_String(const iString *); | 132 | const iString * urlFragmentStripped_String(const iString *); |
132 | const iString * urlQueryStripped_String (const iString *); | 133 | const iString * urlQueryStripped_String (const iString *); |
@@ -138,6 +139,7 @@ const char * makeFileUrl_CStr (const char *localFilePath); | |||
138 | iString * localFilePathFromUrl_String(const iString *); | 139 | iString * localFilePathFromUrl_String(const iString *); |
139 | void urlEncodeSpaces_String (iString *); | 140 | void urlEncodeSpaces_String (iString *); |
140 | const iString * withSpacesEncoded_String(const iString *); | 141 | const iString * withSpacesEncoded_String(const iString *); |
142 | const iString * withScheme_String (const iString *, const char *scheme); /* replace URI scheme */ | ||
141 | const iString * canonicalUrl_String (const iString *); | 143 | const iString * canonicalUrl_String (const iString *); |
142 | 144 | ||
143 | const char * mediaType_Path (const iString *path); | 145 | const char * mediaType_Path (const iString *path); |
diff --git a/src/gopher.c b/src/gopher.c index 008a7743..0e34fe6a 100644 --- a/src/gopher.c +++ b/src/gopher.c | |||
@@ -299,3 +299,13 @@ iBool processResponse_Gopher(iGopher *d, const iBlock *data) { | |||
299 | } | 299 | } |
300 | return changed; | 300 | return changed; |
301 | } | 301 | } |
302 | |||
303 | void setUrlItemType_Gopher(iString *url, char itemType) { | ||
304 | iUrl parts; | ||
305 | init_Url(&parts, url); | ||
306 | if (equalCase_Rangecc(parts.scheme, "gopher")) { | ||
307 | if (parts.path.start && size_Range(&parts.path) >= 2) { | ||
308 | ((char *) parts.path.start)[1] = itemType; | ||
309 | } | ||
310 | } | ||
311 | } | ||
diff --git a/src/gopher.h b/src/gopher.h index 3ad7e374..3cad0c21 100644 --- a/src/gopher.h +++ b/src/gopher.h | |||
@@ -44,3 +44,5 @@ iDeclareTypeConstruction(Gopher) | |||
44 | void open_Gopher (iGopher *, const iString *url); | 44 | void open_Gopher (iGopher *, const iString *url); |
45 | iBool processResponse_Gopher (iGopher *, const iBlock *data); | 45 | iBool processResponse_Gopher (iGopher *, const iBlock *data); |
46 | void cancel_Gopher (iGopher *); | 46 | void cancel_Gopher (iGopher *); |
47 | |||
48 | void setUrlItemType_Gopher (iString *url, char itemType); | ||
@@ -163,7 +163,8 @@ API_AVAILABLE(ios(13.0)) | |||
163 | 163 | ||
164 | /*----------------------------------------------------------------------------------------------*/ | 164 | /*----------------------------------------------------------------------------------------------*/ |
165 | 165 | ||
166 | @interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate> { | 166 | @interface AppState : NSObject<UIDocumentPickerDelegate, UITextFieldDelegate, UITextViewDelegate, |
167 | UIScrollViewDelegate> { | ||
167 | iString *fileBeingSaved; | 168 | iString *fileBeingSaved; |
168 | iString *pickFileCommand; | 169 | iString *pickFileCommand; |
169 | iSystemTextInput *sysCtrl; | 170 | iSystemTextInput *sysCtrl; |
@@ -173,6 +174,7 @@ API_AVAILABLE(ios(13.0)) | |||
173 | @end | 174 | @end |
174 | 175 | ||
175 | static AppState *appState_; | 176 | static AppState *appState_; |
177 | static UIScrollView *statusBarTapper_; /* dummy scroll view just for getting notified of taps */ | ||
176 | 178 | ||
177 | @implementation AppState | 179 | @implementation AppState |
178 | 180 | ||
@@ -310,8 +312,15 @@ replacementString:(NSString *)string { | |||
310 | notifyChange_SystemTextInput_(sysCtrl); | 312 | notifyChange_SystemTextInput_(sysCtrl); |
311 | } | 313 | } |
312 | 314 | ||
315 | - (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { | ||
316 | postCommand_App("scroll.top smooth:1"); | ||
317 | return NO; | ||
318 | } | ||
319 | |||
313 | @end | 320 | @end |
314 | 321 | ||
322 | /*----------------------------------------------------------------------------------------------*/ | ||
323 | |||
315 | static void enableMouse_(iBool yes) { | 324 | static void enableMouse_(iBool yes) { |
316 | SDL_EventState(SDL_MOUSEBUTTONDOWN, yes); | 325 | SDL_EventState(SDL_MOUSEBUTTONDOWN, yes); |
317 | SDL_EventState(SDL_MOUSEMOTION, yes); | 326 | SDL_EventState(SDL_MOUSEMOTION, yes); |
@@ -426,6 +435,19 @@ void setupWindow_iOS(iWindow *window) { | |||
426 | UIViewController *ctl = viewController_(window); | 435 | UIViewController *ctl = viewController_(window); |
427 | isSystemDarkMode_ = isDarkMode_(window); | 436 | isSystemDarkMode_ = isDarkMode_(window); |
428 | postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); | 437 | postCommandf_App("~os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); |
438 | /* A hack to get notified on status bar taps. We create a thin dummy UIScrollView | ||
439 | that occupies the top of the screen where the status bar is located. */ { | ||
440 | CGRect statusBarFrame = [UIApplication sharedApplication].statusBarFrame; | ||
441 | statusBarTapper_ = [[UIScrollView alloc] initWithFrame:statusBarFrame]; | ||
442 | // [statusBarTapper_ setBackgroundColor:[UIColor greenColor]]; /* to see where it is */ | ||
443 | [statusBarTapper_ setShowsVerticalScrollIndicator:NO]; | ||
444 | [statusBarTapper_ setShowsHorizontalScrollIndicator:NO]; | ||
445 | [statusBarTapper_ setContentSize:(CGSize){ 10000, 10000 }]; | ||
446 | [statusBarTapper_ setContentOffset:(CGPoint){ 0, 1000 }]; | ||
447 | [statusBarTapper_ setScrollsToTop:YES]; | ||
448 | [statusBarTapper_ setDelegate:appState_]; | ||
449 | [ctl.view addSubview:statusBarTapper_]; | ||
450 | } | ||
429 | } | 451 | } |
430 | 452 | ||
431 | void playHapticEffect_iOS(enum iHapticEffect effect) { | 453 | void playHapticEffect_iOS(enum iHapticEffect effect) { |
@@ -443,6 +465,14 @@ void playHapticEffect_iOS(enum iHapticEffect effect) { | |||
443 | } | 465 | } |
444 | 466 | ||
445 | iBool processEvent_iOS(const SDL_Event *ev) { | 467 | iBool processEvent_iOS(const SDL_Event *ev) { |
468 | if (ev->type == SDL_DISPLAYEVENT) { | ||
469 | if (deviceType_App() == phone_AppDeviceType) { | ||
470 | [statusBarTapper_ setHidden:(ev->display.data1 == SDL_ORIENTATION_LANDSCAPE || | ||
471 | ev->display.data1 == SDL_ORIENTATION_LANDSCAPE_FLIPPED)]; | ||
472 | } | ||
473 | [statusBarTapper_ setFrame:[UIApplication sharedApplication].statusBarFrame]; | ||
474 | return iFalse; | ||
475 | } | ||
446 | if (ev->type == SDL_WINDOWEVENT) { | 476 | if (ev->type == SDL_WINDOWEVENT) { |
447 | if (ev->window.event == SDL_WINDOWEVENT_RESTORED) { | 477 | if (ev->window.event == SDL_WINDOWEVENT_RESTORED) { |
448 | const iBool isDark = isDarkMode_(get_Window()); | 478 | const iBool isDark = isDarkMode_(get_Window()); |
diff --git a/src/macos.m b/src/macos.m index 4ad267c1..191842f6 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -72,10 +72,10 @@ static NSString *currentSystemAppearance_(void) { | |||
72 | } | 72 | } |
73 | 73 | ||
74 | iBool shouldDefaultToMetalRenderer_MacOS(void) { | 74 | iBool shouldDefaultToMetalRenderer_MacOS(void) { |
75 | /* TODO: Test if SDL 2.0.16 works better (no stutters with Metal?). */ | ||
76 | return iFalse; /* | ||
77 | const iInt2 ver = macVer_(); | 75 | const iInt2 ver = macVer_(); |
78 | return ver.x > 10 || ver.y > 13;*/ | 76 | SDL_DisplayMode dispMode; |
77 | SDL_GetDesktopDisplayMode(0, &dispMode); | ||
78 | return dispMode.refresh_rate > 60 && (ver.x > 10 || ver.y > 13); | ||
79 | } | 79 | } |
80 | 80 | ||
81 | static void ignoreImmediateKeyDownEvents_(void) { | 81 | static void ignoreImmediateKeyDownEvents_(void) { |
@@ -436,6 +436,10 @@ static iBool processScrollWheelEvent_(NSEvent *event) { | |||
436 | const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; | 436 | const iBool isInertia = (event.momentumPhase & (NSEventPhaseBegan | NSEventPhaseChanged)) != 0; |
437 | const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; | 437 | const iBool isEnded = event.scrollingDeltaX == 0.0f && event.scrollingDeltaY == 0.0f && !isInertia; |
438 | const iWindow *win = &get_MainWindow()->base; | 438 | const iWindow *win = &get_MainWindow()->base; |
439 | if (event.window != nsWindow_(win->win)) { | ||
440 | /* Not the main window. */ | ||
441 | return iFalse; | ||
442 | } | ||
439 | if (isPerPixel) { | 443 | if (isPerPixel) { |
440 | /* On macOS 12.1, stopping ongoing inertia scroll with a tap seems to sometimes produce | 444 | /* On macOS 12.1, stopping ongoing inertia scroll with a tap seems to sometimes produce |
441 | spurious large scroll events. */ | 445 | spurious large scroll events. */ |
@@ -525,7 +529,6 @@ static iBool processScrollWheelEvent_(NSEvent *event) { | |||
525 | ev.wheel.y = iSign(ev.wheel.y); | 529 | ev.wheel.y = iSign(ev.wheel.y); |
526 | } | 530 | } |
527 | #endif | 531 | #endif |
528 | |||
529 | return iTrue; | 532 | return iTrue; |
530 | } | 533 | } |
531 | 534 | ||
@@ -734,7 +737,7 @@ enum iColorId removeColorEscapes_String(iString *d) { | |||
734 | static NSString *cleanString_(const iString *ansiEscapedText) { | 737 | static NSString *cleanString_(const iString *ansiEscapedText) { |
735 | iString mod; | 738 | iString mod; |
736 | initCopy_String(&mod, ansiEscapedText); | 739 | initCopy_String(&mod, ansiEscapedText); |
737 | iRegExp *ansi = makeAnsiEscapePattern_Text(); | 740 | iRegExp *ansi = makeAnsiEscapePattern_Text(iTrue /* with ESC */); |
738 | replaceRegExp_String(&mod, ansi, "", NULL, NULL); | 741 | replaceRegExp_String(&mod, ansi, "", NULL, NULL); |
739 | iRelease(ansi); | 742 | iRelease(ansi); |
740 | NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; | 743 | NSString *clean = [NSString stringWithUTF8String:cstr_String(&mod)]; |
diff --git a/src/prefs.c b/src/prefs.c index 6164ca25..13a1dab7 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -25,8 +25,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include <the_Foundation/fileinfo.h> | 25 | #include <the_Foundation/fileinfo.h> |
26 | #include <assert.h> | 26 | #include <assert.h> |
27 | 27 | ||
28 | static_assert(offsetof(iPrefs, plainTextWrap) == offsetof(iPrefs, bools[plainTextWrap_PrefsBool]), | 28 | _Static_assert(offsetof(iPrefs, plainTextWrap) == offsetof(iPrefs, bools[plainTextWrap_PrefsBool]), |
29 | "memory layout mismatch (needs struct packing?)"); | 29 | "memory layout mismatch (needs struct packing?)"); |
30 | 30 | ||
31 | void init_Prefs(iPrefs *d) { | 31 | void init_Prefs(iPrefs *d) { |
32 | iForIndices(i, d->strings) { | 32 | iForIndices(i, d->strings) { |
diff --git a/src/resources.c b/src/resources.c index ae85463a..fa7485ce 100644 --- a/src/resources.c +++ b/src/resources.c | |||
@@ -33,6 +33,8 @@ iBlock blobAbout_Resources; | |||
33 | iBlock blobHelp_Resources; | 33 | iBlock blobHelp_Resources; |
34 | iBlock blobLagrange_Resources; | 34 | iBlock blobLagrange_Resources; |
35 | iBlock blobLicense_Resources; | 35 | iBlock blobLicense_Resources; |
36 | iBlock blobVersion_0_13_Resources; | ||
37 | iBlock blobVersion_1_5_Resources; | ||
36 | iBlock blobVersion_Resources; | 38 | iBlock blobVersion_Resources; |
37 | iBlock blobArghelp_Resources; | 39 | iBlock blobArghelp_Resources; |
38 | iBlock blobCs_Resources; | 40 | iBlock blobCs_Resources; |
@@ -76,6 +78,8 @@ static struct { | |||
76 | { &blobVersion_Resources, "about/android-version.gmi" }, | 78 | { &blobVersion_Resources, "about/android-version.gmi" }, |
77 | #else | 79 | #else |
78 | { &blobHelp_Resources, "about/help.gmi" }, | 80 | { &blobHelp_Resources, "about/help.gmi" }, |
81 | { &blobVersion_0_13_Resources, "about/version-0.13.gmi" }, | ||
82 | { &blobVersion_1_5_Resources, "about/version-1.5.gmi" }, | ||
79 | { &blobVersion_Resources, "about/version.gmi" }, | 83 | { &blobVersion_Resources, "about/version.gmi" }, |
80 | #endif | 84 | #endif |
81 | { &blobArghelp_Resources, "arg-help.txt" }, | 85 | { &blobArghelp_Resources, "arg-help.txt" }, |
diff --git a/src/resources.h b/src/resources.h index 3852cc3e..e440fda3 100644 --- a/src/resources.h +++ b/src/resources.h | |||
@@ -35,6 +35,8 @@ extern iBlock blobAbout_Resources; | |||
35 | extern iBlock blobHelp_Resources; | 35 | extern iBlock blobHelp_Resources; |
36 | extern iBlock blobLagrange_Resources; | 36 | extern iBlock blobLagrange_Resources; |
37 | extern iBlock blobLicense_Resources; | 37 | extern iBlock blobLicense_Resources; |
38 | extern iBlock blobVersion_0_13_Resources; | ||
39 | extern iBlock blobVersion_1_5_Resources; | ||
38 | extern iBlock blobVersion_Resources; | 40 | extern iBlock blobVersion_Resources; |
39 | extern iBlock blobArghelp_Resources; | 41 | extern iBlock blobArghelp_Resources; |
40 | extern iBlock blobCs_Resources; | 42 | extern iBlock blobCs_Resources; |
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 03119ca2..97ecb4ba 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 | } |
@@ -2900,10 +2902,14 @@ static void addBannerWarnings_DocumentWidget_(iDocumentWidget *d) { | |||
2900 | add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str); | 2902 | add_Banner(d->banner, warning_BannerType, none_GmStatusCode, title, str); |
2901 | } | 2903 | } |
2902 | /* Warnings related to page contents. */ | 2904 | /* Warnings related to page contents. */ |
2903 | const int dismissed = | 2905 | int dismissed = |
2904 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), | 2906 | value_SiteSpec(collectNewRange_String(urlRoot_String(d->mod.url)), |
2905 | dismissWarnings_SiteSpecKey) | | 2907 | dismissWarnings_SiteSpecKey) | |
2906 | (!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 | } | ||
2907 | const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; | 2913 | const int warnings = warnings_GmDocument(d->view.doc) & ~dismissed; |
2908 | if (warnings & missingGlyphs_GmDocumentWarning) { | 2914 | if (warnings & missingGlyphs_GmDocumentWarning) { |
2909 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); | 2915 | add_Banner(d->banner, warning_BannerType, missingGlyphs_GmStatusCode, NULL, NULL); |
@@ -4069,14 +4075,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4069 | return iTrue; | 4075 | return iTrue; |
4070 | } | 4076 | } |
4071 | else if (equal_Command(cmd, "valueinput.cancelled") && | 4077 | else if (equal_Command(cmd, "valueinput.cancelled") && |
4072 | 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) { |
4073 | postCommand_Root(get_Root(), "navigate.back"); | 4079 | postCommand_Root(get_Root(), "navigate.back"); |
4074 | return iTrue; | 4080 | return iTrue; |
4075 | } | 4081 | } |
4076 | else if (equalWidget_Command(cmd, w, "document.request.updated") && | 4082 | else if (equalWidget_Command(cmd, w, "document.request.updated") && |
4077 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { | 4083 | id_GmRequest(d->request) == argU32Label_Command(cmd, "reqid")) { |
4078 | // set_Block(&d->sourceContent, &lockResponse_GmRequest(d->request)->body); | ||
4079 | // unlockResponse_GmRequest(d->request); | ||
4080 | if (document_App() == d) { | 4084 | if (document_App() == d) { |
4081 | updateFetchProgress_DocumentWidget_(d); | 4085 | updateFetchProgress_DocumentWidget_(d); |
4082 | } | 4086 | } |
@@ -4302,6 +4306,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4302 | else if (equal_Command(cmd, "navigate.parent") && document_App() == d) { | 4306 | else if (equal_Command(cmd, "navigate.parent") && document_App() == d) { |
4303 | iUrl parts; | 4307 | iUrl parts; |
4304 | 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 | } | ||
4305 | /* Remove the last path segment. */ | 4312 | /* Remove the last path segment. */ |
4306 | if (size_Range(&parts.path) > 1) { | 4313 | if (size_Range(&parts.path) > 1) { |
4307 | if (parts.path.end[-1] == '/') { | 4314 | if (parts.path.end[-1] == '/') { |
@@ -4311,14 +4318,42 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4311 | if (parts.path.end[-1] == '/') break; | 4318 | if (parts.path.end[-1] == '/') break; |
4312 | parts.path.end--; | 4319 | parts.path.end--; |
4313 | } | 4320 | } |
4314 | postCommandf_Root(w->root, | 4321 | iString *parentUrl = collectNewRange_String((iRangecc){ constBegin_String(d->mod.url), |
4315 | "open url:%s", | 4322 | parts.path.end }); |
4316 | 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)); | ||
4317 | } | 4336 | } |
4318 | return iTrue; | 4337 | return iTrue; |
4319 | } | 4338 | } |
4320 | else if (equal_Command(cmd, "navigate.root") && document_App() == d) { | 4339 | else if (equal_Command(cmd, "navigate.root") && document_App() == d) { |
4321 | 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)); | ||
4322 | return iTrue; | 4357 | return iTrue; |
4323 | } | 4358 | } |
4324 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { | 4359 | else if (equalWidget_Command(cmd, w, "scroll.moved")) { |
@@ -4341,6 +4376,12 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
4341 | return iTrue; | 4376 | return iTrue; |
4342 | } | 4377 | } |
4343 | 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 | } | ||
4344 | init_Anim(&d->view.scrollY.pos, 0); | 4385 | init_Anim(&d->view.scrollY.pos, 0); |
4345 | invalidate_VisBuf(d->view.visBuf); | 4386 | invalidate_VisBuf(d->view.visBuf); |
4346 | 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/paint.c b/src/ui/paint.c index 5e66f521..67b509fb 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c | |||
@@ -71,9 +71,6 @@ void endTarget_Paint(iPaint *d) { | |||
71 | 71 | ||
72 | void setClip_Paint(iPaint *d, iRect rect) { | 72 | void setClip_Paint(iPaint *d, iRect rect) { |
73 | addv_I2(&rect.pos, origin_Paint); | 73 | addv_I2(&rect.pos, origin_Paint); |
74 | if (isEmpty_Rect(rect)) { | ||
75 | rect = init_Rect(0, 0, 1, 1); | ||
76 | } | ||
77 | iRect targetRect = zero_Rect(); | 74 | iRect targetRect = zero_Rect(); |
78 | SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d)); | 75 | SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d)); |
79 | if (target) { | 76 | if (target) { |
@@ -83,6 +80,9 @@ void setClip_Paint(iPaint *d, iRect rect) { | |||
83 | else { | 80 | else { |
84 | rect = intersect_Rect(rect, rect_Root(get_Root())); | 81 | rect = intersect_Rect(rect, rect_Root(get_Root())); |
85 | } | 82 | } |
83 | if (isEmpty_Rect(rect)) { | ||
84 | rect = init_Rect(0, 0, 1, 1); | ||
85 | } | ||
86 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | 86 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); |
87 | } | 87 | } |
88 | 88 | ||
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 16677f9e..da0ec22c 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -1181,39 +1181,43 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1181 | argLabel_Command(cmd, "noanim") == 0 && | 1181 | argLabel_Command(cmd, "noanim") == 0 && |
1182 | (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); | 1182 | (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); |
1183 | int visX = 0; | 1183 | int visX = 0; |
1184 | int visY = 0; | 1184 | // int visY = 0; |
1185 | if (isVisible_Widget(w)) { | 1185 | if (isVisible_Widget(w)) { |
1186 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); | 1186 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); |
1187 | visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect); | 1187 | // visY = top_Rect(bounds_Widget(w)) - top_Rect(w->root->widget->rect); |
1188 | } | 1188 | } |
1189 | const iBool isHiding = isVisible_Widget(w); | 1189 | const iBool isHiding = isVisible_Widget(w); |
1190 | setFlags_Widget(w, hidden_WidgetFlag, isHiding); | 1190 | setFlags_Widget(w, hidden_WidgetFlag, isHiding); |
1191 | /* Safe area inset for mobile. */ | 1191 | /* Safe area inset for mobile. */ |
1192 | const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); | 1192 | const int safePad = |
1193 | deviceType_App() == desktop_AppDeviceType | ||
1194 | ? 0 | ||
1195 | : (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); | ||
1193 | const int animFlags = easeOut_AnimFlag | softer_AnimFlag; | 1196 | const int animFlags = easeOut_AnimFlag | softer_AnimFlag; |
1194 | if (!isPortraitPhone_App()) { | 1197 | if (!isPortraitPhone_App()) { |
1195 | if (!isHiding) { | 1198 | if (!isHiding) { |
1196 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); | 1199 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); |
1197 | w->rect.size.x = d->widthAsGaps * gap_UI; | 1200 | w->rect.size.x = d->widthAsGaps * gap_UI; |
1198 | invalidate_ListWidget(d->list); | 1201 | invalidate_ListWidget(d->list); |
1199 | if (isAnimated) { | 1202 | if (isAnimated) { |
1200 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | 1203 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); |
1201 | setVisualOffset_Widget( | 1204 | setVisualOffset_Widget(w, |
1202 | w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); | 1205 | (d->side == left_SidebarSide ? -1 : 1) * |
1206 | (w->rect.size.x + safePad), | ||
1207 | 0, | ||
1208 | 0); | ||
1203 | setVisualOffset_Widget(w, 0, 300, animFlags); | 1209 | setVisualOffset_Widget(w, 0, 300, animFlags); |
1210 | } | ||
1204 | } | 1211 | } |
1205 | } | 1212 | else if (isAnimated) { |
1206 | else if (isAnimated) { | 1213 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); |
1207 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | 1214 | if (d->side == right_SidebarSide) { |
1208 | if (d->side == right_SidebarSide) { | 1215 | setVisualOffset_Widget(w, visX, 0, 0); |
1209 | setVisualOffset_Widget(w, visX, 0, 0); | 1216 | setVisualOffset_Widget(w, visX + w->rect.size.x + safePad, 300, animFlags); |
1210 | setVisualOffset_Widget( | 1217 | } |
1211 | w, visX + w->rect.size.x + safePad, 300, animFlags); | 1218 | else { |
1212 | } | 1219 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); |
1213 | else { | 1220 | setVisualOffset_Widget(w, -w->rect.size.x - safePad, 300, animFlags); |
1214 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | ||
1215 | setVisualOffset_Widget( | ||
1216 | w, -w->rect.size.x - safePad, 300, animFlags); | ||
1217 | } | 1221 | } |
1218 | } | 1222 | } |
1219 | setScrollMode_ListWidget(d->list, normal_ScrollMode); | 1223 | setScrollMode_ListWidget(d->list, normal_ScrollMode); |
@@ -1226,15 +1230,16 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1226 | w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight; | 1230 | w->rect.pos.y = height_Rect(safeRect_Root(w->root)) - d->midHeight; |
1227 | setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0); | 1231 | setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 0, 0); |
1228 | setVisualOffset_Widget(w, 0, 300, animFlags); | 1232 | setVisualOffset_Widget(w, 0, 300, animFlags); |
1229 | //animateSlidingSheetHeight_SidebarWidget_(d); | 1233 | // animateSlidingSheetHeight_SidebarWidget_(d); |
1230 | setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); | 1234 | setScrollMode_ListWidget(d->list, disabledAtTopBothDirections_ScrollMode); |
1231 | } | 1235 | } |
1232 | else { | 1236 | else { |
1233 | setVisualOffset_Widget(w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); | 1237 | setVisualOffset_Widget( |
1238 | w, bottom_Rect(rect_Root(w->root)) - w->rect.pos.y, 300, animFlags); | ||
1234 | if (d->isEditing) { | 1239 | if (d->isEditing) { |
1235 | setMobileEditMode_SidebarWidget_(d, iFalse); | 1240 | setMobileEditMode_SidebarWidget_(d, iFalse); |
1241 | } | ||
1236 | } | 1242 | } |
1237 | } | ||
1238 | showToolbar_Root(w->root, isHiding); | 1243 | showToolbar_Root(w->root, isHiding); |
1239 | } | 1244 | } |
1240 | updateToolbarColors_Root(w->root); | 1245 | updateToolbarColors_Root(w->root); |
@@ -1242,7 +1247,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
1242 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ | 1247 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ |
1243 | arrange_Widget(w); | 1248 | arrange_Widget(w); |
1244 | if (!isPortraitPhone_App()) { | 1249 | if (!isPortraitPhone_App()) { |
1245 | updateSize_DocumentWidget(document_App()); | 1250 | updateSize_DocumentWidget(document_App()); |
1246 | } | 1251 | } |
1247 | if (isVisible_Widget(w)) { | 1252 | if (isVisible_Widget(w)) { |
1248 | updateItems_SidebarWidget_(d); | 1253 | updateItems_SidebarWidget_(d); |
diff --git a/src/ui/text.c b/src/ui/text.c index 86ac709b..ab2af2b2 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -391,8 +391,12 @@ static void deinitCache_Text_(iText *d) { | |||
391 | SDL_DestroyTexture(d->cache); | 391 | SDL_DestroyTexture(d->cache); |
392 | } | 392 | } |
393 | 393 | ||
394 | iRegExp *makeAnsiEscapePattern_Text(void) { | 394 | iRegExp *makeAnsiEscapePattern_Text(iBool includeEscChar) { |
395 | return new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0); | 395 | const char *pattern = "\x1b[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])"; |
396 | if (!includeEscChar) { | ||
397 | pattern++; | ||
398 | } | ||
399 | return new_RegExp(pattern, 0); | ||
396 | } | 400 | } |
397 | 401 | ||
398 | void init_Text(iText *d, SDL_Renderer *render) { | 402 | void init_Text(iText *d, SDL_Renderer *render) { |
@@ -400,7 +404,7 @@ void init_Text(iText *d, SDL_Renderer *render) { | |||
400 | activeText_ = d; | 404 | activeText_ = d; |
401 | init_Array(&d->fonts, sizeof(iFont)); | 405 | init_Array(&d->fonts, sizeof(iFont)); |
402 | d->contentFontSize = contentScale_Text_; | 406 | d->contentFontSize = contentScale_Text_; |
403 | d->ansiEscape = makeAnsiEscapePattern_Text(); | 407 | d->ansiEscape = makeAnsiEscapePattern_Text(iFalse /* no ESC */); |
404 | d->baseFontId = -1; | 408 | d->baseFontId = -1; |
405 | d->baseFgColorId = -1; | 409 | d->baseFgColorId = -1; |
406 | d->missingGlyphs = iFalse; | 410 | d->missingGlyphs = iFalse; |
@@ -714,6 +718,34 @@ struct Impl_AttributedRun { | |||
714 | 718 | ||
715 | static iColor fgColor_AttributedRun_(const iAttributedRun *d) { | 719 | static iColor fgColor_AttributedRun_(const iAttributedRun *d) { |
716 | if (d->fgColor_.a) { | 720 | if (d->fgColor_.a) { |
721 | /* Ensure legibility if only the foreground color is set. */ | ||
722 | if (!d->bgColor_.a) { | ||
723 | iColor fg = d->fgColor_; | ||
724 | const iHSLColor themeBg = get_HSLColor(tmBackground_ColorId); | ||
725 | const float bgLuminance = luma_Color(get_Color(tmBackground_ColorId)); | ||
726 | /* TODO: Actually this should check if the FG is too close to the BG, and | ||
727 | either darken or brighten the FG. Now it only accounts for nearly black/white | ||
728 | backgrounds. */ | ||
729 | if (bgLuminance < 0.1f) { | ||
730 | /* Background is dark. Lighten the foreground. */ | ||
731 | iHSLColor fgHsl = hsl_Color(fg); | ||
732 | fgHsl.lum = iMax(0.2f, fgHsl.lum); | ||
733 | return rgb_HSLColor(fgHsl); | ||
734 | } | ||
735 | if (bgLuminance > 0.4f) { | ||
736 | float dim = (bgLuminance - 0.4f); | ||
737 | fg.r *= 1.0f * dim; | ||
738 | fg.g *= 1.0f * dim; | ||
739 | fg.b *= 1.0f * dim; | ||
740 | } | ||
741 | if (themeBg.sat > 0.15f && themeBg.lum >= 0.5f) { | ||
742 | iHSLColor fgHsl = hsl_Color(fg); | ||
743 | fgHsl.hue = themeBg.hue; | ||
744 | fgHsl.lum = themeBg.lum * 0.5f; | ||
745 | fg = rgb_HSLColor(fgHsl); | ||
746 | } | ||
747 | return fg; | ||
748 | } | ||
717 | return d->fgColor_; | 749 | return d->fgColor_; |
718 | } | 750 | } |
719 | if (d->attrib.fgColorId == none_ColorId) { | 751 | if (d->attrib.fgColorId == none_ColorId) { |
@@ -1576,7 +1608,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1576 | iAssert(xAdvance >= 0); | 1608 | iAssert(xAdvance >= 0); |
1577 | if (wrapMode == word_WrapTextMode) { | 1609 | if (wrapMode == word_WrapTextMode) { |
1578 | /* When word wrapping, only consider certain places breakable. */ | 1610 | /* When word wrapping, only consider certain places breakable. */ |
1579 | if ((prevCh == '-' || prevCh == '/') && !isPunct_Char(ch)) { | 1611 | if ((prevCh == '-' || prevCh == '/' || prevCh == '\\') && !isPunct_Char(ch)) { |
1580 | safeBreakPos = logPos; | 1612 | safeBreakPos = logPos; |
1581 | breakAdvance = wrapAdvance; | 1613 | breakAdvance = wrapAdvance; |
1582 | breakRunIndex = runIndex; | 1614 | breakRunIndex = runIndex; |
@@ -1977,6 +2009,7 @@ static iBool cbAdvanceOneLine_(iWrapText *d, iRangecc range, iTextAttrib attrib, | |||
1977 | } | 2009 | } |
1978 | 2010 | ||
1979 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 2011 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
2012 | *endPos = text.end; | ||
1980 | iWrapText wrap = { .mode = word_WrapTextMode, | 2013 | iWrapText wrap = { .mode = word_WrapTextMode, |
1981 | .text = text, | 2014 | .text = text, |
1982 | .maxWidth = width, | 2015 | .maxWidth = width, |
@@ -1991,6 +2024,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
1991 | *endPos = text.start; | 2024 | *endPos = text.start; |
1992 | return zero_I2(); | 2025 | return zero_I2(); |
1993 | } | 2026 | } |
2027 | *endPos = text.end; | ||
1994 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ | 2028 | /* "NoWrap" means words aren't wrapped; the line is broken at nearest character. */ |
1995 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, | 2029 | iWrapText wrap = { .mode = anyCharacter_WrapTextMode, |
1996 | .text = text, | 2030 | .text = text, |
diff --git a/src/ui/text.h b/src/ui/text.h index a34cc9bd..e741880d 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -237,7 +237,7 @@ enum iTextBlockMode { quadrants_TextBlockMode, shading_TextBlockMode }; | |||
237 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, | 237 | iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iTextBlockMode, |
238 | const iString *text); | 238 | const iString *text); |
239 | 239 | ||
240 | iRegExp * makeAnsiEscapePattern_Text (void); | 240 | iRegExp * makeAnsiEscapePattern_Text (iBool includeEscChar); |
241 | 241 | ||
242 | /*-----------------------------------------------------------------------------------------------*/ | 242 | /*-----------------------------------------------------------------------------------------------*/ |
243 | 243 | ||
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; |