diff options
Diffstat (limited to 'src')
41 files changed, 1644 insertions, 394 deletions
@@ -212,6 +212,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
212 | appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling); | 212 | appendFormat_String(str, "smoothscroll arg:%d\n", d->prefs.smoothScrolling); |
213 | appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); | 213 | appendFormat_String(str, "imageloadscroll arg:%d\n", d->prefs.loadImageInsteadOfScrolling); |
214 | appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); | 214 | appendFormat_String(str, "cachesize.set arg:%d\n", d->prefs.maxCacheSize); |
215 | appendFormat_String(str, "memorysize.set arg:%d\n", d->prefs.maxMemorySize); | ||
215 | appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); | 216 | appendFormat_String(str, "decodeurls arg:%d\n", d->prefs.decodeUserVisibleURLs); |
216 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); | 217 | appendFormat_String(str, "linewidth.set arg:%d\n", d->prefs.lineWidth); |
217 | /* TODO: Set up an array of booleans in Prefs and do these in a loop. */ | 218 | /* TODO: Set up an array of booleans in Prefs and do these in a loop. */ |
@@ -762,6 +763,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
762 | setupApplication_iOS(); | 763 | setupApplication_iOS(); |
763 | #endif | 764 | #endif |
764 | init_Keys(); | 765 | init_Keys(); |
766 | loadPalette_Color(dataDir_App_()); | ||
765 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ | 767 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ |
766 | loadPrefs_App_(d); | 768 | loadPrefs_App_(d); |
767 | load_Keys(dataDir_App_()); | 769 | load_Keys(dataDir_App_()); |
@@ -920,13 +922,25 @@ const iString *debugInfo_App(void) { | |||
920 | extern char **environ; /* The environment variables. */ | 922 | extern char **environ; /* The environment variables. */ |
921 | iApp *d = &app_; | 923 | iApp *d = &app_; |
922 | iString *msg = collectNew_String(); | 924 | iString *msg = collectNew_String(); |
925 | iObjectList *docs = iClob(listDocuments_App(NULL)); | ||
923 | format_String(msg, "# Debug information\n"); | 926 | format_String(msg, "# Debug information\n"); |
927 | appendFormat_String(msg, "## Memory usage\n"); { | ||
928 | iMemInfo total = { 0, 0 }; | ||
929 | iForEach(ObjectList, i, docs) { | ||
930 | iDocumentWidget *doc = i.object; | ||
931 | iMemInfo usage = memoryUsage_History(history_DocumentWidget(doc)); | ||
932 | total.cacheSize += usage.cacheSize; | ||
933 | total.memorySize += usage.memorySize; | ||
934 | } | ||
935 | appendFormat_String(msg, "Total cache: %.3f MB\n", total.cacheSize / 1.0e6f); | ||
936 | appendFormat_String(msg, "Total memory: %.3f MB\n", total.memorySize / 1.0e6f); | ||
937 | } | ||
924 | appendFormat_String(msg, "## Documents\n"); | 938 | appendFormat_String(msg, "## Documents\n"); |
925 | iForEach(ObjectList, k, iClob(listDocuments_App(NULL))) { | 939 | iForEach(ObjectList, k, docs) { |
926 | iDocumentWidget *doc = k.object; | 940 | iDocumentWidget *doc = k.object; |
927 | appendFormat_String(msg, "### Tab %d.%zu: %s\n", | 941 | appendFormat_String(msg, "### Tab %d.%zu: %s\n", |
928 | constAs_Widget(doc)->root == get_Window()->roots[0] ? 0 : 1, | 942 | constAs_Widget(doc)->root == get_Window()->roots[0] ? 1 : 2, |
929 | childIndex_Widget(constAs_Widget(doc)->parent, k.object), | 943 | childIndex_Widget(constAs_Widget(doc)->parent, k.object) + 1, |
930 | cstr_String(bookmarkTitle_DocumentWidget(doc))); | 944 | cstr_String(bookmarkTitle_DocumentWidget(doc))); |
931 | append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc)))); | 945 | append_String(msg, collect_String(debugInfo_History(history_DocumentWidget(doc)))); |
932 | } | 946 | } |
@@ -981,6 +995,33 @@ void trimCache_App(void) { | |||
981 | iRelease(docs); | 995 | iRelease(docs); |
982 | } | 996 | } |
983 | 997 | ||
998 | void trimMemory_App(void) { | ||
999 | iApp *d = &app_; | ||
1000 | size_t memorySize = 0; | ||
1001 | const size_t limit = d->prefs.maxMemorySize * 1000000; | ||
1002 | iObjectList *docs = listDocuments_App(NULL); | ||
1003 | iForEach(ObjectList, i, docs) { | ||
1004 | memorySize += memorySize_History(history_DocumentWidget(i.object)); | ||
1005 | } | ||
1006 | init_ObjectListIterator(&i, docs); | ||
1007 | iBool wasPruned = iFalse; | ||
1008 | while (memorySize > limit) { | ||
1009 | iDocumentWidget *doc = i.object; | ||
1010 | const size_t pruned = pruneLeastImportantMemory_History(history_DocumentWidget(doc)); | ||
1011 | if (pruned) { | ||
1012 | memorySize -= pruned; | ||
1013 | wasPruned = iTrue; | ||
1014 | } | ||
1015 | next_ObjectListIterator(&i); | ||
1016 | if (!i.value) { | ||
1017 | if (!wasPruned) break; | ||
1018 | wasPruned = iFalse; | ||
1019 | init_ObjectListIterator(&i, docs); | ||
1020 | } | ||
1021 | } | ||
1022 | iRelease(docs); | ||
1023 | } | ||
1024 | |||
984 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | 1025 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { |
985 | if (!isEmpty_Periodic(&d->periodic)) { | 1026 | if (!isEmpty_Periodic(&d->periodic)) { |
986 | return iFalse; | 1027 | return iFalse; |
@@ -1051,6 +1092,13 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1051 | postRefresh_App(); | 1092 | postRefresh_App(); |
1052 | break; | 1093 | break; |
1053 | case SDL_APP_WILLENTERBACKGROUND: | 1094 | case SDL_APP_WILLENTERBACKGROUND: |
1095 | #if defined (iPlatformAppleMobile) | ||
1096 | updateNowPlayingInfo_iOS(); | ||
1097 | #endif | ||
1098 | setFreezeDraw_Window(d->window, iTrue); | ||
1099 | savePrefs_App_(d); | ||
1100 | saveState_App_(d); | ||
1101 | break; | ||
1054 | case SDL_APP_TERMINATING: | 1102 | case SDL_APP_TERMINATING: |
1055 | setFreezeDraw_Window(d->window, iTrue); | 1103 | setFreezeDraw_Window(d->window, iTrue); |
1056 | savePrefs_App_(d); | 1104 | savePrefs_App_(d); |
@@ -1606,7 +1654,9 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1606 | postCommandf_App("searchurl address:%s", | 1654 | postCommandf_App("searchurl address:%s", |
1607 | cstrText_InputWidget(findChild_Widget(d, "prefs.searchurl"))); | 1655 | cstrText_InputWidget(findChild_Widget(d, "prefs.searchurl"))); |
1608 | postCommandf_App("cachesize.set arg:%d", | 1656 | postCommandf_App("cachesize.set arg:%d", |
1609 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); | 1657 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.cachesize")))); |
1658 | postCommandf_App("memorysize.set arg:%d", | ||
1659 | toInt_String(text_InputWidget(findChild_Widget(d, "prefs.memorysize")))); | ||
1610 | postCommandf_App("ca.file path:%s", | 1660 | postCommandf_App("ca.file path:%s", |
1611 | cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); | 1661 | cstrText_InputWidget(findChild_Widget(d, "prefs.ca.file"))); |
1612 | postCommandf_App("ca.path path:%s", | 1662 | postCommandf_App("ca.path path:%s", |
@@ -2019,7 +2069,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2019 | else if (equal_Command(cmd, "hidetoolbarscroll")) { | 2069 | else if (equal_Command(cmd, "hidetoolbarscroll")) { |
2020 | d->prefs.hideToolbarOnScroll = arg_Command(cmd); | 2070 | d->prefs.hideToolbarOnScroll = arg_Command(cmd); |
2021 | if (!d->prefs.hideToolbarOnScroll) { | 2071 | if (!d->prefs.hideToolbarOnScroll) { |
2022 | showToolbars_Root(get_Root(), iTrue); | 2072 | showToolbar_Root(get_Root(), iTrue); |
2023 | } | 2073 | } |
2024 | return iTrue; | 2074 | return iTrue; |
2025 | } | 2075 | } |
@@ -2168,6 +2218,13 @@ iBool handleCommand_App(const char *cmd) { | |||
2168 | } | 2218 | } |
2169 | return iTrue; | 2219 | return iTrue; |
2170 | } | 2220 | } |
2221 | else if (equal_Command(cmd, "memorysize.set")) { | ||
2222 | d->prefs.maxMemorySize = arg_Command(cmd); | ||
2223 | if (d->prefs.maxMemorySize <= 0) { | ||
2224 | d->prefs.maxMemorySize = 0; | ||
2225 | } | ||
2226 | return iTrue; | ||
2227 | } | ||
2171 | else if (equal_Command(cmd, "searchurl")) { | 2228 | else if (equal_Command(cmd, "searchurl")) { |
2172 | iString *url = &d->prefs.searchUrl; | 2229 | iString *url = &d->prefs.searchUrl; |
2173 | setCStr_String(url, suffixPtr_Command(cmd, "address")); | 2230 | setCStr_String(url, suffixPtr_Command(cmd, "address")); |
@@ -2229,7 +2286,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2229 | } | 2286 | } |
2230 | else if (equal_Command(cmd, "open")) { | 2287 | else if (equal_Command(cmd, "open")) { |
2231 | iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); | 2288 | iString *url = collectNewCStr_String(suffixPtr_Command(cmd, "url")); |
2232 | const iBool noProxy = argLabel_Command(cmd, "noproxy"); | 2289 | const iBool noProxy = argLabel_Command(cmd, "noproxy") != 0; |
2290 | const iBool fromSidebar = argLabel_Command(cmd, "fromsidebar") != 0; | ||
2233 | iUrl parts; | 2291 | iUrl parts; |
2234 | init_Url(&parts, url); | 2292 | init_Url(&parts, url); |
2235 | if (argLabel_Command(cmd, "default") || equalCase_Rangecc(parts.scheme, "mailto") || | 2293 | if (argLabel_Command(cmd, "default") || equalCase_Rangecc(parts.scheme, "mailto") || |
@@ -2280,7 +2338,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2280 | else { | 2338 | else { |
2281 | urlEncodePath_String(url); | 2339 | urlEncodePath_String(url); |
2282 | } | 2340 | } |
2283 | setUrlFromCache_DocumentWidget(doc, url, isHistory); | 2341 | setUrlFlags_DocumentWidget(doc, url, |
2342 | (isHistory ? useCachedContentIfAvailable_DocumentWidgetSetUrlFlag : 0) | | ||
2343 | (fromSidebar ? openedFromSidebar_DocumentWidgetSetUrlFlag : 0)); | ||
2284 | /* Optionally, jump to a text in the document. This will only work if the document | 2344 | /* Optionally, jump to a text in the document. This will only work if the document |
2285 | is already available, e.g., it's from "about:" or restored from cache. */ | 2345 | is already available, e.g., it's from "about:" or restored from cache. */ |
2286 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); | 2346 | const iRangecc gotoHeading = range_Command(cmd, "gotoheading"); |
@@ -2295,6 +2355,36 @@ iBool handleCommand_App(const char *cmd) { | |||
2295 | } | 2355 | } |
2296 | setCurrent_Root(oldRoot); | 2356 | setCurrent_Root(oldRoot); |
2297 | } | 2357 | } |
2358 | else if (equal_Command(cmd, "file.open")) { | ||
2359 | const char *path = suffixPtr_Command(cmd, "path"); | ||
2360 | if (path) { | ||
2361 | postCommandf_App("open temp:%d url:%s", | ||
2362 | argLabel_Command(cmd, "temp"), | ||
2363 | makeFileUrl_CStr(path)); | ||
2364 | return iTrue; | ||
2365 | } | ||
2366 | #if defined (iPlatformAppleMobile) | ||
2367 | pickFileForOpening_iOS(); | ||
2368 | #endif | ||
2369 | return iTrue; | ||
2370 | } | ||
2371 | else if (equal_Command(cmd, "file.delete")) { | ||
2372 | const char *path = suffixPtr_Command(cmd, "path"); | ||
2373 | if (argLabel_Command(cmd, "confirm")) { | ||
2374 | makeQuestion_Widget( | ||
2375 | uiHeading_ColorEscape "${heading.file.delete}", | ||
2376 | format_CStr("${dlg.file.delete.confirm}\n%s", path), | ||
2377 | (iMenuItem[]){ | ||
2378 | { "${cancel}", 0, 0, NULL }, | ||
2379 | { uiTextCaution_ColorEscape "${dlg.file.delete}", 0, 0, | ||
2380 | format_CStr("!file.delete path:%s", path) } }, | ||
2381 | 2); | ||
2382 | } | ||
2383 | else { | ||
2384 | remove(path); | ||
2385 | } | ||
2386 | return iTrue; | ||
2387 | } | ||
2298 | else if (equal_Command(cmd, "document.request.cancelled")) { | 2388 | else if (equal_Command(cmd, "document.request.cancelled")) { |
2299 | /* TODO: How should cancelled requests be treated in the history? */ | 2389 | /* TODO: How should cancelled requests be treated in the history? */ |
2300 | #if 0 | 2390 | #if 0 |
@@ -2446,6 +2536,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2446 | iTrue); | 2536 | iTrue); |
2447 | setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"), | 2537 | setText_InputWidget(findChild_Widget(dlg, "prefs.cachesize"), |
2448 | collectNewFormat_String("%d", d->prefs.maxCacheSize)); | 2538 | collectNewFormat_String("%d", d->prefs.maxCacheSize)); |
2539 | setText_InputWidget(findChild_Widget(dlg, "prefs.memorysize"), | ||
2540 | collectNewFormat_String("%d", d->prefs.maxMemorySize)); | ||
2449 | setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); | 2541 | setToggle_Widget(findChild_Widget(dlg, "prefs.decodeurls"), d->prefs.decodeUserVisibleURLs); |
2450 | setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl); | 2542 | setText_InputWidget(findChild_Widget(dlg, "prefs.searchurl"), &d->prefs.searchUrl); |
2451 | setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.caFile); | 2543 | setText_InputWidget(findChild_Widget(dlg, "prefs.ca.file"), &d->prefs.caFile); |
@@ -2532,11 +2624,16 @@ iBool handleCommand_App(const char *cmd) { | |||
2532 | return iTrue; | 2624 | return iTrue; |
2533 | } | 2625 | } |
2534 | else if (equal_Command(cmd, "feeds.update.started")) { | 2626 | else if (equal_Command(cmd, "feeds.update.started")) { |
2535 | showCollapsed_Widget(findWidget_App("feeds.progress"), iTrue); | 2627 | iAnyObject *prog = findWidget_Root("feeds.progress"); |
2628 | const iWidget *navBar = findWidget_Root("navbar"); | ||
2629 | updateTextAndResizeWidthCStr_LabelWidget( | ||
2630 | prog, flags_Widget(navBar) & tight_WidgetFlag || deviceType_App() == phone_AppDeviceType ? | ||
2631 | "\u2605" : "\u2605 ${status.feeds}"); | ||
2632 | showCollapsed_Widget(prog, iTrue); | ||
2536 | return iFalse; | 2633 | return iFalse; |
2537 | } | 2634 | } |
2538 | else if (equal_Command(cmd, "feeds.update.finished")) { | 2635 | else if (equal_Command(cmd, "feeds.update.finished")) { |
2539 | showCollapsed_Widget(findWidget_App("feeds.progress"), iFalse); | 2636 | showCollapsed_Widget(findWidget_Root("feeds.progress"), iFalse); |
2540 | refreshFinished_Feeds(); | 2637 | refreshFinished_Feeds(); |
2541 | postRefresh_App(); | 2638 | postRefresh_App(); |
2542 | return iFalse; | 2639 | return iFalse; |
@@ -86,6 +86,7 @@ uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ | |||
86 | iBool isLandscape_App (void); | 86 | iBool isLandscape_App (void); |
87 | iLocalDef iBool isPortrait_App (void) { return !isLandscape_App(); } | 87 | iLocalDef iBool isPortrait_App (void) { return !isLandscape_App(); } |
88 | enum iAppDeviceType deviceType_App (void); | 88 | enum iAppDeviceType deviceType_App (void); |
89 | iLocalDef iBool isPortraitPhone_App (void) { return isPortrait_App() && deviceType_App() == phone_AppDeviceType; } | ||
89 | iGmCerts * certs_App (void); | 90 | iGmCerts * certs_App (void); |
90 | iVisited * visited_App (void); | 91 | iVisited * visited_App (void); |
91 | iBookmarks * bookmarks_App (void); | 92 | iBookmarks * bookmarks_App (void); |
@@ -96,6 +97,7 @@ iObjectList * listDocuments_App (const iRoot *rootOrNull); /* NULL for a | |||
96 | iStringSet * listOpenURLs_App (void); /* all tabs */ | 97 | iStringSet * listOpenURLs_App (void); /* all tabs */ |
97 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); | 98 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); |
98 | void trimCache_App (void); | 99 | void trimCache_App (void); |
100 | void trimMemory_App (void); | ||
99 | 101 | ||
100 | iDocumentWidget * document_Root (iRoot *); | 102 | iDocumentWidget * document_Root (iRoot *); |
101 | 103 | ||
diff --git a/src/audio/player.c b/src/audio/player.c index 9e026561..94bcd065 100644 --- a/src/audio/player.c +++ b/src/audio/player.c | |||
@@ -455,6 +455,8 @@ struct Impl_Player { | |||
455 | iAVFAudioPlayer * avfPlayer; /* iOS */ | 455 | iAVFAudioPlayer * avfPlayer; /* iOS */ |
456 | }; | 456 | }; |
457 | 457 | ||
458 | static iPlayer *activePlayer_; | ||
459 | |||
458 | iDefineTypeConstruction(Player) | 460 | iDefineTypeConstruction(Player) |
459 | 461 | ||
460 | static size_t sampleSize_Player_(const iPlayer *d) { | 462 | static size_t sampleSize_Player_(const iPlayer *d) { |
@@ -655,8 +657,14 @@ void deinit_Player(iPlayer *d) { | |||
655 | #if defined (iPlatformAppleMobile) | 657 | #if defined (iPlatformAppleMobile) |
656 | if (d->avfPlayer) { | 658 | if (d->avfPlayer) { |
657 | delete_AVFAudioPlayer(d->avfPlayer); | 659 | delete_AVFAudioPlayer(d->avfPlayer); |
660 | if (activePlayer_ == d) { | ||
661 | clearNowPlayingInfo_iOS(); | ||
662 | } | ||
658 | } | 663 | } |
659 | #endif | 664 | #endif |
665 | if (activePlayer_ == d) { | ||
666 | activePlayer_ = NULL; | ||
667 | } | ||
660 | } | 668 | } |
661 | 669 | ||
662 | iBool isStarted_Player(const iPlayer *d) { | 670 | iBool isStarted_Player(const iPlayer *d) { |
@@ -724,6 +732,13 @@ void updateSourceData_Player(iPlayer *d, const iString *mimeType, const iBlock * | |||
724 | unlock_Mutex(&input->mtx); | 732 | unlock_Mutex(&input->mtx); |
725 | } | 733 | } |
726 | 734 | ||
735 | size_t sourceDataSize_Player(const iPlayer *d) { | ||
736 | lock_Mutex(&d->data->mtx); | ||
737 | const size_t size = size_Block(&d->data->data); | ||
738 | unlock_Mutex(&d->data->mtx); | ||
739 | return size; | ||
740 | } | ||
741 | |||
727 | iBool start_Player(iPlayer *d) { | 742 | iBool start_Player(iPlayer *d) { |
728 | if (isStarted_Player(d)) { | 743 | if (isStarted_Player(d)) { |
729 | return iFalse; | 744 | return iFalse; |
@@ -732,6 +747,7 @@ iBool start_Player(iPlayer *d) { | |||
732 | if (d->avfPlayer) { | 747 | if (d->avfPlayer) { |
733 | play_AVFAudioPlayer(d->avfPlayer); | 748 | play_AVFAudioPlayer(d->avfPlayer); |
734 | setNotIdle_Player(d); | 749 | setNotIdle_Player(d); |
750 | activePlayer_ = d; | ||
735 | return iTrue; | 751 | return iTrue; |
736 | } | 752 | } |
737 | #endif | 753 | #endif |
@@ -749,6 +765,7 @@ iBool start_Player(iPlayer *d) { | |||
749 | d->decoder->gain = d->volume; | 765 | d->decoder->gain = d->volume; |
750 | SDL_PauseAudioDevice(d->device, SDL_FALSE); | 766 | SDL_PauseAudioDevice(d->device, SDL_FALSE); |
751 | setNotIdle_Player(d); | 767 | setNotIdle_Player(d); |
768 | activePlayer_ = d; | ||
752 | return iTrue; | 769 | return iTrue; |
753 | } | 770 | } |
754 | 771 | ||
@@ -882,3 +899,7 @@ iString *metadataLabel_Player(const iPlayer *d) { | |||
882 | } | 899 | } |
883 | return meta; | 900 | return meta; |
884 | } | 901 | } |
902 | |||
903 | iPlayer *active_Player(void) { | ||
904 | return activePlayer_; | ||
905 | } | ||
diff --git a/src/audio/player.h b/src/audio/player.h index 8753d811..ca307dc4 100644 --- a/src/audio/player.h +++ b/src/audio/player.h | |||
@@ -48,6 +48,7 @@ enum iPlayerTag { | |||
48 | 48 | ||
49 | void updateSourceData_Player (iPlayer *, const iString *mimeType, const iBlock *data, | 49 | void updateSourceData_Player (iPlayer *, const iString *mimeType, const iBlock *data, |
50 | enum iPlayerUpdate update); | 50 | enum iPlayerUpdate update); |
51 | size_t sourceDataSize_Player (const iPlayer *); | ||
51 | 52 | ||
52 | iBool start_Player (iPlayer *); | 53 | iBool start_Player (iPlayer *); |
53 | void stop_Player (iPlayer *); | 54 | void stop_Player (iPlayer *); |
@@ -67,3 +68,5 @@ float streamProgress_Player (const iPlayer *); /* normalized 0...1 */ | |||
67 | 68 | ||
68 | uint32_t idleTimeMs_Player (const iPlayer *); | 69 | uint32_t idleTimeMs_Player (const iPlayer *); |
69 | iString * metadataLabel_Player (const iPlayer *); | 70 | iString * metadataLabel_Player (const iPlayer *); |
71 | |||
72 | iPlayer * active_Player (void); | ||
@@ -24,18 +24,26 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | 24 | ||
25 | #include "lang.h" | 25 | #include "lang.h" |
26 | 26 | ||
27 | enum iSourceFormat { | ||
28 | undefined_SourceFormat = -1, | ||
29 | gemini_SourceFormat = 0, | ||
30 | plainText_SourceFormat, | ||
31 | }; | ||
32 | |||
27 | enum iFileVersion { | 33 | enum iFileVersion { |
28 | initial_FileVersion = 0, | 34 | initial_FileVersion = 0, |
29 | addedResponseTimestamps_FileVersion = 1, | 35 | addedResponseTimestamps_FileVersion = 1, |
30 | multipleRoots_FileVersion = 2, | 36 | multipleRoots_FileVersion = 2, |
31 | serializedSidebarState_FileVersion = 3, | 37 | serializedSidebarState_FileVersion = 3, |
38 | addedRecentUrlFlags_FileVersion = 4, | ||
32 | /* meta */ | 39 | /* meta */ |
33 | idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ | 40 | idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ |
34 | latest_FileVersion = 3, | 41 | latest_FileVersion = 4, |
35 | }; | 42 | }; |
36 | 43 | ||
37 | /* Icons */ | 44 | /* Icons */ |
38 | 45 | ||
46 | #define menu_Icon "\U0001d362" | ||
39 | #define rightArrowhead_Icon "\u27a4" | 47 | #define rightArrowhead_Icon "\u27a4" |
40 | #define leftArrowhead_Icon "\u27a4" | 48 | #define leftArrowhead_Icon "\u27a4" |
41 | #define warning_Icon "\u26a0" | 49 | #define warning_Icon "\u26a0" |
@@ -104,6 +112,9 @@ enum iFileVersion { | |||
104 | # define shiftReturn_Icon shift_Icon " " return_Icon | 112 | # define shiftReturn_Icon shift_Icon " " return_Icon |
105 | #endif | 113 | #endif |
106 | 114 | ||
115 | #if defined (iPlatformAppleDesktop) | ||
116 | # define iHaveNativeMenus | ||
117 | #endif | ||
107 | 118 | ||
108 | /* UI labels that depend on the platform */ | 119 | /* UI labels that depend on the platform */ |
109 | 120 | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index 002494b6..f15d9d1d 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -21,6 +21,7 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | 21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ |
22 | 22 | ||
23 | #include "gmdocument.h" | 23 | #include "gmdocument.h" |
24 | #include "gmtypesetter.h" | ||
24 | #include "gmutil.h" | 25 | #include "gmutil.h" |
25 | #include "lang.h" | 26 | #include "lang.h" |
26 | #include "ui/color.h" | 27 | #include "ui/color.h" |
@@ -48,7 +49,7 @@ iBool isDark_GmDocumentTheme(enum iGmDocumentTheme d) { | |||
48 | iDeclareType(GmLink) | 49 | iDeclareType(GmLink) |
49 | 50 | ||
50 | struct Impl_GmLink { | 51 | struct Impl_GmLink { |
51 | iString url; | 52 | iString url; /* resolved */ |
52 | iRangecc urlRange; /* URL in the source */ | 53 | iRangecc urlRange; /* URL in the source */ |
53 | iRangecc labelRange; /* label in the source */ | 54 | iRangecc labelRange; /* label in the source */ |
54 | iRangecc labelIcon; /* special icon defined in the label text */ | 55 | iRangecc labelIcon; /* special icon defined in the label text */ |
@@ -74,9 +75,10 @@ iDefineTypeConstruction(GmLink) | |||
74 | 75 | ||
75 | struct Impl_GmDocument { | 76 | struct Impl_GmDocument { |
76 | iObject object; | 77 | iObject object; |
77 | enum iGmDocumentFormat format; | 78 | enum iSourceFormat format; |
78 | iString source; | 79 | iString unormSource; /* unnormalized source */ |
79 | iString url; /* for resolving relative links */ | 80 | iString source; /* normalized source */ |
81 | iString url; /* for resolving relative links */ | ||
80 | iString localHost; | 82 | iString localHost; |
81 | iInt2 size; | 83 | iInt2 size; |
82 | iArray layout; /* contents of source, laid out in document space */ | 84 | iArray layout; /* contents of source, laid out in document space */ |
@@ -90,12 +92,14 @@ struct Impl_GmDocument { | |||
90 | iChar siteIcon; | 92 | iChar siteIcon; |
91 | iMedia * media; | 93 | iMedia * media; |
92 | iStringSet *openURLs; /* currently open URLs for highlighting links */ | 94 | iStringSet *openURLs; /* currently open URLs for highlighting links */ |
95 | iBool isPaletteValid; | ||
96 | iColor palette[tmMax_ColorId]; /* copy of the color palette */ | ||
93 | }; | 97 | }; |
94 | 98 | ||
95 | iDefineObjectConstruction(GmDocument) | 99 | iDefineObjectConstruction(GmDocument) |
96 | 100 | ||
97 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { | 101 | static enum iGmLineType lineType_GmDocument_(const iGmDocument *d, const iRangecc line) { |
98 | if (d->format == plainText_GmDocumentFormat) { | 102 | if (d->format == plainText_SourceFormat) { |
99 | return text_GmLineType; | 103 | return text_GmLineType; |
100 | } | 104 | } |
101 | return lineType_Rangecc(line); | 105 | return lineType_Rangecc(line); |
@@ -314,7 +318,7 @@ static void linkContentWasLaidOut_GmDocument_(iGmDocument *d, const iGmMediaInfo | |||
314 | 318 | ||
315 | static iBool isNormalized_GmDocument_(const iGmDocument *d) { | 319 | static iBool isNormalized_GmDocument_(const iGmDocument *d) { |
316 | const iPrefs *prefs = prefs_App(); | 320 | const iPrefs *prefs = prefs_App(); |
317 | if (d->format == plainText_GmDocumentFormat) { | 321 | if (d->format == plainText_SourceFormat) { |
318 | return iTrue; /* tabs are always normalized in plain text */ | 322 | return iTrue; /* tabs are always normalized in plain text */ |
319 | } | 323 | } |
320 | if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) { | 324 | if (startsWithCase_String(&d->url, "gemini:") && prefs->monospaceGemini) { |
@@ -444,12 +448,15 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
444 | enum iGmLineType prevType = text_GmLineType; | 448 | enum iGmLineType prevType = text_GmLineType; |
445 | enum iGmLineType prevNonBlankType = text_GmLineType; | 449 | enum iGmLineType prevNonBlankType = text_GmLineType; |
446 | iBool followsBlank = iFalse; | 450 | iBool followsBlank = iFalse; |
447 | if (d->format == plainText_GmDocumentFormat) { | 451 | if (d->format == plainText_SourceFormat) { |
448 | isPreformat = iTrue; | 452 | isPreformat = iTrue; |
449 | isFirstText = iFalse; | 453 | isFirstText = iFalse; |
450 | } | 454 | } |
451 | while (nextSplit_Rangecc(content, "\n", &contentLine)) { | 455 | while (nextSplit_Rangecc(content, "\n", &contentLine)) { |
452 | iRangecc line = contentLine; /* `line` will be trimmed later; would confuse nextSplit */ | 456 | iRangecc line = contentLine; /* `line` will be trimmed; modifying would confuse `nextSplit_Rangecc` */ |
457 | if (*line.end == '\r') { | ||
458 | line.end--; /* trim CR always */ | ||
459 | } | ||
453 | iGmRun run = { .color = white_ColorId }; | 460 | iGmRun run = { .color = white_ColorId }; |
454 | enum iGmLineType type; | 461 | enum iGmLineType type; |
455 | float indent = 0.0f; | 462 | float indent = 0.0f; |
@@ -510,14 +517,14 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
510 | if (contentLine.start == content.start) { | 517 | if (contentLine.start == content.start) { |
511 | prevType = type; | 518 | prevType = type; |
512 | } | 519 | } |
513 | if (d->format == gemini_GmDocumentFormat && | 520 | if (d->format == gemini_SourceFormat && |
514 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { | 521 | startsWithSc_Rangecc(line, "```", &iCaseSensitive)) { |
515 | isPreformat = iFalse; | 522 | isPreformat = iFalse; |
516 | addSiteBanner = iFalse; /* overrides the banner */ | 523 | addSiteBanner = iFalse; /* overrides the banner */ |
517 | continue; | 524 | continue; |
518 | } | 525 | } |
519 | run.preId = preId; | 526 | run.preId = preId; |
520 | run.font = (d->format == plainText_GmDocumentFormat ? regularMonospace_FontId : preFont); | 527 | run.font = (d->format == plainText_SourceFormat ? regularMonospace_FontId : preFont); |
521 | indent = indents[type]; | 528 | indent = indents[type]; |
522 | } | 529 | } |
523 | if (addSiteBanner) { | 530 | if (addSiteBanner) { |
@@ -590,7 +597,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
590 | } | 597 | } |
591 | } | 598 | } |
592 | /* Folded blocks are represented by a single run with the alt text. */ | 599 | /* Folded blocks are represented by a single run with the alt text. */ |
593 | if (isPreformat && d->format != plainText_GmDocumentFormat) { | 600 | if (isPreformat && d->format != plainText_SourceFormat) { |
594 | const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); | 601 | const iGmPreMeta *meta = constAt_Array(&d->preMeta, preId - 1); |
595 | if (meta->flags & folded_GmPreMetaFlag) { | 602 | if (meta->flags & folded_GmPreMetaFlag) { |
596 | const iBool isBlank = isEmpty_Range(&meta->altText); | 603 | const iBool isBlank = isEmpty_Range(&meta->altText); |
@@ -686,7 +693,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
686 | pushBack_Array(&d->layout, &icon); | 693 | pushBack_Array(&d->layout, &icon); |
687 | } | 694 | } |
688 | run.color = colors[type]; | 695 | run.color = colors[type]; |
689 | if (d->format == plainText_GmDocumentFormat) { | 696 | if (d->format == plainText_SourceFormat) { |
690 | run.color = colors[text_GmLineType]; | 697 | run.color = colors[text_GmLineType]; |
691 | } | 698 | } |
692 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ | 699 | /* Special formatting for the first paragraph (e.g., subtitle, introduction, or lede). */ |
@@ -715,8 +722,8 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
715 | type == quote_GmLineType ? 4 : 0); | 722 | type == quote_GmLineType ? 4 : 0); |
716 | } | 723 | } |
717 | const iBool isWordWrapped = | 724 | const iBool isWordWrapped = |
718 | (d->format == plainText_GmDocumentFormat ? prefs->plainTextWrap : !isPreformat); | 725 | (d->format == plainText_SourceFormat ? prefs->plainTextWrap : !isPreformat); |
719 | if (isPreformat && d->format != plainText_GmDocumentFormat) { | 726 | if (isPreformat && d->format != plainText_SourceFormat) { |
720 | /* Remember the top left coordinates of the block (first line of block). */ | 727 | /* Remember the top left coordinates of the block (first line of block). */ |
721 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); | 728 | iGmPreMeta *meta = at_Array(&d->preMeta, preId - 1); |
722 | if (~meta->flags & topLeft_GmPreMetaFlag) { | 729 | if (~meta->flags & topLeft_GmPreMetaFlag) { |
@@ -869,10 +876,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
869 | } | 876 | } |
870 | } | 877 | } |
871 | } | 878 | } |
879 | printf("[GmDocument] layout size: %zu runs (%zu bytes)\n", | ||
880 | size_Array(&d->layout), size_Array(&d->layout) * sizeof(iGmRun)); | ||
872 | } | 881 | } |
873 | 882 | ||
874 | void init_GmDocument(iGmDocument *d) { | 883 | void init_GmDocument(iGmDocument *d) { |
875 | d->format = gemini_GmDocumentFormat; | 884 | d->format = gemini_SourceFormat; |
885 | init_String(&d->unormSource); | ||
876 | init_String(&d->source); | 886 | init_String(&d->source); |
877 | init_String(&d->url); | 887 | init_String(&d->url); |
878 | init_String(&d->localHost); | 888 | init_String(&d->localHost); |
@@ -888,6 +898,8 @@ void init_GmDocument(iGmDocument *d) { | |||
888 | d->siteIcon = 0; | 898 | d->siteIcon = 0; |
889 | d->media = new_Media(); | 899 | d->media = new_Media(); |
890 | d->openURLs = NULL; | 900 | d->openURLs = NULL; |
901 | d->isPaletteValid = iFalse; | ||
902 | iZap(d->palette); | ||
891 | } | 903 | } |
892 | 904 | ||
893 | void deinit_GmDocument(iGmDocument *d) { | 905 | void deinit_GmDocument(iGmDocument *d) { |
@@ -903,6 +915,7 @@ void deinit_GmDocument(iGmDocument *d) { | |||
903 | deinit_String(&d->localHost); | 915 | deinit_String(&d->localHost); |
904 | deinit_String(&d->url); | 916 | deinit_String(&d->url); |
905 | deinit_String(&d->source); | 917 | deinit_String(&d->source); |
918 | deinit_String(&d->unormSource); | ||
906 | } | 919 | } |
907 | 920 | ||
908 | iMedia *media_GmDocument(iGmDocument *d) { | 921 | iMedia *media_GmDocument(iGmDocument *d) { |
@@ -913,6 +926,11 @@ const iMedia *constMedia_GmDocument(const iGmDocument *d) { | |||
913 | return d->media; | 926 | return d->media; |
914 | } | 927 | } |
915 | 928 | ||
929 | const iString *url_GmDocument(const iGmDocument *d) { | ||
930 | return &d->url; | ||
931 | } | ||
932 | |||
933 | #if 0 | ||
916 | void reset_GmDocument(iGmDocument *d) { | 934 | void reset_GmDocument(iGmDocument *d) { |
917 | clear_Media(d->media); | 935 | clear_Media(d->media); |
918 | clearLinks_GmDocument_(d); | 936 | clearLinks_GmDocument_(d); |
@@ -921,8 +939,11 @@ void reset_GmDocument(iGmDocument *d) { | |||
921 | clear_Array(&d->preMeta); | 939 | clear_Array(&d->preMeta); |
922 | clear_String(&d->url); | 940 | clear_String(&d->url); |
923 | clear_String(&d->localHost); | 941 | clear_String(&d->localHost); |
942 | clear_String(&d->source); | ||
943 | clear_String(&d->unormSource); | ||
924 | d->themeSeed = 0; | 944 | d->themeSeed = 0; |
925 | } | 945 | } |
946 | #endif | ||
926 | 947 | ||
927 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { | 948 | static void setDerivedThemeColors_(enum iGmDocumentTheme theme) { |
928 | set_Color(tmQuoteIcon_ColorId, | 949 | set_Color(tmQuoteIcon_ColorId, |
@@ -1395,9 +1416,23 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | |||
1395 | } | 1416 | } |
1396 | printf("---\n"); | 1417 | printf("---\n"); |
1397 | #endif | 1418 | #endif |
1419 | /* Color functions operate on the global palette for convenience, but we may need to switch | ||
1420 | palettes on the fly if more than one GmDocument is being displayed simultaneously. */ | ||
1421 | memcpy(d->palette, get_Root()->tmPalette, sizeof(d->palette)); | ||
1422 | d->isPaletteValid = iTrue; | ||
1398 | } | 1423 | } |
1399 | 1424 | ||
1400 | void setFormat_GmDocument(iGmDocument *d, enum iGmDocumentFormat format) { | 1425 | void makePaletteGlobal_GmDocument(const iGmDocument *d) { |
1426 | if (d->isPaletteValid) { | ||
1427 | memcpy(get_Root()->tmPalette, d->palette, sizeof(d->palette)); | ||
1428 | } | ||
1429 | } | ||
1430 | |||
1431 | void invalidatePalette_GmDocument(iGmDocument *d) { | ||
1432 | d->isPaletteValid = iFalse; | ||
1433 | } | ||
1434 | |||
1435 | void setFormat_GmDocument(iGmDocument *d, enum iSourceFormat format) { | ||
1401 | d->format = format; | 1436 | d->format = format; |
1402 | } | 1437 | } |
1403 | 1438 | ||
@@ -1442,10 +1477,12 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1442 | iRangecc src = range_String(&d->source); | 1477 | iRangecc src = range_String(&d->source); |
1443 | iRangecc line = iNullRange; | 1478 | iRangecc line = iNullRange; |
1444 | iBool isPreformat = iFalse; | 1479 | iBool isPreformat = iFalse; |
1445 | if (d->format == plainText_GmDocumentFormat) { | 1480 | if (d->format == plainText_SourceFormat) { |
1446 | isPreformat = iTrue; /* Cannot be turned off. */ | 1481 | isPreformat = iTrue; /* Cannot be turned off. */ |
1447 | } | 1482 | } |
1448 | const int preTabWidth = 4; /* TODO: user-configurable parameter */ | 1483 | const int preTabWidth = 4; /* TODO: user-configurable parameter */ |
1484 | iBool wasNormalized = iFalse; | ||
1485 | iBool hasTabs = iFalse; | ||
1449 | while (nextSplit_Rangecc(src, "\n", &line)) { | 1486 | while (nextSplit_Rangecc(src, "\n", &line)) { |
1450 | if (isPreformat) { | 1487 | if (isPreformat) { |
1451 | /* Replace any tab characters with spaces for visualization. */ | 1488 | /* Replace any tab characters with spaces for visualization. */ |
@@ -1456,13 +1493,19 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1456 | while (numSpaces-- > 0) { | 1493 | while (numSpaces-- > 0) { |
1457 | appendCStrN_String(normalized, " ", 1); | 1494 | appendCStrN_String(normalized, " ", 1); |
1458 | } | 1495 | } |
1496 | hasTabs = iTrue; | ||
1497 | wasNormalized = iTrue; | ||
1459 | } | 1498 | } |
1460 | else if (*ch != '\r') { | 1499 | else if (*ch != '\v') { |
1461 | appendCStrN_String(normalized, ch, 1); | 1500 | appendCStrN_String(normalized, ch, 1); |
1462 | } | 1501 | } |
1502 | else { | ||
1503 | hasTabs = iTrue; | ||
1504 | wasNormalized = iTrue; | ||
1505 | } | ||
1463 | } | 1506 | } |
1464 | appendCStr_String(normalized, "\n"); | 1507 | appendCStr_String(normalized, "\n"); |
1465 | if (d->format == gemini_GmDocumentFormat && | 1508 | if (d->format == gemini_SourceFormat && |
1466 | lineType_GmDocument_(d, line) == preformatted_GmLineType) { | 1509 | lineType_GmDocument_(d, line) == preformatted_GmLineType) { |
1467 | isPreformat = iFalse; | 1510 | isPreformat = iFalse; |
1468 | } | 1511 | } |
@@ -1478,7 +1521,10 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1478 | int spaceCount = 0; | 1521 | int spaceCount = 0; |
1479 | for (const char *ch = line.start; ch != line.end; ch++) { | 1522 | for (const char *ch = line.start; ch != line.end; ch++) { |
1480 | char c = *ch; | 1523 | char c = *ch; |
1481 | if (c == '\r') continue; | 1524 | if (c == '\v') { |
1525 | wasNormalized = iTrue; | ||
1526 | continue; | ||
1527 | } | ||
1482 | if (isNormalizableSpace_(c)) { | 1528 | if (isNormalizableSpace_(c)) { |
1483 | if (isPrevSpace) { | 1529 | if (isPrevSpace) { |
1484 | if (++spaceCount == 8) { | 1530 | if (++spaceCount == 8) { |
@@ -1487,9 +1533,13 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1487 | popBack_Block(&normalized->chars); | 1533 | popBack_Block(&normalized->chars); |
1488 | pushBack_Block(&normalized->chars, '\t'); | 1534 | pushBack_Block(&normalized->chars, '\t'); |
1489 | } | 1535 | } |
1536 | wasNormalized = iTrue; | ||
1490 | continue; /* skip repeated spaces */ | 1537 | continue; /* skip repeated spaces */ |
1491 | } | 1538 | } |
1492 | c = ' '; | 1539 | if (c != ' ') { |
1540 | c = ' '; | ||
1541 | wasNormalized = iTrue; | ||
1542 | } | ||
1493 | isPrevSpace = iTrue; | 1543 | isPrevSpace = iTrue; |
1494 | } | 1544 | } |
1495 | else { | 1545 | else { |
@@ -1500,8 +1550,14 @@ static void normalize_GmDocument(iGmDocument *d) { | |||
1500 | } | 1550 | } |
1501 | appendCStr_String(normalized, "\n"); | 1551 | appendCStr_String(normalized, "\n"); |
1502 | } | 1552 | } |
1553 | printf("hasTabs: %d\n", hasTabs); | ||
1554 | printf("wasNormalized: %d\n", wasNormalized); | ||
1555 | fflush(stdout); | ||
1503 | set_String(&d->source, collect_String(normalized)); | 1556 | set_String(&d->source, collect_String(normalized)); |
1504 | normalize_String(&d->source); /* NFC */ | 1557 | normalize_String(&d->source); /* NFC */ |
1558 | printf("orig:%zu norm:%zu\n", size_String(&d->unormSource), size_String(&d->source)); | ||
1559 | /* normalized source has an extra newline at the end */ | ||
1560 | // iAssert(wasNormalized || equal_String(&d->unormSource, &d->source)); | ||
1505 | } | 1561 | } |
1506 | 1562 | ||
1507 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { | 1563 | void setUrl_GmDocument(iGmDocument *d, const iString *url) { |
@@ -1512,8 +1568,18 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { | |||
1512 | updateIconBasedOnUrl_GmDocument_(d); | 1568 | updateIconBasedOnUrl_GmDocument_(d); |
1513 | } | 1569 | } |
1514 | 1570 | ||
1515 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width) { | 1571 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width, |
1516 | set_String(&d->source, source); | 1572 | enum iGmDocumentUpdate updateType) { |
1573 | printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n", | ||
1574 | size_String(source), width, updateType == final_GmDocumentUpdate); | ||
1575 | if (size_String(source) == size_String(&d->unormSource)) { | ||
1576 | iAssert(equal_String(source, &d->unormSource)); | ||
1577 | printf("[GmDocument] source is unchanged!\n"); | ||
1578 | return; /* Nothing to do. */ | ||
1579 | } | ||
1580 | set_String(&d->unormSource, source); | ||
1581 | /* Normalize. */ | ||
1582 | set_String(&d->source, &d->unormSource); | ||
1517 | if (isNormalized_GmDocument_(d)) { | 1583 | if (isNormalized_GmDocument_(d)) { |
1518 | normalize_GmDocument(d); | 1584 | normalize_GmDocument(d); |
1519 | } | 1585 | } |
@@ -1615,6 +1681,14 @@ const iString *source_GmDocument(const iGmDocument *d) { | |||
1615 | return &d->source; | 1681 | return &d->source; |
1616 | } | 1682 | } |
1617 | 1683 | ||
1684 | size_t memorySize_GmDocument(const iGmDocument *d) { | ||
1685 | return size_String(&d->unormSource) + | ||
1686 | size_String(&d->source) + | ||
1687 | size_Array(&d->layout) * sizeof(iGmRun) + | ||
1688 | size_Array(&d->links) * sizeof(iGmLink) + | ||
1689 | memorySize_Media(d->media); | ||
1690 | } | ||
1691 | |||
1618 | iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { | 1692 | iRangecc findText_GmDocument(const iGmDocument *d, const iString *text, const char *start) { |
1619 | const char * src = constBegin_String(&d->source); | 1693 | const char * src = constBegin_String(&d->source); |
1620 | const size_t startPos = (start ? start - src : 0); | 1694 | const size_t startPos = (start ? start - src : 0); |
diff --git a/src/gmdocument.h b/src/gmdocument.h index fcbb7e59..831459d8 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "defs.h" | ||
25 | #include "gmutil.h" | 26 | #include "gmutil.h" |
26 | #include "media.h" | 27 | #include "media.h" |
27 | 28 | ||
@@ -125,12 +126,12 @@ enum iGmRunMediaType { | |||
125 | 126 | ||
126 | struct Impl_GmRun { | 127 | struct Impl_GmRun { |
127 | iRangecc text; | 128 | iRangecc text; |
129 | iRect bounds; /* used for hit testing, may extend to edges */ | ||
130 | iRect visBounds; /* actual visual bounds */ | ||
128 | uint8_t font; | 131 | uint8_t font; |
129 | uint8_t color; | 132 | uint8_t color; |
130 | uint8_t flags; | 133 | uint8_t flags; |
131 | uint8_t mediaType; | 134 | uint8_t mediaType; |
132 | iRect bounds; /* used for hit testing, may extend to edges */ | ||
133 | iRect visBounds; /* actual visual bounds */ | ||
134 | uint16_t preId; /* preformatted block ID (sequential) */ | 135 | uint16_t preId; /* preformatted block ID (sequential) */ |
135 | iGmLinkId linkId; /* zero for non-links */ | 136 | iGmLinkId linkId; /* zero for non-links */ |
136 | uint16_t mediaId; /* zero if not an image */ | 137 | uint16_t mediaId; /* zero if not an image */ |
@@ -148,34 +149,37 @@ iRangecc findLoc_GmRun (const iGmRun *, iInt2 pos); | |||
148 | iDeclareClass(GmDocument) | 149 | iDeclareClass(GmDocument) |
149 | iDeclareObjectConstruction(GmDocument) | 150 | iDeclareObjectConstruction(GmDocument) |
150 | 151 | ||
151 | enum iGmDocumentFormat { | ||
152 | undefined_GmDocumentFormat = -1, | ||
153 | gemini_GmDocumentFormat = 0, | ||
154 | plainText_GmDocumentFormat, | ||
155 | }; | ||
156 | |||
157 | enum iGmDocumentBanner { | 152 | enum iGmDocumentBanner { |
158 | none_GmDocumentBanner, | 153 | none_GmDocumentBanner, |
159 | siteDomain_GmDocumentBanner, | 154 | siteDomain_GmDocumentBanner, |
160 | certificateWarning_GmDocumentBanner, | 155 | certificateWarning_GmDocumentBanner, |
161 | }; | 156 | }; |
162 | 157 | ||
158 | enum iGmDocumentUpdate { | ||
159 | partial_GmDocumentUpdate, /* appending more content */ | ||
160 | final_GmDocumentUpdate, /* process all lines, including the last one if not terminated */ | ||
161 | }; | ||
162 | |||
163 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); | 163 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); |
164 | void setFormat_GmDocument (iGmDocument *, enum iGmDocumentFormat format); | 164 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); |
165 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); | 165 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); |
166 | void setWidth_GmDocument (iGmDocument *, int width); | 166 | void setWidth_GmDocument (iGmDocument *, int width); |
167 | void redoLayout_GmDocument (iGmDocument *); | 167 | void redoLayout_GmDocument (iGmDocument *); |
168 | iBool updateOpenURLs_GmDocument(iGmDocument *); | 168 | iBool updateOpenURLs_GmDocument(iGmDocument *); |
169 | void setUrl_GmDocument (iGmDocument *, const iString *url); | 169 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
170 | void setSource_GmDocument (iGmDocument *, const iString *source, int width); | 170 | void setSource_GmDocument (iGmDocument *, const iString *source, int width, |
171 | enum iGmDocumentUpdate updateType); | ||
171 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); | 172 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); |
173 | void invalidatePalette_GmDocument(iGmDocument *); | ||
174 | void makePaletteGlobal_GmDocument(const iGmDocument *); /* copies document colors to the global palette */ | ||
172 | 175 | ||
173 | void reset_GmDocument (iGmDocument *); /* free images */ | 176 | //void reset_GmDocument (iGmDocument *); /* free images */ |
174 | 177 | ||
175 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); | 178 | typedef void (*iGmDocumentRenderFunc)(void *, const iGmRun *); |
176 | 179 | ||
177 | iMedia * media_GmDocument (iGmDocument *); | 180 | iMedia * media_GmDocument (iGmDocument *); |
178 | const iMedia * constMedia_GmDocument (const iGmDocument *); | 181 | const iMedia * constMedia_GmDocument (const iGmDocument *); |
182 | const iString * url_GmDocument (const iGmDocument *); | ||
179 | 183 | ||
180 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, | 184 | void render_GmDocument (const iGmDocument *, iRangei visRangeY, |
181 | iGmDocumentRenderFunc render, void *); /* includes partial overlaps */ | 185 | iGmDocumentRenderFunc render, void *); /* includes partial overlaps */ |
@@ -190,6 +194,7 @@ enum iGmDocumentBanner bannerType_GmDocument(const iGmDocument *); | |||
190 | const iString * bannerText_GmDocument (const iGmDocument *); | 194 | const iString * bannerText_GmDocument (const iGmDocument *); |
191 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ | 195 | const iArray * headings_GmDocument (const iGmDocument *); /* array of GmHeadings */ |
192 | const iString * source_GmDocument (const iGmDocument *); | 196 | const iString * source_GmDocument (const iGmDocument *); |
197 | size_t memorySize_GmDocument (const iGmDocument *); /* bytes */ | ||
193 | 198 | ||
194 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); | 199 | iRangecc findText_GmDocument (const iGmDocument *, const iString *text, const char *start); |
195 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); | 200 | iRangecc findTextBefore_GmDocument (const iGmDocument *, const iString *text, const char *before); |
diff --git a/src/gmtypesetter.c b/src/gmtypesetter.c new file mode 100644 index 00000000..29a1bd93 --- /dev/null +++ b/src/gmtypesetter.c | |||
@@ -0,0 +1,25 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "gmtypesetter.h" | ||
24 | #include "gmdocument.h" | ||
25 | |||
diff --git a/src/gmtypesetter.h b/src/gmtypesetter.h new file mode 100644 index 00000000..aba351dd --- /dev/null +++ b/src/gmtypesetter.h | |||
@@ -0,0 +1,41 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include "defs.h" | ||
26 | |||
27 | #include <the_Foundation/array.h> | ||
28 | #include <the_Foundation/string.h> | ||
29 | #include <the_Foundation/vec2.h> | ||
30 | |||
31 | /* GmTypesetter has two jobs: it normalizes incoming source text, and typesets it as a | ||
32 | sequence of GmRuns. New data can be appended progressively. */ | ||
33 | |||
34 | iDeclareType(GmTypesetter) | ||
35 | iDeclareTypeConstruction(GmTypesetter) | ||
36 | |||
37 | void reset_GmTypesetter (iGmTypesetter *, enum iSourceFormat format); | ||
38 | void setWidth_GmTypesetter (iGmTypesetter *, int width); | ||
39 | void addInput_GmTypesetter (iGmTypesetter *, const iString *source); | ||
40 | iBool getRuns_GmTypesetter (iGmTypesetter *, iArray *runs_out); /* returns false when no output generated */ | ||
41 | void skip_GmTypesetter (iGmTypesetter *, int ySkip); | ||
diff --git a/src/gmutil.c b/src/gmutil.c index 3ca93901..7a1ae938 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -556,7 +556,7 @@ const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { | |||
556 | iString *head = newRange_String( | 556 | iString *head = newRange_String( |
557 | (iRangecc){ constBegin_String(url) + fragPos + 1, constEnd_String(url) }); | 557 | (iRangecc){ constBegin_String(url) + fragPos + 1, constEnd_String(url) }); |
558 | format_String(cmd, | 558 | format_String(cmd, |
559 | "open newtab:%d gotourlheading:%s url:%s", | 559 | "open fromsidebar:1 newtab:%d gotourlheading:%s url:%s", |
560 | newTab, | 560 | newTab, |
561 | cstr_String(head), | 561 | cstr_String(head), |
562 | cstr_Rangecc((iRangecc){ constBegin_String(url), | 562 | cstr_Rangecc((iRangecc){ constBegin_String(url), |
@@ -564,7 +564,7 @@ const iString *feedEntryOpenCommand_String(const iString *url, int newTab) { | |||
564 | delete_String(head); | 564 | delete_String(head); |
565 | } | 565 | } |
566 | else { | 566 | else { |
567 | format_String(cmd, "open newtab:%d url:%s", newTab, cstr_String(url)); | 567 | format_String(cmd, "open fromsidebar:1 newtab:%d url:%s", newTab, cstr_String(url)); |
568 | } | 568 | } |
569 | return cmd; | 569 | return cmd; |
570 | } | 570 | } |
diff --git a/src/history.c b/src/history.c index ed8e6725..2cea393d 100644 --- a/src/history.c +++ b/src/history.c | |||
@@ -34,11 +34,14 @@ static const size_t maxStack_History_ = 50; /* back/forward navigable items */ | |||
34 | 34 | ||
35 | void init_RecentUrl(iRecentUrl *d) { | 35 | void init_RecentUrl(iRecentUrl *d) { |
36 | init_String(&d->url); | 36 | init_String(&d->url); |
37 | d->normScrollY = 0; | 37 | d->normScrollY = 0; |
38 | d->cachedResponse = NULL; | 38 | d->cachedResponse = NULL; |
39 | d->cachedDoc = NULL; | ||
40 | d->flags.openedFromSidebar = iFalse; | ||
39 | } | 41 | } |
40 | 42 | ||
41 | void deinit_RecentUrl(iRecentUrl *d) { | 43 | void deinit_RecentUrl(iRecentUrl *d) { |
44 | iRelease(d->cachedDoc); | ||
42 | deinit_String(&d->url); | 45 | deinit_String(&d->url); |
43 | delete_GmResponse(d->cachedResponse); | 46 | delete_GmResponse(d->cachedResponse); |
44 | } | 47 | } |
@@ -48,11 +51,30 @@ iDefineTypeConstruction(RecentUrl) | |||
48 | iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { | 51 | iRecentUrl *copy_RecentUrl(const iRecentUrl *d) { |
49 | iRecentUrl *copy = new_RecentUrl(); | 52 | iRecentUrl *copy = new_RecentUrl(); |
50 | set_String(©->url, &d->url); | 53 | set_String(©->url, &d->url); |
51 | copy->normScrollY = d->normScrollY; | 54 | copy->normScrollY = d->normScrollY; |
52 | copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL; | 55 | copy->cachedResponse = d->cachedResponse ? copy_GmResponse(d->cachedResponse) : NULL; |
56 | copy->cachedDoc = ref_Object(d->cachedDoc); | ||
57 | copy->flags = d->flags; | ||
53 | return copy; | 58 | return copy; |
54 | } | 59 | } |
55 | 60 | ||
61 | size_t cacheSize_RecentUrl(const iRecentUrl *d) { | ||
62 | size_t size = 0; | ||
63 | if (d->cachedResponse) { | ||
64 | size += size_String(&d->cachedResponse->meta); | ||
65 | size += size_Block(&d->cachedResponse->body); | ||
66 | } | ||
67 | return size; | ||
68 | } | ||
69 | |||
70 | size_t memorySize_RecentUrl(const iRecentUrl *d) { | ||
71 | size_t size = cacheSize_RecentUrl(d); | ||
72 | if (d->cachedDoc) { | ||
73 | size += memorySize_GmDocument(d->cachedDoc); | ||
74 | } | ||
75 | return size; | ||
76 | } | ||
77 | |||
56 | /*----------------------------------------------------------------------------------------------*/ | 78 | /*----------------------------------------------------------------------------------------------*/ |
57 | 79 | ||
58 | struct Impl_History { | 80 | struct Impl_History { |
@@ -88,24 +110,45 @@ iHistory *copy_History(const iHistory *d) { | |||
88 | return copy; | 110 | return copy; |
89 | } | 111 | } |
90 | 112 | ||
113 | iMemInfo memoryUsage_History(const iHistory *d) { | ||
114 | iMemInfo mem = { 0, 0 }; | ||
115 | iConstForEach(Array, i, &d->recent) { | ||
116 | const iRecentUrl *item = i.value; | ||
117 | mem.cacheSize += cacheSize_RecentUrl(item); | ||
118 | mem.memorySize += memorySize_RecentUrl(item); | ||
119 | } | ||
120 | return mem; | ||
121 | } | ||
122 | |||
91 | iString *debugInfo_History(const iHistory *d) { | 123 | iString *debugInfo_History(const iHistory *d) { |
92 | iString *str = new_String(); | 124 | iString *str = new_String(); |
93 | format_String(str, | 125 | format_String(str, |
94 | "```\n" | 126 | "```\n" |
95 | "Idx | Size | SP%% | URL\n" | 127 | "Idx | Cache | Memory | SP%% | URL\n" |
96 | "----+---------+-----+-----\n"); | 128 | "----+---------+----------+-----+-----\n"); |
97 | size_t totalSize = 0; | 129 | size_t totalCache = 0; |
130 | size_t totalMemory = 0; | ||
98 | iConstForEach(Array, i, &d->recent) { | 131 | iConstForEach(Array, i, &d->recent) { |
99 | const iRecentUrl *item = i.value; | 132 | const iRecentUrl *item = i.value; |
100 | appendFormat_String( | 133 | appendFormat_String( |
101 | str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1); | 134 | str, " %2zu | ", size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1); |
102 | if (item->cachedResponse) { | 135 | const size_t cacheSize = cacheSize_RecentUrl(item); |
103 | appendFormat_String(str, "%7zu", size_Block(&item->cachedResponse->body)); | 136 | const size_t memSize = memorySize_RecentUrl(item); |
104 | totalSize += size_Block(&item->cachedResponse->body); | 137 | if (cacheSize) { |
138 | appendFormat_String(str, "%7zu", cacheSize); | ||
139 | totalCache += cacheSize; | ||
105 | } | 140 | } |
106 | else { | 141 | else { |
107 | appendFormat_String(str, " --"); | 142 | appendFormat_String(str, " --"); |
108 | } | 143 | } |
144 | appendCStr_String(str, " | "); | ||
145 | if (memSize) { | ||
146 | appendFormat_String(str, "%8zu", memSize); | ||
147 | totalMemory += memSize; | ||
148 | } | ||
149 | else { | ||
150 | appendFormat_String(str, " --"); | ||
151 | } | ||
109 | appendFormat_String(str, | 152 | appendFormat_String(str, |
110 | " | %3d | %s\n", | 153 | " | %3d | %s\n", |
111 | iRound(100.0f * item->normScrollY), | 154 | iRound(100.0f * item->normScrollY), |
@@ -114,8 +157,10 @@ iString *debugInfo_History(const iHistory *d) { | |||
114 | appendFormat_String(str, "\n```\n"); | 157 | appendFormat_String(str, "\n```\n"); |
115 | appendFormat_String(str, | 158 | appendFormat_String(str, |
116 | "Total cached data: %.3f MB\n" | 159 | "Total cached data: %.3f MB\n" |
160 | "Total memory usage: %.3f MB\n" | ||
117 | "Navigation position: %zu\n\n", | 161 | "Navigation position: %zu\n\n", |
118 | totalSize / 1.0e6f, | 162 | totalCache / 1.0e6f, |
163 | totalMemory / 1.0e6f, | ||
119 | d->recentPos); | 164 | d->recentPos); |
120 | return str; | 165 | return str; |
121 | } | 166 | } |
@@ -128,6 +173,7 @@ void serialize_History(const iHistory *d, iStream *outs) { | |||
128 | const iRecentUrl *item = i.value; | 173 | const iRecentUrl *item = i.value; |
129 | serialize_String(&item->url, outs); | 174 | serialize_String(&item->url, outs); |
130 | write32_Stream(outs, item->normScrollY * 1.0e6f); | 175 | write32_Stream(outs, item->normScrollY * 1.0e6f); |
176 | writeU16_Stream(outs, item->flags.openedFromSidebar ? iBit(1) : 0); | ||
131 | if (item->cachedResponse) { | 177 | if (item->cachedResponse) { |
132 | write8_Stream(outs, 1); | 178 | write8_Stream(outs, 1); |
133 | serialize_GmResponse(item->cachedResponse, outs); | 179 | serialize_GmResponse(item->cachedResponse, outs); |
@@ -149,6 +195,12 @@ void deserialize_History(iHistory *d, iStream *ins) { | |||
149 | init_RecentUrl(&item); | 195 | init_RecentUrl(&item); |
150 | deserialize_String(&item.url, ins); | 196 | deserialize_String(&item.url, ins); |
151 | item.normScrollY = (float) read32_Stream(ins) / 1.0e6f; | 197 | item.normScrollY = (float) read32_Stream(ins) / 1.0e6f; |
198 | if (version_Stream(ins) >= addedRecentUrlFlags_FileVersion) { | ||
199 | uint16_t flags = readU16_Stream(ins); | ||
200 | if (flags & iBit(1)) { | ||
201 | item.flags.openedFromSidebar = iTrue; | ||
202 | } | ||
203 | } | ||
152 | if (read8_Stream(ins)) { | 204 | if (read8_Stream(ins)) { |
153 | item.cachedResponse = new_GmResponse(); | 205 | item.cachedResponse = new_GmResponse(); |
154 | deserialize_GmResponse(item.cachedResponse, ins); | 206 | deserialize_GmResponse(item.cachedResponse, ins); |
@@ -241,6 +293,39 @@ void add_History(iHistory *d, const iString *url ){ | |||
241 | unlock_Mutex(d->mtx); | 293 | unlock_Mutex(d->mtx); |
242 | } | 294 | } |
243 | 295 | ||
296 | iBool preceding_History(iHistory *d, iRecentUrl *recent_out) { | ||
297 | iBool ok = iFalse; | ||
298 | lock_Mutex(d->mtx); | ||
299 | if (!isEmpty_Array(&d->recent) && d->recentPos < size_Array(&d->recent) - 1) { | ||
300 | const iRecentUrl *recent = constAt_Array(&d->recent, size_Array(&d->recent) - 1 - | ||
301 | (d->recentPos + 1)); | ||
302 | set_String(&recent_out->url, &recent->url); | ||
303 | recent_out->normScrollY = recent->normScrollY; | ||
304 | iChangeRef(recent_out->cachedDoc, recent->cachedDoc); | ||
305 | /* Cached response is not returned, would involve a deep copy. */ | ||
306 | ok = iTrue; | ||
307 | } | ||
308 | unlock_Mutex(d->mtx); | ||
309 | return ok; | ||
310 | } | ||
311 | |||
312 | #if 0 | ||
313 | iBool following_History(iHistory *d, iRecentUrl *recent_out) { | ||
314 | iBool ok = iFalse; | ||
315 | lock_Mutex(d->mtx); | ||
316 | if (!isEmpty_Array(&d->recent) && d->recentPos > 0) { | ||
317 | const iRecentUrl *recent = constAt_Array(&d->recent, d->recentPos - 1); | ||
318 | set_String(&recent_out->url, &recent->url); | ||
319 | recent_out->normScrollY = recent->normScrollY; | ||
320 | recent_out->cachedDoc = ref_Object(recent->cachedDoc); | ||
321 | /* Cached response is not returned, would involve a deep copy. */ | ||
322 | ok = iTrue; | ||
323 | } | ||
324 | unlock_Mutex(d->mtx); | ||
325 | return ok; | ||
326 | } | ||
327 | #endif | ||
328 | |||
244 | iBool goBack_History(iHistory *d) { | 329 | iBool goBack_History(iHistory *d) { |
245 | lock_Mutex(d->mtx); | 330 | lock_Mutex(d->mtx); |
246 | if (!isEmpty_Array(&d->recent) && d->recentPos < size_Array(&d->recent) - 1) { | 331 | if (!isEmpty_Array(&d->recent) && d->recentPos < size_Array(&d->recent) - 1) { |
@@ -302,19 +387,42 @@ void setCachedResponse_History(iHistory *d, const iGmResponse *response) { | |||
302 | unlock_Mutex(d->mtx); | 387 | unlock_Mutex(d->mtx); |
303 | } | 388 | } |
304 | 389 | ||
390 | void setCachedDocument_History(iHistory *d, iGmDocument *doc, iBool openedFromSidebar) { | ||
391 | lock_Mutex(d->mtx); | ||
392 | iRecentUrl *item = mostRecentUrl_History(d); | ||
393 | if (item) { | ||
394 | iAssert(equal_String(url_GmDocument(doc), &item->url)); | ||
395 | item->flags.openedFromSidebar = openedFromSidebar; | ||
396 | if (item->cachedDoc != doc) { | ||
397 | iRelease(item->cachedDoc); | ||
398 | item->cachedDoc = ref_Object(doc); | ||
399 | } | ||
400 | } | ||
401 | unlock_Mutex(d->mtx); | ||
402 | } | ||
403 | |||
305 | size_t cacheSize_History(const iHistory *d) { | 404 | size_t cacheSize_History(const iHistory *d) { |
306 | size_t cached = 0; | 405 | size_t cached = 0; |
307 | lock_Mutex(d->mtx); | 406 | lock_Mutex(d->mtx); |
308 | iConstForEach(Array, i, &d->recent) { | 407 | iConstForEach(Array, i, &d->recent) { |
309 | const iRecentUrl *url = i.value; | 408 | const iRecentUrl *url = i.value; |
310 | if (url->cachedResponse) { | 409 | cached += cacheSize_RecentUrl(url); |
311 | cached += size_Block(&url->cachedResponse->body); | ||
312 | } | ||
313 | } | 410 | } |
314 | unlock_Mutex(d->mtx); | 411 | unlock_Mutex(d->mtx); |
315 | return cached; | 412 | return cached; |
316 | } | 413 | } |
317 | 414 | ||
415 | size_t memorySize_History(const iHistory *d) { | ||
416 | size_t bytes = 0; | ||
417 | lock_Mutex(d->mtx); | ||
418 | iConstForEach(Array, i, &d->recent) { | ||
419 | const iRecentUrl *url = i.value; | ||
420 | bytes += memorySize_RecentUrl(url); | ||
421 | } | ||
422 | unlock_Mutex(d->mtx); | ||
423 | return bytes; | ||
424 | } | ||
425 | |||
318 | void clearCache_History(iHistory *d) { | 426 | void clearCache_History(iHistory *d) { |
319 | lock_Mutex(d->mtx); | 427 | lock_Mutex(d->mtx); |
320 | iForEach(Array, i, &d->recent) { | 428 | iForEach(Array, i, &d->recent) { |
@@ -323,6 +431,7 @@ void clearCache_History(iHistory *d) { | |||
323 | delete_GmResponse(url->cachedResponse); | 431 | delete_GmResponse(url->cachedResponse); |
324 | url->cachedResponse = NULL; | 432 | url->cachedResponse = NULL; |
325 | } | 433 | } |
434 | iReleasePtr(&url->cachedDoc); /* release all cached documents and media as well */ | ||
326 | } | 435 | } |
327 | unlock_Mutex(d->mtx); | 436 | unlock_Mutex(d->mtx); |
328 | } | 437 | } |
@@ -338,7 +447,7 @@ size_t pruneLeastImportant_History(iHistory *d) { | |||
338 | const iRecentUrl *url = i.value; | 447 | const iRecentUrl *url = i.value; |
339 | if (url->cachedResponse) { | 448 | if (url->cachedResponse) { |
340 | const double urlScore = | 449 | const double urlScore = |
341 | size_Block(&url->cachedResponse->body) * | 450 | cacheSize_RecentUrl(url) * |
342 | pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25); | 451 | pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25); |
343 | if (urlScore > score) { | 452 | if (urlScore > score) { |
344 | chosen = index_ArrayConstIterator(&i); | 453 | chosen = index_ArrayConstIterator(&i); |
@@ -348,14 +457,60 @@ size_t pruneLeastImportant_History(iHistory *d) { | |||
348 | } | 457 | } |
349 | if (chosen != iInvalidPos) { | 458 | if (chosen != iInvalidPos) { |
350 | iRecentUrl *url = at_Array(&d->recent, chosen); | 459 | iRecentUrl *url = at_Array(&d->recent, chosen); |
351 | delta = size_Block(&url->cachedResponse->body); | 460 | delta = cacheSize_RecentUrl(url); |
352 | delete_GmResponse(url->cachedResponse); | 461 | delete_GmResponse(url->cachedResponse); |
353 | url->cachedResponse = NULL; | 462 | url->cachedResponse = NULL; |
463 | iReleasePtr(&url->cachedDoc); | ||
354 | } | 464 | } |
355 | unlock_Mutex(d->mtx); | 465 | unlock_Mutex(d->mtx); |
356 | return delta; | 466 | return delta; |
357 | } | 467 | } |
358 | 468 | ||
469 | size_t pruneLeastImportantMemory_History(iHistory *d) { | ||
470 | size_t delta = 0; | ||
471 | size_t chosen = iInvalidPos; | ||
472 | double score = 0.0f; | ||
473 | iTime now; | ||
474 | initCurrent_Time(&now); | ||
475 | lock_Mutex(d->mtx); | ||
476 | iConstForEach(Array, i, &d->recent) { | ||
477 | const iRecentUrl *url = i.value; | ||
478 | if (d->recentPos == size_Array(&d->recent) - index_ArrayConstIterator(&i) - 1) { | ||
479 | continue; /* Not the current navigation position. */ | ||
480 | } | ||
481 | if (url->cachedDoc) { | ||
482 | const double urlScore = | ||
483 | memorySize_RecentUrl(url) * | ||
484 | (url->cachedResponse | ||
485 | ? pow(secondsSince_Time(&now, &url->cachedResponse->when) / 60.0, 1.25) | ||
486 | : 1.0); | ||
487 | if (urlScore > score) { | ||
488 | chosen = index_ArrayConstIterator(&i); | ||
489 | score = urlScore; | ||
490 | } | ||
491 | } | ||
492 | } | ||
493 | if (chosen != iInvalidPos) { | ||
494 | iRecentUrl *url = at_Array(&d->recent, chosen); | ||
495 | const size_t before = memorySize_RecentUrl(url); | ||
496 | iReleasePtr(&url->cachedDoc); | ||
497 | delta = before - memorySize_RecentUrl(url); | ||
498 | } | ||
499 | unlock_Mutex(d->mtx); | ||
500 | return delta; | ||
501 | } | ||
502 | |||
503 | void invalidateTheme_History(iHistory *d) { | ||
504 | lock_Mutex(d->mtx); | ||
505 | iForEach(Array, i, &d->recent) { | ||
506 | iRecentUrl *r = i.value; | ||
507 | if (r->cachedDoc) { | ||
508 | invalidatePalette_GmDocument(r->cachedDoc); | ||
509 | } | ||
510 | } | ||
511 | unlock_Mutex(d->mtx); | ||
512 | } | ||
513 | |||
359 | const iStringArray *searchContents_History(const iHistory *d, const iRegExp *pattern) { | 514 | const iStringArray *searchContents_History(const iHistory *d, const iRegExp *pattern) { |
360 | iStringArray *urls = iClob(new_StringArray()); | 515 | iStringArray *urls = iClob(new_StringArray()); |
361 | lock_Mutex(d->mtx); | 516 | lock_Mutex(d->mtx); |
diff --git a/src/history.h b/src/history.h index 164a61d6..7dad72df 100644 --- a/src/history.h +++ b/src/history.h | |||
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "gmdocument.h" | ||
25 | #include "gmrequest.h" | 26 | #include "gmrequest.h" |
26 | 27 | ||
27 | #include <the_Foundation/ptrarray.h> | 28 | #include <the_Foundation/ptrarray.h> |
@@ -37,6 +38,17 @@ struct Impl_RecentUrl { | |||
37 | iString url; | 38 | iString url; |
38 | float normScrollY; /* normalized to document height */ | 39 | float normScrollY; /* normalized to document height */ |
39 | iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ | 40 | iGmResponse *cachedResponse; /* kept in memory for quicker back navigation */ |
41 | iGmDocument *cachedDoc; /* cached copy of the presentation: layout and media (not serialized) */ | ||
42 | struct { | ||
43 | uint8_t openedFromSidebar : 1; | ||
44 | } flags; | ||
45 | }; | ||
46 | |||
47 | iDeclareType(MemInfo) | ||
48 | |||
49 | struct Impl_MemInfo { | ||
50 | size_t cacheSize; /* number of bytes stored persistently */ | ||
51 | size_t memorySize; /* number of bytes stored in RAM */ | ||
40 | }; | 52 | }; |
41 | 53 | ||
42 | /*----------------------------------------------------------------------------------------------*/ | 54 | /*----------------------------------------------------------------------------------------------*/ |
@@ -51,13 +63,19 @@ void clear_History (iHistory *); | |||
51 | void add_History (iHistory *, const iString *url); | 63 | void add_History (iHistory *, const iString *url); |
52 | void replace_History (iHistory *, const iString *url); | 64 | void replace_History (iHistory *, const iString *url); |
53 | void setCachedResponse_History (iHistory *, const iGmResponse *response); | 65 | void setCachedResponse_History (iHistory *, const iGmResponse *response); |
66 | void setCachedDocument_History (iHistory *, iGmDocument *doc, iBool openedFromSidebar); | ||
54 | iBool goBack_History (iHistory *); | 67 | iBool goBack_History (iHistory *); |
55 | iBool goForward_History (iHistory *); | 68 | iBool goForward_History (iHistory *); |
69 | iBool preceding_History (iHistory *d, iRecentUrl *recent_out); | ||
70 | //iBool following_History (iHistory *d, iRecentUrl *recent_out); | ||
56 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); | 71 | iRecentUrl *recentUrl_History (iHistory *, size_t pos); |
57 | iRecentUrl *mostRecentUrl_History (iHistory *); | 72 | iRecentUrl *mostRecentUrl_History (iHistory *); |
58 | iRecentUrl *findUrl_History (iHistory *, const iString *url); | 73 | iRecentUrl *findUrl_History (iHistory *, const iString *url); |
59 | void clearCache_History (iHistory *); | 74 | |
60 | size_t pruneLeastImportant_History (iHistory *); | 75 | void clearCache_History (iHistory *); |
76 | size_t pruneLeastImportant_History (iHistory *); | ||
77 | size_t pruneLeastImportantMemory_History (iHistory *); | ||
78 | void invalidateTheme_History (iHistory *); /* theme has changed, cached contents need updating */ | ||
61 | 79 | ||
62 | iBool atLatest_History (const iHistory *); | 80 | iBool atLatest_History (const iHistory *); |
63 | iBool atOldest_History (const iHistory *); | 81 | iBool atOldest_History (const iHistory *); |
@@ -73,6 +91,7 @@ const iRecentUrl * | |||
73 | const iGmResponse * | 91 | const iGmResponse * |
74 | cachedResponse_History (const iHistory *); | 92 | cachedResponse_History (const iHistory *); |
75 | size_t cacheSize_History (const iHistory *); | 93 | size_t cacheSize_History (const iHistory *); |
94 | size_t memorySize_History (const iHistory *); | ||
76 | 95 | ||
77 | iString * debugInfo_History (const iHistory *); | 96 | iString * debugInfo_History (const iHistory *); |
78 | 97 | iMemInfo memoryUsage_History (const iHistory *); | |
@@ -28,6 +28,7 @@ iDeclareType(Window) | |||
28 | 28 | ||
29 | enum iHapticEffect { | 29 | enum iHapticEffect { |
30 | tap_HapticEffect, | 30 | tap_HapticEffect, |
31 | gentleTap_HapticEffect, | ||
31 | }; | 32 | }; |
32 | 33 | ||
33 | void setupApplication_iOS (void); | 34 | void setupApplication_iOS (void); |
@@ -35,6 +36,7 @@ void setupWindow_iOS (iWindow *window); | |||
35 | iBool processEvent_iOS (const SDL_Event *); | 36 | iBool processEvent_iOS (const SDL_Event *); |
36 | void playHapticEffect_iOS (enum iHapticEffect effect); | 37 | void playHapticEffect_iOS (enum iHapticEffect effect); |
37 | void exportDownloadedFile_iOS(const iString *path); | 38 | void exportDownloadedFile_iOS(const iString *path); |
39 | void pickFileForOpening_iOS (void); | ||
38 | 40 | ||
39 | iBool isPhone_iOS (void); | 41 | iBool isPhone_iOS (void); |
40 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); | 42 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); |
@@ -55,3 +57,6 @@ double currentTime_AVFAudioPlayer (const iAVFAudioPlayer *); | |||
55 | double duration_AVFAudioPlayer (const iAVFAudioPlayer *); | 57 | double duration_AVFAudioPlayer (const iAVFAudioPlayer *); |
56 | iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *); | 58 | iBool isStarted_AVFAudioPlayer (const iAVFAudioPlayer *); |
57 | iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *); | 59 | iBool isPaused_AVFAudioPlayer (const iAVFAudioPlayer *); |
60 | |||
61 | void clearNowPlayingInfo_iOS (void); | ||
62 | void updateNowPlayingInfo_iOS (void); | ||
@@ -22,6 +22,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #include "ios.h" | 23 | #include "ios.h" |
24 | #include "app.h" | 24 | #include "app.h" |
25 | #include "audio/player.h" | ||
25 | #include "ui/command.h" | 26 | #include "ui/command.h" |
26 | #include "ui/window.h" | 27 | #include "ui/window.h" |
27 | 28 | ||
@@ -32,9 +33,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | #include <SDL_syswm.h> | 33 | #include <SDL_syswm.h> |
33 | #include <SDL_timer.h> | 34 | #include <SDL_timer.h> |
34 | 35 | ||
35 | #import <UIKit/UIKit.h> | ||
36 | #import <CoreHaptics/CoreHaptics.h> | ||
37 | #import <AVFAudio/AVFAudio.h> | 36 | #import <AVFAudio/AVFAudio.h> |
37 | #import <CoreHaptics/CoreHaptics.h> | ||
38 | #import <UIKit/UIKit.h> | ||
39 | #import <MediaPlayer/MediaPlayer.h> | ||
38 | 40 | ||
39 | static iBool isSystemDarkMode_ = iFalse; | 41 | static iBool isSystemDarkMode_ = iFalse; |
40 | static iBool isPhone_ = iFalse; | 42 | static iBool isPhone_ = iFalse; |
@@ -64,6 +66,7 @@ API_AVAILABLE(ios(13.0)) | |||
64 | @interface HapticState : NSObject | 66 | @interface HapticState : NSObject |
65 | @property (nonatomic, strong) CHHapticEngine *engine; | 67 | @property (nonatomic, strong) CHHapticEngine *engine; |
66 | @property (nonatomic, strong) NSDictionary *tapDef; | 68 | @property (nonatomic, strong) NSDictionary *tapDef; |
69 | @property (nonatomic, strong) NSDictionary *gentleTapDef; | ||
67 | @end | 70 | @end |
68 | 71 | ||
69 | @implementation HapticState | 72 | @implementation HapticState |
@@ -105,26 +108,47 @@ API_AVAILABLE(ios(13.0)) | |||
105 | CHHapticPatternKeyEvent: @{ | 108 | CHHapticPatternKeyEvent: @{ |
106 | CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient, | 109 | CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient, |
107 | CHHapticPatternKeyTime: @0.0, | 110 | CHHapticPatternKeyTime: @0.0, |
108 | CHHapticPatternKeyEventDuration:@0.1 | 111 | CHHapticPatternKeyEventDuration:@0.1, |
112 | CHHapticPatternKeyEventParameters: @[ | ||
113 | @{ | ||
114 | CHHapticPatternKeyParameterID: CHHapticEventParameterIDHapticIntensity, | ||
115 | CHHapticPatternKeyParameterValue: @1.0 | ||
116 | } | ||
117 | ] | ||
118 | }, | ||
119 | }, | ||
120 | ] | ||
121 | }; | ||
122 | self.gentleTapDef = @{ | ||
123 | CHHapticPatternKeyPattern: | ||
124 | @[ | ||
125 | @{ | ||
126 | CHHapticPatternKeyEvent: @{ | ||
127 | CHHapticPatternKeyEventType: CHHapticEventTypeHapticTransient, | ||
128 | CHHapticPatternKeyTime: @0.0, | ||
129 | CHHapticPatternKeyEventDuration:@0.1, | ||
130 | CHHapticPatternKeyEventParameters: @[ | ||
131 | @{ | ||
132 | CHHapticPatternKeyParameterID: CHHapticEventParameterIDHapticIntensity, | ||
133 | CHHapticPatternKeyParameterValue: @0.33 | ||
134 | } | ||
135 | ] | ||
109 | }, | 136 | }, |
110 | }, | 137 | }, |
111 | ], | 138 | ] |
112 | }; | 139 | }; |
113 | } | 140 | } |
114 | 141 | ||
115 | -(void)playTapEffect { | 142 | |
143 | -(void)playHapticEffect:(NSDictionary *)def { | ||
116 | NSError *error = nil; | 144 | NSError *error = nil; |
117 | CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:self.tapDef | 145 | CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithDictionary:def |
118 | error:&error]; | 146 | error:&error]; |
119 | // TODO: Check the error. | 147 | // TODO: Check the error. |
120 | id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error]; | 148 | id<CHHapticPatternPlayer> player = [self.engine createPlayerWithPattern:pattern error:&error]; |
121 | // TODO: Check the error. | 149 | // TODO: Check the error. |
122 | [self.engine startWithCompletionHandler:^(NSError *err){ | 150 | [self.engine startWithCompletionHandler:^(NSError *err){ |
123 | if (err == nil) { | 151 | if (err == nil) { |
124 | /* Just keep it running. */ | ||
125 | // [self.engine notifyWhenPlayersFinished:^(NSError * _Nullable error) { | ||
126 | // return CHHapticEngineFinishedActionStopEngine; | ||
127 | // }]; | ||
128 | NSError *startError = nil; | 152 | NSError *startError = nil; |
129 | [player startAtTime:0.0 error:&startError]; | 153 | [player startAtTime:0.0 error:&startError]; |
130 | } | 154 | } |
@@ -181,11 +205,22 @@ static AppState *appState_; | |||
181 | 205 | ||
182 | - (void)documentPicker:(UIDocumentPickerViewController *)controller | 206 | - (void)documentPicker:(UIDocumentPickerViewController *)controller |
183 | didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | 207 | didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { |
184 | [self removeSavedFile]; | 208 | if (fileBeingSaved) { |
209 | [self removeSavedFile]; | ||
210 | } | ||
211 | else { | ||
212 | /* A file is being opened. */ | ||
213 | NSURL *url = [urls firstObject]; | ||
214 | iString *path = localFilePathFromUrl_String(collectNewCStr_String([[url absoluteString] | ||
215 | UTF8String])); | ||
216 | postCommandf_App("file.open temp:1 path:%s", cstrCollect_String(path)); | ||
217 | } | ||
185 | } | 218 | } |
186 | 219 | ||
187 | - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { | 220 | - (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller { |
188 | [self removeSavedFile]; | 221 | if (fileBeingSaved) { |
222 | [self removeSavedFile]; | ||
223 | } | ||
189 | } | 224 | } |
190 | 225 | ||
191 | -(void)keyboardOnScreen:(NSNotification *)notification { | 226 | -(void)keyboardOnScreen:(NSNotification *)notification { |
@@ -230,6 +265,55 @@ void setupApplication_iOS(void) { | |||
230 | name:UIKeyboardWillHideNotification | 265 | name:UIKeyboardWillHideNotification |
231 | object:nil]; | 266 | object:nil]; |
232 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; | 267 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; |
268 | /* Media player remote controls. */ | ||
269 | MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; | ||
270 | [[commandCenter pauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { | ||
271 | iPlayer *player = active_Player(); | ||
272 | if (player) { | ||
273 | setPaused_Player(player, iTrue); | ||
274 | return MPRemoteCommandHandlerStatusSuccess; | ||
275 | } | ||
276 | return MPRemoteCommandHandlerStatusCommandFailed; | ||
277 | }]; | ||
278 | [[commandCenter playCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { | ||
279 | iPlayer *player = active_Player(); | ||
280 | if (player) { | ||
281 | if (isPaused_Player(player)) { | ||
282 | setPaused_Player(player, iFalse); | ||
283 | } | ||
284 | else { | ||
285 | start_Player(player); | ||
286 | } | ||
287 | return MPRemoteCommandHandlerStatusSuccess; | ||
288 | } | ||
289 | return MPRemoteCommandHandlerStatusCommandFailed; | ||
290 | }]; | ||
291 | [[commandCenter stopCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { | ||
292 | iPlayer *player = active_Player(); | ||
293 | if (player) { | ||
294 | stop_Player(player); | ||
295 | return MPRemoteCommandHandlerStatusSuccess; | ||
296 | } | ||
297 | return MPRemoteCommandHandlerStatusCommandFailed; | ||
298 | }]; | ||
299 | [[commandCenter togglePlayPauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { | ||
300 | iPlayer *player = active_Player(); | ||
301 | if (player) { | ||
302 | setPaused_Player(player, !isPaused_Player(player)); | ||
303 | return MPRemoteCommandHandlerStatusSuccess; | ||
304 | } | ||
305 | return MPRemoteCommandHandlerStatusCommandFailed; | ||
306 | }]; | ||
307 | [[commandCenter nextTrackCommand] setEnabled:NO]; | ||
308 | [[commandCenter previousTrackCommand] setEnabled:NO]; | ||
309 | [[commandCenter changeRepeatModeCommand] setEnabled:NO]; | ||
310 | [[commandCenter changeShuffleModeCommand] setEnabled:NO]; | ||
311 | [[commandCenter changePlaybackRateCommand] setEnabled:NO]; | ||
312 | [[commandCenter seekForwardCommand] setEnabled:NO]; | ||
313 | [[commandCenter seekBackwardCommand] setEnabled:NO]; | ||
314 | [[commandCenter skipForwardCommand] setEnabled:NO]; | ||
315 | [[commandCenter skipBackwardCommand] setEnabled:NO]; | ||
316 | [[commandCenter changePlaybackPositionCommand] setEnabled:NO]; | ||
233 | } | 317 | } |
234 | 318 | ||
235 | static iBool isDarkMode_(iWindow *window) { | 319 | static iBool isDarkMode_(iWindow *window) { |
@@ -265,7 +349,7 @@ iBool isPhone_iOS(void) { | |||
265 | } | 349 | } |
266 | 350 | ||
267 | int displayRefreshRate_iOS(void) { | 351 | int displayRefreshRate_iOS(void) { |
268 | return uiWindow_(get_Window()).screen.maximumFramesPerSecond; | 352 | return (int) uiWindow_(get_Window()).screen.maximumFramesPerSecond; |
269 | } | 353 | } |
270 | 354 | ||
271 | void setupWindow_iOS(iWindow *window) { | 355 | void setupWindow_iOS(iWindow *window) { |
@@ -279,7 +363,10 @@ void playHapticEffect_iOS(enum iHapticEffect effect) { | |||
279 | HapticState *hs = (HapticState *) appState_.haptic; | 363 | HapticState *hs = (HapticState *) appState_.haptic; |
280 | switch(effect) { | 364 | switch(effect) { |
281 | case tap_HapticEffect: | 365 | case tap_HapticEffect: |
282 | [hs playTapEffect]; | 366 | [hs playHapticEffect:hs.tapDef]; |
367 | break; | ||
368 | case gentleTap_HapticEffect: | ||
369 | [hs playHapticEffect:hs.gentleTapDef]; | ||
283 | break; | 370 | break; |
284 | } | 371 | } |
285 | } | 372 | } |
@@ -324,6 +411,38 @@ iBool processEvent_iOS(const SDL_Event *ev) { | |||
324 | return iFalse; /* allow normal processing */ | 411 | return iFalse; /* allow normal processing */ |
325 | } | 412 | } |
326 | 413 | ||
414 | void updateNowPlayingInfo_iOS(void) { | ||
415 | const iPlayer *player = active_Player(); | ||
416 | if (!player) { | ||
417 | clearNowPlayingInfo_iOS(); | ||
418 | return; | ||
419 | } | ||
420 | NSMutableDictionary<NSString *, id> *info = [[NSMutableDictionary<NSString *, id> alloc] init]; | ||
421 | [info setObject:[NSNumber numberWithDouble:time_Player(player)] | ||
422 | forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime]; | ||
423 | [info setObject:[NSNumber numberWithInt:MPNowPlayingInfoMediaTypeAudio] | ||
424 | forKey:MPNowPlayingInfoPropertyMediaType]; | ||
425 | [info setObject:[NSNumber numberWithDouble:duration_Player(player)] | ||
426 | forKey:MPMediaItemPropertyPlaybackDuration]; | ||
427 | const iString *title = tag_Player(player, title_PlayerTag); | ||
428 | const iString *artist = tag_Player(player, artist_PlayerTag); | ||
429 | if (isEmpty_String(title)) { | ||
430 | title = collectNewCStr_String("Audio"); /* TODO: Use link label or URL file name */ | ||
431 | } | ||
432 | if (isEmpty_String(artist)) { | ||
433 | artist = collectNewCStr_String("Lagrange"); /* TODO: Use domain or base URL */ | ||
434 | } | ||
435 | [info setObject:[NSString stringWithUTF8String:cstr_String(title)] | ||
436 | forKey:MPMediaItemPropertyTitle]; | ||
437 | [info setObject:[NSString stringWithUTF8String:cstr_String(artist)] | ||
438 | forKey:MPMediaItemPropertyArtist]; | ||
439 | [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:info]; | ||
440 | } | ||
441 | |||
442 | void clearNowPlayingInfo_iOS(void) { | ||
443 | [[MPNowPlayingInfoCenter defaultCenter] setNowPlayingInfo:nil]; | ||
444 | } | ||
445 | |||
327 | void exportDownloadedFile_iOS(const iString *path) { | 446 | void exportDownloadedFile_iOS(const iString *path) { |
328 | NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path) | 447 | NSURL *url = [NSURL fileURLWithPath:[[NSString alloc] initWithCString:cstr_String(path) |
329 | encoding:NSUTF8StringEncoding]]; | 448 | encoding:NSUTF8StringEncoding]]; |
@@ -335,6 +454,17 @@ void exportDownloadedFile_iOS(const iString *path) { | |||
335 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; | 454 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; |
336 | } | 455 | } |
337 | 456 | ||
457 | void pickFileForOpening_iOS(void) { | ||
458 | UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] | ||
459 | initWithDocumentTypes:@[@"fi.skyjake.lagrange.gemini", | ||
460 | @"public.text", | ||
461 | @"public.image", | ||
462 | @"public.audio"] | ||
463 | inMode:UIDocumentPickerModeImport]; | ||
464 | picker.delegate = appState_; | ||
465 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; | ||
466 | } | ||
467 | |||
338 | /*----------------------------------------------------------------------------------------------*/ | 468 | /*----------------------------------------------------------------------------------------------*/ |
339 | 469 | ||
340 | enum iAVFAudioPlayerState { | 470 | enum iAVFAudioPlayerState { |
diff --git a/src/macos.m b/src/macos.m index bbef94c6..6bacfdd1 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -480,7 +480,7 @@ void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem * | |||
480 | [menu setAutoenablesItems:NO]; | 480 | [menu setAutoenablesItems:NO]; |
481 | for (size_t i = 0; i < count; ++i) { | 481 | for (size_t i = 0; i < count; ++i) { |
482 | const char *label = translateCStr_Lang(items[i].label); | 482 | const char *label = translateCStr_Lang(items[i].label); |
483 | if (label[0] == '\r') { | 483 | if (label[0] == '\v') { |
484 | /* Skip the formatting escape. */ | 484 | /* Skip the formatting escape. */ |
485 | label += 2; | 485 | label += 2; |
486 | } | 486 | } |
diff --git a/src/media.c b/src/media.c index 1313b7da..eb4a8311 100644 --- a/src/media.c +++ b/src/media.c | |||
@@ -24,6 +24,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | #include "gmdocument.h" | 24 | #include "gmdocument.h" |
25 | #include "gmrequest.h" | 25 | #include "gmrequest.h" |
26 | #include "ui/window.h" | 26 | #include "ui/window.h" |
27 | #include "ui/paint.h" /* size_SDLTexture */ | ||
27 | #include "audio/player.h" | 28 | #include "audio/player.h" |
28 | #include "app.h" | 29 | #include "app.h" |
29 | #include "stb_image.h" | 30 | #include "stb_image.h" |
@@ -261,6 +262,31 @@ void clear_Media(iMedia *d) { | |||
261 | clear_PtrArray(&d->downloads); | 262 | clear_PtrArray(&d->downloads); |
262 | } | 263 | } |
263 | 264 | ||
265 | size_t memorySize_Media(const iMedia *d) { | ||
266 | size_t memSize = 0; | ||
267 | iConstForEach(PtrArray, i, &d->images) { | ||
268 | const iGmImage *img = i.ptr; | ||
269 | if (img->texture) { | ||
270 | const iInt2 texSize = size_SDLTexture(img->texture); | ||
271 | memSize += 4 * texSize.x * texSize.y; /* RGBA */ | ||
272 | } | ||
273 | else { | ||
274 | memSize += size_Block(&img->partialData); | ||
275 | } | ||
276 | } | ||
277 | iConstForEach(PtrArray, a, &d->audio) { | ||
278 | const iGmAudio *audio = a.ptr; | ||
279 | if (audio->player) { | ||
280 | memSize += sourceDataSize_Player(audio->player); | ||
281 | } | ||
282 | } | ||
283 | iConstForEach(PtrArray, n, &d->downloads) { | ||
284 | const iGmDownload *down = n.ptr; | ||
285 | memSize += down->numBytes; | ||
286 | } | ||
287 | return memSize; | ||
288 | } | ||
289 | |||
264 | iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { | 290 | iBool setDownloadUrl_Media(iMedia *d, iGmLinkId linkId, const iString *url) { |
265 | iGmDownload *dl = NULL; | 291 | iGmDownload *dl = NULL; |
266 | iMediaId existing = findLinkDownload_Media(d, linkId); | 292 | iMediaId existing = findLinkDownload_Media(d, linkId); |
@@ -464,6 +490,15 @@ iPlayer *audioPlayer_Media(const iMedia *d, iMediaId audioId) { | |||
464 | return NULL; | 490 | return NULL; |
465 | } | 491 | } |
466 | 492 | ||
493 | void pauseAllPlayers_Media(const iMedia *d, iBool setPaused) { | ||
494 | for (size_t i = 0; i < size_PtrArray(&d->audio); ++i) { | ||
495 | const iGmAudio *audio = constAt_PtrArray(&d->audio, i); | ||
496 | if (audio->player) { | ||
497 | setPaused_Player(audio->player, setPaused); | ||
498 | } | ||
499 | } | ||
500 | } | ||
501 | |||
467 | iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) { | 502 | iBool downloadInfo_Media(const iMedia *d, iMediaId downloadId, iGmMediaInfo *info_out) { |
468 | if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { | 503 | if (downloadId > 0 && downloadId <= size_PtrArray(&d->downloads)) { |
469 | const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); | 504 | const iGmDownload *dl = constAt_PtrArray(&d->downloads, downloadId - 1); |
diff --git a/src/media.h b/src/media.h index 7cc941d0..f7ad6efd 100644 --- a/src/media.h +++ b/src/media.h | |||
@@ -50,6 +50,8 @@ void clear_Media (iMedia *); | |||
50 | iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); | 50 | iBool setDownloadUrl_Media (iMedia *, uint16_t linkId, const iString *url); |
51 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); | 51 | iBool setData_Media (iMedia *, uint16_t linkId, const iString *mime, const iBlock *data, int flags); |
52 | 52 | ||
53 | size_t memorySize_Media (const iMedia *); | ||
54 | |||
53 | iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); | 55 | iMediaId findLinkImage_Media (const iMedia *, uint16_t linkId); |
54 | iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); | 56 | iBool imageInfo_Media (const iMedia *, iMediaId imageId, iGmMediaInfo *info_out); |
55 | iInt2 imageSize_Media (const iMedia *, iMediaId imageId); | 57 | iInt2 imageSize_Media (const iMedia *, iMediaId imageId); |
@@ -59,6 +61,7 @@ size_t numAudio_Media (const iMedia *); | |||
59 | iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId); | 61 | iMediaId findLinkAudio_Media (const iMedia *, uint16_t linkId); |
60 | iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out); | 62 | iBool audioInfo_Media (const iMedia *, iMediaId audioId, iGmMediaInfo *info_out); |
61 | iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); | 63 | iPlayer * audioPlayer_Media (const iMedia *, iMediaId audioId); |
64 | void pauseAllPlayers_Media(const iMedia *, iBool setPaused); | ||
62 | 65 | ||
63 | iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId); | 66 | iMediaId findLinkDownload_Media (const iMedia *, uint16_t linkId); |
64 | iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out); | 67 | iBool downloadInfo_Media (const iMedia *, iMediaId downloadId, iGmMediaInfo *info_out); |
diff --git a/src/prefs.c b/src/prefs.c index 385dee78..f1842e9a 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -46,6 +46,7 @@ void init_Prefs(iPrefs *d) { | |||
46 | d->openArchiveIndexPages = iTrue; | 46 | d->openArchiveIndexPages = iTrue; |
47 | d->decodeUserVisibleURLs = iTrue; | 47 | d->decodeUserVisibleURLs = iTrue; |
48 | d->maxCacheSize = 10; | 48 | d->maxCacheSize = 10; |
49 | d->maxMemorySize = 200; | ||
49 | d->font = nunito_TextFont; | 50 | d->font = nunito_TextFont; |
50 | d->headingFont = nunito_TextFont; | 51 | d->headingFont = nunito_TextFont; |
51 | d->monospaceGemini = iFalse; | 52 | d->monospaceGemini = iFalse; |
diff --git a/src/prefs.h b/src/prefs.h index 7185c8f9..655ec949 100644 --- a/src/prefs.h +++ b/src/prefs.h | |||
@@ -66,6 +66,7 @@ struct Impl_Prefs { | |||
66 | iString caPath; | 66 | iString caPath; |
67 | iBool decodeUserVisibleURLs; | 67 | iBool decodeUserVisibleURLs; |
68 | int maxCacheSize; /* MB */ | 68 | int maxCacheSize; /* MB */ |
69 | int maxMemorySize; /* MB */ | ||
69 | iString geminiProxy; | 70 | iString geminiProxy; |
70 | iString gopherProxy; | 71 | iString gopherProxy; |
71 | iString httpProxy; | 72 | iString httpProxy; |
diff --git a/src/ui/color.c b/src/ui/color.c index a6ba18e4..05ec1f6f 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -24,11 +24,13 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | #include "root.h" | 24 | #include "root.h" |
25 | #include "app.h" | 25 | #include "app.h" |
26 | 26 | ||
27 | #include <the_Foundation/file.h> | ||
28 | #include <the_Foundation/path.h> | ||
27 | #include <the_Foundation/string.h> | 29 | #include <the_Foundation/string.h> |
28 | 30 | ||
29 | static const iColor transparent_; | 31 | static const iColor transparent_; |
30 | 32 | ||
31 | static const iColor darkPalette_[] = { | 33 | static iColor darkPalette_[] = { |
32 | { 0, 0, 0, 255 }, | 34 | { 0, 0, 0, 255 }, |
33 | { 40, 40, 40, 255 }, | 35 | { 40, 40, 40, 255 }, |
34 | { 80, 80, 80, 255 }, | 36 | { 80, 80, 80, 255 }, |
@@ -47,7 +49,7 @@ static const iColor darkPalette_[] = { | |||
47 | { 0, 200, 0, 255 }, | 49 | { 0, 200, 0, 255 }, |
48 | }; | 50 | }; |
49 | 51 | ||
50 | static const iColor lightPalette_[] = { | 52 | static iColor lightPalette_[] = { |
51 | { 0, 0, 0, 255 }, | 53 | { 0, 0, 0, 255 }, |
52 | { 75, 75, 75, 255 }, | 54 | { 75, 75, 75, 255 }, |
53 | { 150, 150, 150, 255 }, | 55 | { 150, 150, 150, 255 }, |
@@ -468,12 +470,12 @@ const char *escape_Color(int color) { | |||
468 | return esc[color]; | 470 | return esc[color]; |
469 | } | 471 | } |
470 | /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ | 472 | /* TODO: Conflict with format strings! "%" (37) may be used as the color value. */ |
471 | /* Double-\r is used for range extension. */ | 473 | /* Double-\v is used for range extension. */ |
472 | if (color + asciiBase_ColorEscape > 127) { | 474 | if (color + asciiBase_ColorEscape > 127) { |
473 | iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); | 475 | iAssert(color - asciiExtended_ColorEscape + asciiBase_ColorEscape <= 127); |
474 | return format_CStr("\r\r%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); | 476 | return format_CStr("\v\v%c", color - asciiExtended_ColorEscape + asciiBase_ColorEscape); |
475 | } | 477 | } |
476 | return format_CStr("\r%c", color + asciiBase_ColorEscape); | 478 | return format_CStr("\v%c", color + asciiBase_ColorEscape); |
477 | } | 479 | } |
478 | 480 | ||
479 | iHSLColor setSat_HSLColor(iHSLColor d, float sat) { | 481 | iHSLColor setSat_HSLColor(iHSLColor d, float sat) { |
@@ -804,3 +806,77 @@ iColor ansiForeground_Color(iRangecc escapeSequence, int fallback) { | |||
804 | } | 806 | } |
805 | return clr; | 807 | return clr; |
806 | } | 808 | } |
809 | |||
810 | iBool loadPalette_Color(const char *path) { | ||
811 | iBool wasLoaded = iFalse; | ||
812 | iFile *f = newCStr_File(concatPath_CStr(path, "palette.txt")); | ||
813 | if (open_File(f, text_FileMode | readOnly_FileMode)) { | ||
814 | iColor *dstPal = darkPalette_; | ||
815 | iRangecc srcLine = iNullRange; | ||
816 | const iBlock *src = collect_Block(readAll_File(f)); | ||
817 | while (nextSplit_Rangecc(range_Block(src), "\n", &srcLine)) { | ||
818 | iRangecc line = srcLine; | ||
819 | trim_Rangecc(&line); | ||
820 | if (isEmpty_Range(&line)) { | ||
821 | continue; | ||
822 | } | ||
823 | if (*line.start == '#') { | ||
824 | /* Control directive. */ | ||
825 | line.start++; | ||
826 | trim_Rangecc(&line); | ||
827 | if (equalCase_Rangecc(line, "dark")) { | ||
828 | dstPal = darkPalette_; | ||
829 | } | ||
830 | else if (equalCase_Rangecc(line, "light")) { | ||
831 | dstPal = lightPalette_; | ||
832 | } | ||
833 | continue; | ||
834 | } | ||
835 | static const struct { | ||
836 | const char *label; | ||
837 | int paletteIndex; | ||
838 | } colors_[] = { | ||
839 | { "black:", 0 }, { "gray25:", 1 }, { "gray50:", 2 }, { "gray75:", 3 }, | ||
840 | { "white:", 4 }, { "brown:", 5 }, { "orange:", 6 }, { "teal:", 7 }, | ||
841 | { "cyan:", 8 }, { "yellow:", 9 }, { "red:", 10 }, { "magenta:", 11 }, | ||
842 | { "blue:", 12 }, { "green:", 13 }, | ||
843 | }; | ||
844 | iForIndices(i, colors_) { | ||
845 | if (startsWithCase_Rangecc(line, colors_[i].label)) { | ||
846 | iColor *dst = &dstPal[colors_[i].paletteIndex]; | ||
847 | line.start += strlen(colors_[i].label); | ||
848 | trim_Rangecc(&line); | ||
849 | if (!isEmpty_Range(&line)) { | ||
850 | if (*line.start == '#') { | ||
851 | /* Hexadecimal color. */ | ||
852 | line.start++; | ||
853 | if (size_Range(&line) == 6) { | ||
854 | iBlock *vals = hexDecode_Rangecc(line); | ||
855 | iAssert(size_Block(vals) == 3); | ||
856 | const uint8_t *rgb = constData_Block(vals); | ||
857 | *dst = (iColor){ rgb[0], rgb[1], rgb[2], 255 }; | ||
858 | delete_Block(vals); | ||
859 | } | ||
860 | else { | ||
861 | fprintf(stderr, "[Color] invalid custom color: %s\n", | ||
862 | cstr_Rangecc(line)); | ||
863 | } | ||
864 | } | ||
865 | else { | ||
866 | unsigned int red = 0, green = 0, blue = 0; | ||
867 | sscanf(line.start, "%u %u %u", &red, &green, &blue); | ||
868 | if (red > 255 || green > 255 || blue > 255) { | ||
869 | fprintf(stderr, "[Color] RGB value(s) out of range: %s\n", | ||
870 | cstr_Rangecc(line)); | ||
871 | } | ||
872 | *dst = (iColor){ red, green, blue, 255 }; | ||
873 | } | ||
874 | } | ||
875 | } | ||
876 | } | ||
877 | } | ||
878 | wasLoaded = iTrue; | ||
879 | } | ||
880 | iRelease(f); | ||
881 | return wasLoaded; | ||
882 | } | ||
diff --git a/src/ui/color.h b/src/ui/color.h index d2fa3c00..37ec49eb 100644 --- a/src/ui/color.h +++ b/src/ui/color.h | |||
@@ -187,26 +187,26 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) { | |||
187 | #define asciiBase_ColorEscape 33 | 187 | #define asciiBase_ColorEscape 33 |
188 | #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) | 188 | #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) |
189 | 189 | ||
190 | #define restore_ColorEscape "\r\x24" /* ASCII Cancel */ | 190 | #define restore_ColorEscape "\v\x24" /* ASCII Cancel */ |
191 | #define black_ColorEscape "\r!" | 191 | #define black_ColorEscape "\v!" |
192 | #define gray25_ColorEscape "\r\"" | 192 | #define gray25_ColorEscape "\v\"" |
193 | #define gray50_ColorEscape "\r#" | 193 | #define gray50_ColorEscape "\v#" |
194 | #define gray75_ColorEscape "\r$" | 194 | #define gray75_ColorEscape "\v$" |
195 | #define white_ColorEscape "\r%" | 195 | #define white_ColorEscape "\v%" |
196 | #define brown_ColorEscape "\r&" | 196 | #define brown_ColorEscape "\v&" |
197 | #define orange_ColorEscape "\r'" | 197 | #define orange_ColorEscape "\v'" |
198 | #define teal_ColorEscape "\r(" | 198 | #define teal_ColorEscape "\v(" |
199 | #define cyan_ColorEscape "\r)" | 199 | #define cyan_ColorEscape "\v)" |
200 | #define yellow_ColorEscape "\r*" | 200 | #define yellow_ColorEscape "\v*" |
201 | #define red_ColorEscape "\r+" | 201 | #define red_ColorEscape "\v+" |
202 | #define magenta_ColorEscape "\r," | 202 | #define magenta_ColorEscape "\v," |
203 | #define blue_ColorEscape "\r-" | 203 | #define blue_ColorEscape "\v-" |
204 | #define green_ColorEscape "\r." | 204 | #define green_ColorEscape "\v." |
205 | #define uiText_ColorEscape "\r4" | 205 | #define uiText_ColorEscape "\v4" |
206 | #define uiTextAction_ColorEscape "\r<" | 206 | #define uiTextAction_ColorEscape "\v<" |
207 | #define uiTextCaution_ColorEscape "\r=" | 207 | #define uiTextCaution_ColorEscape "\v=" |
208 | #define uiTextStrong_ColorEscape "\r:" | 208 | #define uiTextStrong_ColorEscape "\v:" |
209 | #define uiHeading_ColorEscape "\rR" | 209 | #define uiHeading_ColorEscape "\vR" |
210 | 210 | ||
211 | iDeclareType(Color) | 211 | iDeclareType(Color) |
212 | iDeclareType(HSLColor) | 212 | iDeclareType(HSLColor) |
@@ -244,7 +244,9 @@ iLocalDef void setHsl_Color(int color, iHSLColor hsl) { | |||
244 | set_Color(color, rgb_HSLColor(hsl)); | 244 | set_Color(color, rgb_HSLColor(hsl)); |
245 | } | 245 | } |
246 | 246 | ||
247 | iBool loadPalette_Color (const char *path); | ||
247 | void setThemePalette_Color (enum iColorTheme theme); | 248 | void setThemePalette_Color (enum iColorTheme theme); |
248 | 249 | ||
249 | iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); | 250 | iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); |
250 | const char * escape_Color (int color); | 251 | const char * escape_Color (int color); |
252 | |||
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 6dc80a07..cb1fde28 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -130,7 +130,8 @@ void deinit_PersistentDocumentState(iPersistentDocumentState *d) { | |||
130 | 130 | ||
131 | void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) { | 131 | void serialize_PersistentDocumentState(const iPersistentDocumentState *d, iStream *outs) { |
132 | serialize_String(d->url, outs); | 132 | serialize_String(d->url, outs); |
133 | writeU16_Stream(outs, d->reloadInterval & 7); | 133 | uint16_t params = d->reloadInterval & 7; |
134 | writeU16_Stream(outs, params); | ||
134 | serialize_History(d->history, outs); | 135 | serialize_History(d->history, outs); |
135 | } | 136 | } |
136 | 137 | ||
@@ -223,6 +224,7 @@ enum iDocumentWidgetFlag { | |||
223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), | 224 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), |
224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ | 225 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ |
225 | urlChanged_DocumentWidgetFlag = iBit(13), | 226 | urlChanged_DocumentWidgetFlag = iBit(13), |
227 | openedFromSidebar_DocumentWidgetFlag = iBit(14), | ||
226 | }; | 228 | }; |
227 | 229 | ||
228 | enum iDocumentLinkOrdinalMode { | 230 | enum iDocumentLinkOrdinalMode { |
@@ -315,6 +317,10 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
315 | init_Widget(w); | 317 | init_Widget(w); |
316 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); | 318 | setId_Widget(w, format_CStr("document%03d", ++docEnum_)); |
317 | setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); | 319 | setFlags_Widget(w, hover_WidgetFlag | noBackground_WidgetFlag, iTrue); |
320 | if (deviceType_App() != desktop_AppDeviceType) { | ||
321 | setFlags_Widget(w, leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag | | ||
322 | horizontalOffset_WidgetFlag, iTrue); | ||
323 | } | ||
318 | init_PersistentDocumentState(&d->mod); | 324 | init_PersistentDocumentState(&d->mod); |
319 | d->flags = 0; | 325 | d->flags = 0; |
320 | d->phoneToolbar = NULL; | 326 | d->phoneToolbar = NULL; |
@@ -392,6 +398,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
392 | } | 398 | } |
393 | 399 | ||
394 | void deinit_DocumentWidget(iDocumentWidget *d) { | 400 | void deinit_DocumentWidget(iDocumentWidget *d) { |
401 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | ||
395 | removeTicker_App(animate_DocumentWidget_, d); | 402 | removeTicker_App(animate_DocumentWidget_, d); |
396 | removeTicker_App(prerender_DocumentWidget_, d); | 403 | removeTicker_App(prerender_DocumentWidget_, d); |
397 | remove_Periodic(periodic_App(), d); | 404 | remove_Periodic(periodic_App(), d); |
@@ -976,6 +983,9 @@ static void updateTimestampBuf_DocumentWidget_(const iDocumentWidget *d) { | |||
976 | } | 983 | } |
977 | 984 | ||
978 | static void invalidate_DocumentWidget_(iDocumentWidget *d) { | 985 | static void invalidate_DocumentWidget_(iDocumentWidget *d) { |
986 | if (flags_Widget(as_Widget(d)) & destroyPending_WidgetFlag) { | ||
987 | return; | ||
988 | } | ||
979 | invalidate_VisBuf(d->visBuf); | 989 | invalidate_VisBuf(d->visBuf); |
980 | clear_PtrSet(d->invalidRuns); | 990 | clear_PtrSet(d->invalidRuns); |
981 | } | 991 | } |
@@ -1016,9 +1026,7 @@ static void showOrHidePinningIndicator_DocumentWidget_(iDocumentWidget *d) { | |||
1016 | isPinned_DocumentWidget_(d)); | 1026 | isPinned_DocumentWidget_(d)); |
1017 | } | 1027 | } |
1018 | 1028 | ||
1019 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | 1029 | static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { |
1020 | setUrl_GmDocument(d->doc, d->mod.url); | ||
1021 | setSource_GmDocument(d->doc, source, documentWidth_DocumentWidget_(d)); | ||
1022 | documentRunsInvalidated_DocumentWidget_(d); | 1030 | documentRunsInvalidated_DocumentWidget_(d); |
1023 | updateWindowTitle_DocumentWidget_(d); | 1031 | updateWindowTitle_DocumentWidget_(d); |
1024 | updateVisible_DocumentWidget_(d); | 1032 | updateVisible_DocumentWidget_(d); |
@@ -1035,6 +1043,26 @@ void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | |||
1035 | } | 1043 | } |
1036 | } | 1044 | } |
1037 | showOrHidePinningIndicator_DocumentWidget_(d); | 1045 | showOrHidePinningIndicator_DocumentWidget_(d); |
1046 | setCachedDocument_History(d->mod.history, | ||
1047 | d->doc, /* keeps a ref */ | ||
1048 | (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); | ||
1049 | } | ||
1050 | |||
1051 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | ||
1052 | setUrl_GmDocument(d->doc, d->mod.url); | ||
1053 | setSource_GmDocument(d->doc, | ||
1054 | source, | ||
1055 | documentWidth_DocumentWidget_(d), | ||
1056 | isFinished_GmRequest(d->request) ? final_GmDocumentUpdate | ||
1057 | : partial_GmDocumentUpdate); | ||
1058 | documentWasChanged_DocumentWidget_(d); | ||
1059 | } | ||
1060 | |||
1061 | static void replaceDocument_DocumentWidget_(iDocumentWidget *d, iGmDocument *newDoc) { | ||
1062 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); | ||
1063 | iRelease(d->doc); | ||
1064 | d->doc = ref_Object(newDoc); | ||
1065 | documentWasChanged_DocumentWidget_(d); | ||
1038 | } | 1066 | } |
1039 | 1067 | ||
1040 | static void updateTheme_DocumentWidget_(iDocumentWidget *d) { | 1068 | static void updateTheme_DocumentWidget_(iDocumentWidget *d) { |
@@ -1143,16 +1171,22 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1143 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, | 1171 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, |
1144 | 2); | 1172 | 2); |
1145 | } | 1173 | } |
1146 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); | 1174 | /* Make a new document for the error page.*/ { |
1147 | setFormat_GmDocument(d->doc, gemini_GmDocumentFormat); | 1175 | iGmDocument *errorDoc = new_GmDocument(); |
1176 | setUrl_GmDocument(errorDoc, d->mod.url); | ||
1177 | setBanner_GmDocument(errorDoc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); | ||
1178 | setFormat_GmDocument(errorDoc, gemini_SourceFormat); | ||
1179 | replaceDocument_DocumentWidget_(d, errorDoc); | ||
1180 | iRelease(errorDoc); | ||
1181 | } | ||
1148 | translate_Lang(src); | 1182 | translate_Lang(src); |
1183 | d->state = ready_RequestState; | ||
1149 | setSource_DocumentWidget(d, src); | 1184 | setSource_DocumentWidget(d, src); |
1150 | updateTheme_DocumentWidget_(d); | 1185 | updateTheme_DocumentWidget_(d); |
1151 | reset_SmoothScroll(&d->scrollY); | 1186 | reset_SmoothScroll(&d->scrollY); |
1152 | init_Anim(&d->sideOpacity, 0); | 1187 | init_Anim(&d->sideOpacity, 0); |
1153 | init_Anim(&d->altTextOpacity, 0); | 1188 | init_Anim(&d->altTextOpacity, 0); |
1154 | resetWideRuns_DocumentWidget_(d); | 1189 | resetWideRuns_DocumentWidget_(d); |
1155 | d->state = ready_RequestState; | ||
1156 | } | 1190 | } |
1157 | 1191 | ||
1158 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { | 1192 | static void updateFetchProgress_DocumentWidget_(iDocumentWidget *d) { |
@@ -1264,9 +1298,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1264 | 2); | 1298 | 2); |
1265 | } | 1299 | } |
1266 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | 1300 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { |
1267 | redoLayout_GmDocument(d->doc); | 1301 | redoLayout_GmDocument(d->doc); |
1268 | updateVisible_DocumentWidget_(d); | 1302 | updateVisible_DocumentWidget_(d); |
1269 | invalidate_DocumentWidget_(d); | 1303 | invalidate_DocumentWidget_(d); |
1270 | } | 1304 | } |
1271 | } | 1305 | } |
1272 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1306 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { |
@@ -1348,7 +1382,9 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1348 | } | 1382 | } |
1349 | } | 1383 | } |
1350 | 1384 | ||
1351 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse *response, | 1385 | static void updateDocument_DocumentWidget_(iDocumentWidget *d, |
1386 | const iGmResponse *response, | ||
1387 | iGmDocument *cachedDoc, | ||
1352 | const iBool isInitialUpdate) { | 1388 | const iBool isInitialUpdate) { |
1353 | if (d->state == ready_RequestState) { | 1389 | if (d->state == ready_RequestState) { |
1354 | return; | 1390 | return; |
@@ -1371,7 +1407,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1371 | if (isSuccess_GmStatusCode(statusCode)) { | 1407 | if (isSuccess_GmStatusCode(statusCode)) { |
1372 | /* Check the MIME type. */ | 1408 | /* Check the MIME type. */ |
1373 | iRangecc charset = range_CStr("utf-8"); | 1409 | iRangecc charset = range_CStr("utf-8"); |
1374 | enum iGmDocumentFormat docFormat = undefined_GmDocumentFormat; | 1410 | enum iSourceFormat docFormat = undefined_SourceFormat; |
1375 | const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ | 1411 | const iString *mimeStr = collect_String(lower_String(&response->meta)); /* for convenience */ |
1376 | set_String(&d->sourceMime, mimeStr); | 1412 | set_String(&d->sourceMime, mimeStr); |
1377 | iRangecc mime = range_String(mimeStr); | 1413 | iRangecc mime = range_String(mimeStr); |
@@ -1380,20 +1416,20 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1380 | iRangecc param = seg; | 1416 | iRangecc param = seg; |
1381 | trim_Rangecc(¶m); | 1417 | trim_Rangecc(¶m); |
1382 | if (equal_Rangecc(param, "text/gemini")) { | 1418 | if (equal_Rangecc(param, "text/gemini")) { |
1383 | docFormat = gemini_GmDocumentFormat; | 1419 | docFormat = gemini_SourceFormat; |
1384 | setRange_String(&d->sourceMime, param); | 1420 | setRange_String(&d->sourceMime, param); |
1385 | } | 1421 | } |
1386 | else if (startsWith_Rangecc(param, "text/") || | 1422 | else if (startsWith_Rangecc(param, "text/") || |
1387 | equal_Rangecc(param, "application/json") || | 1423 | equal_Rangecc(param, "application/json") || |
1388 | equal_Rangecc(param, "application/x-pem-file") || | 1424 | equal_Rangecc(param, "application/x-pem-file") || |
1389 | equal_Rangecc(param, "application/pem-certificate-chain")) { | 1425 | equal_Rangecc(param, "application/pem-certificate-chain")) { |
1390 | docFormat = plainText_GmDocumentFormat; | 1426 | docFormat = plainText_SourceFormat; |
1391 | setRange_String(&d->sourceMime, param); | 1427 | setRange_String(&d->sourceMime, param); |
1392 | } | 1428 | } |
1393 | else if (equal_Rangecc(param, "application/zip") || | 1429 | else if (equal_Rangecc(param, "application/zip") || |
1394 | (startsWith_Rangecc(param, "application/") && | 1430 | (startsWith_Rangecc(param, "application/") && |
1395 | endsWithCase_Rangecc(param, "+zip"))) { | 1431 | endsWithCase_Rangecc(param, "+zip"))) { |
1396 | docFormat = gemini_GmDocumentFormat; | 1432 | docFormat = gemini_SourceFormat; |
1397 | setRange_String(&d->sourceMime, param); | 1433 | setRange_String(&d->sourceMime, param); |
1398 | iString *key = collectNew_String(); | 1434 | iString *key = collectNew_String(); |
1399 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1435 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
@@ -1420,9 +1456,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1420 | startsWith_Rangecc(param, "audio/")) { | 1456 | startsWith_Rangecc(param, "audio/")) { |
1421 | const iBool isAudio = startsWith_Rangecc(param, "audio/"); | 1457 | const iBool isAudio = startsWith_Rangecc(param, "audio/"); |
1422 | /* Make a simple document with an image or audio player. */ | 1458 | /* Make a simple document with an image or audio player. */ |
1423 | docFormat = gemini_GmDocumentFormat; | 1459 | docFormat = gemini_SourceFormat; |
1424 | setRange_String(&d->sourceMime, param); | 1460 | setRange_String(&d->sourceMime, param); |
1425 | const iGmLinkId imgLinkId = 1; /* there's only the one link */ | 1461 | const iGmLinkId imgLinkId = 1; /* there's only the one link */ |
1462 | /* TODO: Do the image loading in `postProcessRequestContent_DocumentWidget_()` */ | ||
1426 | if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { | 1463 | if ((isAudio && isInitialUpdate) || (!isAudio && isRequestFinished)) { |
1427 | const char *linkTitle = | 1464 | const char *linkTitle = |
1428 | startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; | 1465 | startsWith_String(mimeStr, "image/") ? "Image" : "Audio"; |
@@ -1466,7 +1503,7 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1466 | } | 1503 | } |
1467 | } | 1504 | } |
1468 | } | 1505 | } |
1469 | if (docFormat == undefined_GmDocumentFormat) { | 1506 | if (docFormat == undefined_SourceFormat) { |
1470 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); | 1507 | showErrorPage_DocumentWidget_(d, unsupportedMimeType_GmStatusCode, &response->meta); |
1471 | deinit_String(&str); | 1508 | deinit_String(&str); |
1472 | return; | 1509 | return; |
@@ -1478,7 +1515,10 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse | |||
1478 | collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); | 1515 | collect_String(decode_Block(&str.chars, cstr_Rangecc(charset)))); |
1479 | } | 1516 | } |
1480 | } | 1517 | } |
1481 | if (setSource) { | 1518 | if (cachedDoc) { |
1519 | replaceDocument_DocumentWidget_(d, cachedDoc); | ||
1520 | } | ||
1521 | else if (setSource) { | ||
1482 | setSource_DocumentWidget(d, &str); | 1522 | setSource_DocumentWidget(d, &str); |
1483 | } | 1523 | } |
1484 | deinit_String(&str); | 1524 | deinit_String(&str); |
@@ -1558,14 +1598,16 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | |||
1558 | } | 1598 | } |
1559 | 1599 | ||
1560 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, | 1600 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, |
1561 | const iGmResponse *resp) { | 1601 | const iGmResponse *resp, iGmDocument *cachedDoc) { |
1562 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 1602 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
1563 | clear_ObjectList(d->media); | 1603 | clear_ObjectList(d->media); |
1564 | delete_Gempub(d->sourceGempub); | 1604 | delete_Gempub(d->sourceGempub); |
1565 | d->sourceGempub = NULL; | 1605 | d->sourceGempub = NULL; |
1566 | reset_GmDocument(d->doc); | 1606 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); |
1607 | iRelease(d->doc); | ||
1567 | destroy_Widget(d->footerButtons); | 1608 | destroy_Widget(d->footerButtons); |
1568 | d->footerButtons = NULL; | 1609 | d->footerButtons = NULL; |
1610 | d->doc = new_GmDocument(); | ||
1569 | resetWideRuns_DocumentWidget_(d); | 1611 | resetWideRuns_DocumentWidget_(d); |
1570 | d->state = fetching_RequestState; | 1612 | d->state = fetching_RequestState; |
1571 | /* Do the fetch. */ { | 1613 | /* Do the fetch. */ { |
@@ -1576,10 +1618,12 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n | |||
1576 | d->sourceStatus = success_GmStatusCode; | 1618 | d->sourceStatus = success_GmStatusCode; |
1577 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | 1619 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); |
1578 | set_Block(&d->sourceContent, &resp->body); | 1620 | set_Block(&d->sourceContent, &resp->body); |
1579 | updateDocument_DocumentWidget_(d, resp, iTrue); | 1621 | updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); |
1580 | postProcessRequestContent_DocumentWidget_(d, iTrue); | 1622 | // setCachedDocument_History(d->mod.history, d->doc, |
1623 | // (d->flags & openedFromSidebar_DocumentWidgetFlag) != 0); | ||
1581 | } | 1624 | } |
1582 | d->state = ready_RequestState; | 1625 | d->state = ready_RequestState; |
1626 | postProcessRequestContent_DocumentWidget_(d, iTrue); | ||
1583 | init_Anim(&d->altTextOpacity, 0); | 1627 | init_Anim(&d->altTextOpacity, 0); |
1584 | reset_SmoothScroll(&d->scrollY); | 1628 | reset_SmoothScroll(&d->scrollY); |
1585 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | 1629 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); |
@@ -1589,13 +1633,18 @@ static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float n | |||
1589 | cacheDocumentGlyphs_DocumentWidget_(d); | 1633 | cacheDocumentGlyphs_DocumentWidget_(d); |
1590 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | 1634 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; |
1591 | d->flags &= ~urlChanged_DocumentWidgetFlag; | 1635 | d->flags &= ~urlChanged_DocumentWidgetFlag; |
1592 | postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | 1636 | postCommandf_Root( |
1637 | as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1593 | } | 1638 | } |
1594 | 1639 | ||
1595 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 1640 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { |
1596 | const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); | 1641 | const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); |
1597 | if (recent && recent->cachedResponse) { | 1642 | if (recent && recent->cachedResponse) { |
1598 | updateFromCachedResponse_DocumentWidget_(d, recent->normScrollY, recent->cachedResponse); | 1643 | iChangeFlags(d->flags, |
1644 | openedFromSidebar_DocumentWidgetFlag, | ||
1645 | recent->flags.openedFromSidebar); | ||
1646 | updateFromCachedResponse_DocumentWidget_( | ||
1647 | d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); | ||
1599 | return iTrue; | 1648 | return iTrue; |
1600 | } | 1649 | } |
1601 | else if (!isEmpty_String(d->mod.url)) { | 1650 | else if (!isEmpty_String(d->mod.url)) { |
@@ -1636,7 +1685,7 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1636 | if (deviceType_App() == phone_AppDeviceType) { | 1685 | if (deviceType_App() == phone_AppDeviceType) { |
1637 | const float normPos = normScrollPos_DocumentWidget_(d); | 1686 | const float normPos = normScrollPos_DocumentWidget_(d); |
1638 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { | 1687 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { |
1639 | showToolbars_Root(as_Widget(d)->root, offset < 0); | 1688 | showToolbar_Root(as_Widget(d)->root, offset < 0); |
1640 | } | 1689 | } |
1641 | } | 1690 | } |
1642 | updateVisible_DocumentWidget_(d); | 1691 | updateVisible_DocumentWidget_(d); |
@@ -1845,13 +1894,15 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1845 | /* Keep scroll position when reloading the same page. */ | 1894 | /* Keep scroll position when reloading the same page. */ |
1846 | reset_SmoothScroll(&d->scrollY); | 1895 | reset_SmoothScroll(&d->scrollY); |
1847 | } | 1896 | } |
1848 | reset_GmDocument(d->doc); /* new content incoming */ | 1897 | pauseAllPlayers_Media(media_GmDocument(d->doc), iTrue); |
1898 | iRelease(d->doc); /* new content incoming */ | ||
1899 | d->doc = new_GmDocument(); | ||
1849 | delete_Gempub(d->sourceGempub); | 1900 | delete_Gempub(d->sourceGempub); |
1850 | d->sourceGempub = NULL; | 1901 | d->sourceGempub = NULL; |
1851 | destroy_Widget(d->footerButtons); | 1902 | destroy_Widget(d->footerButtons); |
1852 | d->footerButtons = NULL; | 1903 | d->footerButtons = NULL; |
1853 | resetWideRuns_DocumentWidget_(d); | 1904 | resetWideRuns_DocumentWidget_(d); |
1854 | updateDocument_DocumentWidget_(d, resp, iTrue); | 1905 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); |
1855 | break; | 1906 | break; |
1856 | case categoryRedirect_GmStatusCode: | 1907 | case categoryRedirect_GmStatusCode: |
1857 | if (isEmpty_String(&resp->meta)) { | 1908 | if (isEmpty_String(&resp->meta)) { |
@@ -1902,7 +1953,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1902 | switch (category_GmStatusCode(statusCode)) { | 1953 | switch (category_GmStatusCode(statusCode)) { |
1903 | case categorySuccess_GmStatusCode: | 1954 | case categorySuccess_GmStatusCode: |
1904 | /* More content available. */ | 1955 | /* More content available. */ |
1905 | updateDocument_DocumentWidget_(d, resp, iFalse); | 1956 | updateDocument_DocumentWidget_(d, resp, NULL, iFalse); |
1906 | break; | 1957 | break; |
1907 | default: | 1958 | default: |
1908 | break; | 1959 | break; |
@@ -2087,16 +2138,16 @@ static const iString *saveToDownloads_(const iString *url, const iString *mime, | |||
2087 | exportDownloadedFile_iOS(savePath); | 2138 | exportDownloadedFile_iOS(savePath); |
2088 | #else | 2139 | #else |
2089 | if (showDialog) { | 2140 | if (showDialog) { |
2090 | const iMenuItem items[2] = { | 2141 | const iMenuItem items[2] = { |
2091 | { "${dlg.save.opendownload}", 0, 0, | 2142 | { "${dlg.save.opendownload}", 0, 0, |
2092 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, | 2143 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, |
2093 | { "${dlg.message.ok}", 0, 0, "message.ok" }, | 2144 | { "${dlg.message.ok}", 0, 0, "message.ok" }, |
2094 | }; | 2145 | }; |
2095 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", | 2146 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", |
2096 | format_CStr("%s\n${dlg.save.size} %.3f %s", | 2147 | format_CStr("%s\n${dlg.save.size} %.3f %s", |
2097 | cstr_String(path_File(f)), | 2148 | cstr_String(path_File(f)), |
2098 | isMega ? size / 1.0e6f : (size / 1.0e3f), | 2149 | isMega ? size / 1.0e6f : (size / 1.0e3f), |
2099 | isMega ? "${mb}" : "${kb}"), | 2150 | isMega ? "${mb}" : "${kb}"), |
2100 | items, | 2151 | items, |
2101 | iElemCount(items)); | 2152 | iElemCount(items)); |
2102 | } | 2153 | } |
@@ -2210,6 +2261,147 @@ static iBool handlePinch_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | |||
2210 | return iTrue; | 2261 | return iTrue; |
2211 | } | 2262 | } |
2212 | 2263 | ||
2264 | static void swap_DocumentWidget_(iDocumentWidget *d, iGmDocument *doc, | ||
2265 | iDocumentWidget *swapBuffersWith) { | ||
2266 | if (doc) { | ||
2267 | iAssert(isInstance_Object(doc, &Class_GmDocument)); | ||
2268 | iGmDocument *copy = ref_Object(doc); | ||
2269 | iRelease(d->doc); | ||
2270 | d->doc = copy; | ||
2271 | d->scrollY = swapBuffersWith->scrollY; | ||
2272 | updateVisible_DocumentWidget_(d); | ||
2273 | iSwap(iVisBuf *, d->visBuf, swapBuffersWith->visBuf); | ||
2274 | iSwap(iVisBufMeta *, d->visBufMeta, swapBuffersWith->visBufMeta); | ||
2275 | iSwap(iDrawBufs *, d->drawBufs, swapBuffersWith->drawBufs); | ||
2276 | invalidate_DocumentWidget_(swapBuffersWith); | ||
2277 | } | ||
2278 | } | ||
2279 | |||
2280 | static iWidget *swipeParent_DocumentWidget_(iDocumentWidget *d) { | ||
2281 | return findChild_Widget(as_Widget(d)->root->widget, "doctabs"); | ||
2282 | } | ||
2283 | |||
2284 | static iBool handleSwipe_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | ||
2285 | iWidget *w = as_Widget(d); | ||
2286 | /* Swipe animations are rather complex and utilize both cached GmDocument content | ||
2287 | and temporary DocumentWidgets. Depending on the swipe direction, this DocumentWidget | ||
2288 | may wait until the finger is released to actually perform the navigation action. */ | ||
2289 | if (equal_Command(cmd, "edgeswipe.moved")) { | ||
2290 | //printf("[%p] responds to edgeswipe.moved\n", d); | ||
2291 | as_Widget(d)->offsetRef = NULL; | ||
2292 | const int side = argLabel_Command(cmd, "side"); | ||
2293 | const int offset = arg_Command(cmd); | ||
2294 | if (side == 1) { /* left edge */ | ||
2295 | if (atOldest_History(d->mod.history)) { | ||
2296 | return iTrue; | ||
2297 | } | ||
2298 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
2299 | /* The temporary "swipeIn" will display the previous page until the finger is lifted. */ | ||
2300 | iDocumentWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); | ||
2301 | if (!swipeIn) { | ||
2302 | const iBool sidebarSwipe = (isPortraitPhone_App() && | ||
2303 | d->flags & openedFromSidebar_DocumentWidgetFlag && | ||
2304 | !isVisible_Widget(findWidget_App("sidebar"))); | ||
2305 | swipeIn = new_DocumentWidget(); | ||
2306 | setId_Widget(as_Widget(swipeIn), "swipein"); | ||
2307 | setFlags_Widget(as_Widget(swipeIn), | ||
2308 | disabled_WidgetFlag | refChildrenOffset_WidgetFlag | | ||
2309 | fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | ||
2310 | swipeIn->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); | ||
2311 | swipeIn->widget.rect.size = d->widget.rect.size; | ||
2312 | swipeIn->widget.offsetRef = parent_Widget(w); | ||
2313 | if (!sidebarSwipe) { | ||
2314 | iRecentUrl *recent = new_RecentUrl(); | ||
2315 | preceding_History(d->mod.history, recent); | ||
2316 | if (recent->cachedDoc) { | ||
2317 | iChangeRef(swipeIn->doc, recent->cachedDoc); | ||
2318 | updateScrollMax_DocumentWidget_(d); | ||
2319 | setValue_Anim(&swipeIn->scrollY.pos, size_GmDocument(swipeIn->doc).y * recent->normScrollY, 0); | ||
2320 | updateVisible_DocumentWidget_(swipeIn); | ||
2321 | swipeIn->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
2322 | } | ||
2323 | delete_RecentUrl(recent); | ||
2324 | } | ||
2325 | addChildPos_Widget(swipeParent, iClob(swipeIn), front_WidgetAddPos); | ||
2326 | } | ||
2327 | } | ||
2328 | if (side == 2) { /* right edge */ | ||
2329 | if (offset < -get_Window()->pixelRatio * 10) { | ||
2330 | int animSpan = 10; | ||
2331 | if (!atLatest_History(d->mod.history) && | ||
2332 | ~flags_Widget(w) & dragged_WidgetFlag) { | ||
2333 | animSpan = 0; | ||
2334 | postCommand_Widget(d, "navigate.forward"); | ||
2335 | setFlags_Widget(w, dragged_WidgetFlag, iTrue); | ||
2336 | /* Set up the swipe dummy. */ | ||
2337 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
2338 | iDocumentWidget *target = new_DocumentWidget(); | ||
2339 | setId_Widget(as_Widget(target), "swipeout"); | ||
2340 | /* The target takes the old document and jumps on top. */ | ||
2341 | target->widget.rect.pos = windowToInner_Widget(swipeParent, localToWindow_Widget(w, w->rect.pos)); | ||
2342 | target->widget.rect.size = d->widget.rect.size; | ||
2343 | setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | ||
2344 | swap_DocumentWidget_(target, d->doc, d); | ||
2345 | addChildPos_Widget(swipeParent, iClob(target), front_WidgetAddPos); | ||
2346 | setFlags_Widget(as_Widget(target), refChildrenOffset_WidgetFlag, iTrue); | ||
2347 | as_Widget(target)->offsetRef = parent_Widget(w); | ||
2348 | destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */ | ||
2349 | } | ||
2350 | if (flags_Widget(w) & dragged_WidgetFlag) { | ||
2351 | setVisualOffset_Widget(w, width_Widget(w) + | ||
2352 | width_Widget(d) * offset / size_Root(w->root).x, | ||
2353 | animSpan, 0); | ||
2354 | } | ||
2355 | else { | ||
2356 | setVisualOffset_Widget(w, offset / 4, animSpan, 0); | ||
2357 | } | ||
2358 | } | ||
2359 | return iTrue; | ||
2360 | } | ||
2361 | } | ||
2362 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 2) { | ||
2363 | if (argLabel_Command(cmd, "abort") && flags_Widget(w) & dragged_WidgetFlag) { | ||
2364 | postCommand_Widget(d, "navigate.back"); | ||
2365 | } | ||
2366 | setFlags_Widget(w, dragged_WidgetFlag, iFalse); | ||
2367 | setVisualOffset_Widget(w, 0, 100, 0); | ||
2368 | return iTrue; | ||
2369 | } | ||
2370 | if (equal_Command(cmd, "edgeswipe.ended") && argLabel_Command(cmd, "side") == 1) { | ||
2371 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
2372 | iWidget *swipeIn = findChild_Widget(swipeParent, "swipein"); | ||
2373 | if (swipeIn) { | ||
2374 | swipeIn->offsetRef = NULL; | ||
2375 | destroy_Widget(swipeIn); | ||
2376 | } | ||
2377 | } | ||
2378 | if (equal_Command(cmd, "swipe.back")) { | ||
2379 | if (atOldest_History(d->mod.history)) { | ||
2380 | setVisualOffset_Widget(w, 0, 100, 0); | ||
2381 | return iTrue; | ||
2382 | } | ||
2383 | iWidget *swipeParent = swipeParent_DocumentWidget_(d); | ||
2384 | iDocumentWidget *target = new_DocumentWidget(); | ||
2385 | setId_Widget(as_Widget(target), "swipeout"); | ||
2386 | /* The target takes the old document and jumps on top. */ | ||
2387 | target->widget.rect.pos = windowToInner_Widget(swipeParent, innerToWindow_Widget(w, zero_I2())); | ||
2388 | /* Note: `innerToWindow_Widget` does not apply visual offset. */ | ||
2389 | target->widget.rect.size = w->rect.size; | ||
2390 | setFlags_Widget(as_Widget(target), fixedPosition_WidgetFlag | fixedSize_WidgetFlag, iTrue); | ||
2391 | swap_DocumentWidget_(target, d->doc, d); | ||
2392 | addChildPos_Widget(swipeParent, iClob(target), back_WidgetAddPos); | ||
2393 | setFlags_Widget(as_Widget(d), refChildrenOffset_WidgetFlag, iTrue); | ||
2394 | as_Widget(d)->offsetRef = swipeParent; | ||
2395 | setVisualOffset_Widget(as_Widget(target), value_Anim(&w->visualOffset), 0, 0); | ||
2396 | setVisualOffset_Widget(as_Widget(target), width_Widget(target), 150, 0); | ||
2397 | destroy_Widget(as_Widget(target)); /* will be actually deleted after animation finishes */ | ||
2398 | setVisualOffset_Widget(w, 0, 0, 0); | ||
2399 | postCommand_Widget(d, "navigate.back"); | ||
2400 | return iTrue; | ||
2401 | } | ||
2402 | return iFalse; | ||
2403 | } | ||
2404 | |||
2213 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { | 2405 | static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) { |
2214 | iWidget *w = as_Widget(d); | 2406 | iWidget *w = as_Widget(d); |
2215 | if (equal_Command(cmd, "document.openurls.changed")) { | 2407 | if (equal_Command(cmd, "document.openurls.changed")) { |
@@ -2258,6 +2450,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2258 | return iFalse; | 2450 | return iFalse; |
2259 | } | 2451 | } |
2260 | else if (equal_Command(cmd, "theme.changed") && document_App() == d) { | 2452 | else if (equal_Command(cmd, "theme.changed") && document_App() == d) { |
2453 | // invalidateTheme_History(d->mod.history); /* cached colors */ | ||
2261 | updateTheme_DocumentWidget_(d); | 2454 | updateTheme_DocumentWidget_(d); |
2262 | updateVisible_DocumentWidget_(d); | 2455 | updateVisible_DocumentWidget_(d); |
2263 | updateTrust_DocumentWidget_(d, NULL); | 2456 | updateTrust_DocumentWidget_(d, NULL); |
@@ -2347,6 +2540,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2347 | msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent))); | 2540 | msg, "%s\n", formatCStrs_Lang("num.bytes.n", size_Block(&d->sourceContent))); |
2348 | } | 2541 | } |
2349 | } | 2542 | } |
2543 | /* TODO: On mobile, omit the CA status. */ | ||
2350 | appendFormat_String( | 2544 | appendFormat_String( |
2351 | msg, | 2545 | msg, |
2352 | "\n%s${pageinfo.cert.status}\n" | 2546 | "\n%s${pageinfo.cert.status}\n" |
@@ -2651,6 +2845,18 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2651 | return iTrue; | 2845 | return iTrue; |
2652 | } | 2846 | } |
2653 | else if (equal_Command(cmd, "navigate.back") && document_App() == d) { | 2847 | else if (equal_Command(cmd, "navigate.back") && document_App() == d) { |
2848 | if (isPortraitPhone_App()) { | ||
2849 | if (d->flags & openedFromSidebar_DocumentWidgetFlag && | ||
2850 | !isVisible_Widget(findWidget_App("sidebar"))) { | ||
2851 | postCommand_App("sidebar.toggle"); | ||
2852 | showToolbar_Root(get_Root(), iTrue); | ||
2853 | #if defined (iPlatformAppleMobile) | ||
2854 | playHapticEffect_iOS(gentleTap_HapticEffect); | ||
2855 | #endif | ||
2856 | return iTrue; | ||
2857 | } | ||
2858 | d->flags &= ~openedFromSidebar_DocumentWidgetFlag; | ||
2859 | } | ||
2654 | if (d->request) { | 2860 | if (d->request) { |
2655 | postCommandf_Root(w->root, | 2861 | postCommandf_Root(w->root, |
2656 | "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); | 2862 | "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); |
@@ -2813,7 +3019,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2813 | uiHeading_ColorEscape "${heading.import.bookmarks}", | 3019 | uiHeading_ColorEscape "${heading.import.bookmarks}", |
2814 | formatCStrs_Lang("dlg.import.found.n", count), | 3020 | formatCStrs_Lang("dlg.import.found.n", count), |
2815 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | 3021 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, |
2816 | { format_CStr(cstrCount_Lang("dlg.import.add.n", count), | 3022 | { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count), |
2817 | uiTextAction_ColorEscape, | 3023 | uiTextAction_ColorEscape, |
2818 | count), | 3024 | count), |
2819 | 0, | 3025 | 0, |
@@ -2872,6 +3078,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2872 | else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) { | 3078 | else if (startsWith_CStr(cmd, "pinch.") && document_Command(cmd) == d) { |
2873 | return handlePinch_DocumentWidget_(d, cmd); | 3079 | return handlePinch_DocumentWidget_(d, cmd); |
2874 | } | 3080 | } |
3081 | else if ((startsWith_CStr(cmd, "edgeswipe.") || startsWith_CStr(cmd, "swipe.")) && | ||
3082 | document_App() == d) { | ||
3083 | return handleSwipe_DocumentWidget_(d, cmd); | ||
3084 | } | ||
2875 | return iFalse; | 3085 | return iFalse; |
2876 | } | 3086 | } |
2877 | 3087 | ||
@@ -3320,6 +3530,18 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3320 | d->contextLink->linkId) }); | 3530 | d->contextLink->linkId) }); |
3321 | } | 3531 | } |
3322 | } | 3532 | } |
3533 | if (equalCase_Rangecc(scheme, "file")) { | ||
3534 | /* Local files may be deleted. */ | ||
3535 | pushBack_Array( | ||
3536 | &items, | ||
3537 | &(iMenuItem){ delete_Icon " " uiTextCaution_ColorEscape | ||
3538 | "${link.file.delete}", | ||
3539 | 0, | ||
3540 | 0, | ||
3541 | format_CStr("!file.delete confirm:1 path:%s", | ||
3542 | cstrCollect_String( | ||
3543 | localFilePathFromUrl_String(linkUrl))) }); | ||
3544 | } | ||
3323 | } | 3545 | } |
3324 | else if (deviceType_App() == desktop_AppDeviceType) { | 3546 | else if (deviceType_App() == desktop_AppDeviceType) { |
3325 | if (!isEmpty_Range(&d->selectMark)) { | 3547 | if (!isEmpty_Range(&d->selectMark)) { |
@@ -3877,9 +4099,9 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3877 | int bg = tmBackgroundOpenLink_ColorId; | 4099 | int bg = tmBackgroundOpenLink_ColorId; |
3878 | const int frame = tmFrameOpenLink_ColorId; | 4100 | const int frame = tmFrameOpenLink_ColorId; |
3879 | iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), | 4101 | iRect wideRect = { init_I2(left_Rect(d->widgetBounds), visPos.y), |
3880 | init_I2(width_Rect(d->widgetBounds) + | 4102 | init_I2(width_Rect(d->widgetBounds) + |
3881 | width_Widget(d->widget->scroll), | 4103 | width_Widget(d->widget->scroll), |
3882 | height_Rect(run->visBounds)) }; | 4104 | height_Rect(run->visBounds)) }; |
3883 | /* The first line is composed of two runs that may be drawn in either order, so | 4105 | /* The first line is composed of two runs that may be drawn in either order, so |
3884 | only draw half of the background. */ | 4106 | only draw half of the background. */ |
3885 | if (run->flags & decoration_GmRunFlag) { | 4107 | if (run->flags & decoration_GmRunFlag) { |
@@ -3943,7 +4165,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3943 | drawCentered_Text(defaultContentSmall_FontId, | 4165 | drawCentered_Text(defaultContentSmall_FontId, |
3944 | circleArea, | 4166 | circleArea, |
3945 | iTrue, | 4167 | iTrue, |
3946 | tmQuote_ColorId, | 4168 | tmQuote_ColorId, |
3947 | "%lc", | 4169 | "%lc", |
3948 | (int) ordChar); | 4170 | (int) ordChar); |
3949 | goto runDrawn; | 4171 | goto runDrawn; |
@@ -4181,7 +4403,7 @@ static void drawSideElements_DocumentWidget_(const iDocumentWidget *d) { | |||
4181 | iDrawBufs * dbuf = d->drawBufs; | 4403 | iDrawBufs * dbuf = d->drawBufs; |
4182 | iPaint p; | 4404 | iPaint p; |
4183 | init_Paint(&p); | 4405 | init_Paint(&p); |
4184 | setClip_Paint(&p, bounds); | 4406 | setClip_Paint(&p, boundsWithoutVisualOffset_Widget(w)); |
4185 | /* Side icon and current heading. */ | 4407 | /* Side icon and current heading. */ |
4186 | if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { | 4408 | if (prefs_App()->sideIcon && opacity > 0 && dbuf->sideIconBuf) { |
4187 | const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); | 4409 | const iInt2 texSize = size_SDLTexture(dbuf->sideIconBuf); |
@@ -4266,7 +4488,8 @@ static iBool render_DocumentWidget_(const iDocumentWidget *d, iDrawContext *ctx, | |||
4266 | /* Swap buffers around to have room available both before and after the visible region. */ | 4488 | /* Swap buffers around to have room available both before and after the visible region. */ |
4267 | allocVisBuffer_DocumentWidget_(d); | 4489 | allocVisBuffer_DocumentWidget_(d); |
4268 | reposition_VisBuf(visBuf, vis); | 4490 | reposition_VisBuf(visBuf, vis); |
4269 | /* Redraw the invalid ranges. */ { | 4491 | /* Redraw the invalid ranges. */ |
4492 | if (~flags_Widget(constAs_Widget(d)) & destroyPending_WidgetFlag) { | ||
4270 | iPaint *p = &ctx->paint; | 4493 | iPaint *p = &ctx->paint; |
4271 | init_Paint(p); | 4494 | init_Paint(p); |
4272 | iForIndices(i, visBuf->buffers) { | 4495 | iForIndices(i, visBuf->buffers) { |
@@ -4430,12 +4653,17 @@ static void prerender_DocumentWidget_(iAny *context) { | |||
4430 | } | 4653 | } |
4431 | 4654 | ||
4432 | static void draw_DocumentWidget_(const iDocumentWidget *d) { | 4655 | static void draw_DocumentWidget_(const iDocumentWidget *d) { |
4433 | const iWidget *w = constAs_Widget(d); | 4656 | const iWidget *w = constAs_Widget(d); |
4434 | const iRect bounds = bounds_Widget(w); | 4657 | const iRect bounds = bounds_Widget(w); |
4658 | const iRect boundsWithoutVisOff = boundsWithoutVisualOffset_Widget(w); | ||
4659 | const iRect clipBounds = intersect_Rect(bounds, boundsWithoutVisOff); | ||
4435 | if (width_Rect(bounds) <= 0) { | 4660 | if (width_Rect(bounds) <= 0) { |
4436 | return; | 4661 | return; |
4437 | } | 4662 | } |
4438 | // draw_Widget(w); | 4663 | /* TODO: Come up with a better palette caching system. |
4664 | It should be able to recompute cached colors in `History` when the theme has changed. | ||
4665 | Cache the theme seed in `GmDocument`? */ | ||
4666 | // makePaletteGlobal_GmDocument(d->doc); | ||
4439 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { | 4667 | if (d->drawBufs->flags & updateTimestampBuf_DrawBufsFlag) { |
4440 | updateTimestampBuf_DocumentWidget_(d); | 4668 | updateTimestampBuf_DocumentWidget_(d); |
4441 | } | 4669 | } |
@@ -4450,14 +4678,15 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4450 | .vis = vis, | 4678 | .vis = vis, |
4451 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, | 4679 | .showLinkNumbers = (d->flags & showLinkNumbers_DocumentWidgetFlag) != 0, |
4452 | }; | 4680 | }; |
4681 | init_Paint(&ctx.paint); | ||
4453 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); | 4682 | render_DocumentWidget_(d, &ctx, iFalse /* just the mandatory parts */); |
4454 | setClip_Paint(&ctx.paint, bounds); | 4683 | setClip_Paint(&ctx.paint, clipBounds); |
4455 | int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); | 4684 | int yTop = docBounds.pos.y - pos_SmoothScroll(&d->scrollY); |
4456 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); | 4685 | draw_VisBuf(d->visBuf, init_I2(bounds.pos.x, yTop), ySpan_Rect(bounds)); |
4457 | /* Text markers. */ | 4686 | /* Text markers. */ |
4458 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; | 4687 | const iBool isTouchSelecting = (flags_Widget(w) & touchDrag_WidgetFlag) != 0; |
4459 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { | 4688 | if (!isEmpty_Range(&d->foundMark) || !isEmpty_Range(&d->selectMark)) { |
4460 | SDL_Renderer *render = renderer_Window(get_Window()); | 4689 | SDL_Renderer *render = renderer_Window(get_Window()); |
4461 | ctx.firstMarkRect = zero_Rect(); | 4690 | ctx.firstMarkRect = zero_Rect(); |
4462 | ctx.lastMarkRect = zero_Rect(); | 4691 | ctx.lastMarkRect = zero_Rect(); |
4463 | SDL_SetRenderDrawBlendMode(render, | 4692 | SDL_SetRenderDrawBlendMode(render, |
@@ -4486,7 +4715,6 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4486 | } | 4715 | } |
4487 | } | 4716 | } |
4488 | drawMedia_DocumentWidget_(d, &ctx.paint); | 4717 | drawMedia_DocumentWidget_(d, &ctx.paint); |
4489 | unsetClip_Paint(&ctx.paint); | ||
4490 | /* Fill the top and bottom, in case the document is short. */ | 4718 | /* Fill the top and bottom, in case the document is short. */ |
4491 | if (yTop > top_Rect(bounds)) { | 4719 | if (yTop > top_Rect(bounds)) { |
4492 | fillRect_Paint(&ctx.paint, | 4720 | fillRect_Paint(&ctx.paint, |
@@ -4500,6 +4728,7 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4500 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), | 4728 | init_Rect(bounds.pos.x, yBottom, bounds.size.x, bottom_Rect(bounds) - yBottom), |
4501 | tmBackground_ColorId); | 4729 | tmBackground_ColorId); |
4502 | } | 4730 | } |
4731 | unsetClip_Paint(&ctx.paint); | ||
4503 | drawSideElements_DocumentWidget_(d); | 4732 | drawSideElements_DocumentWidget_(d); |
4504 | if (prefs_App()->hoverLink && d->hoverLink) { | 4733 | if (prefs_App()->hoverLink && d->hoverLink) { |
4505 | const int font = uiLabel_FontId; | 4734 | const int font = uiLabel_FontId; |
@@ -4561,6 +4790,23 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4561 | drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected", | 4790 | drawCentered_Text(uiLabelBold_FontId, rect, iFalse, uiBackground_ColorId, "%zu bytes selected", |
4562 | size_Range(&mark)); | 4791 | size_Range(&mark)); |
4563 | } | 4792 | } |
4793 | if (w->offsetRef) { | ||
4794 | const int offX = visualOffsetByReference_Widget(w); | ||
4795 | if (offX) { | ||
4796 | setClip_Paint(&ctx.paint, clipBounds); | ||
4797 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
4798 | ctx.paint.alpha = iAbs(offX) / (float) get_Window()->size.x * 300; | ||
4799 | fillRect_Paint(&ctx.paint, bounds, backgroundFadeColor_Widget()); | ||
4800 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
4801 | unsetClip_Paint(&ctx.paint); | ||
4802 | } | ||
4803 | else { | ||
4804 | /* TODO: Should have a better place to do this; drawing is supposed to be immutable. */ | ||
4805 | iWidget *mut = iConstCast(iWidget *, w); | ||
4806 | mut->offsetRef = NULL; | ||
4807 | mut->flags &= ~refChildrenOffset_WidgetFlag; | ||
4808 | } | ||
4809 | } | ||
4564 | } | 4810 | } |
4565 | 4811 | ||
4566 | /*----------------------------------------------------------------------------------------------*/ | 4812 | /*----------------------------------------------------------------------------------------------*/ |
@@ -4628,9 +4874,12 @@ static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | |||
4628 | d->flags |= urlChanged_DocumentWidgetFlag; | 4874 | d->flags |= urlChanged_DocumentWidgetFlag; |
4629 | set_String(d->mod.url, url); | 4875 | set_String(d->mod.url, url); |
4630 | } | 4876 | } |
4631 | } | 4877 | } |
4632 | 4878 | ||
4633 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { | 4879 | void setUrlFlags_DocumentWidget(iDocumentWidget *d, const iString *url, int setUrlFlags) { |
4880 | iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, | ||
4881 | (setUrlFlags & openedFromSidebar_DocumentWidgetSetUrlFlag) != 0); | ||
4882 | const iBool isFromCache = (setUrlFlags & useCachedContentIfAvailable_DocumentWidgetSetUrlFlag) != 0; | ||
4634 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4883 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
4635 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); | 4884 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); |
4636 | /* See if there a username in the URL. */ | 4885 | /* See if there a username in the URL. */ |
@@ -4642,6 +4891,7 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo | |||
4642 | 4891 | ||
4643 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, | 4892 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, |
4644 | const iBlock *source) { | 4893 | const iBlock *source) { |
4894 | d->flags &= ~openedFromSidebar_DocumentWidgetFlag; | ||
4645 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4895 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
4646 | setUrl_DocumentWidget_(d, url); | 4896 | setUrl_DocumentWidget_(d, url); |
4647 | parseUser_DocumentWidget_(d); | 4897 | parseUser_DocumentWidget_(d); |
@@ -4650,7 +4900,7 @@ void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, cons | |||
4650 | initCurrent_Time(&resp->when); | 4900 | initCurrent_Time(&resp->when); |
4651 | set_String(&resp->meta, mime); | 4901 | set_String(&resp->meta, mime); |
4652 | set_Block(&resp->body, source); | 4902 | set_Block(&resp->body, source); |
4653 | updateFromCachedResponse_DocumentWidget_(d, 0, resp); | 4903 | updateFromCachedResponse_DocumentWidget_(d, 0, resp, NULL); |
4654 | delete_GmResponse(resp); | 4904 | delete_GmResponse(resp); |
4655 | } | 4905 | } |
4656 | 4906 | ||
@@ -4659,12 +4909,12 @@ iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { | |||
4659 | delete_History(d->mod.history); | 4909 | delete_History(d->mod.history); |
4660 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); | 4910 | d->initNormScrollY = normScrollPos_DocumentWidget_(d); |
4661 | d->mod.history = copy_History(orig->mod.history); | 4911 | d->mod.history = copy_History(orig->mod.history); |
4662 | setUrlFromCache_DocumentWidget(d, orig->mod.url, iTrue); | 4912 | setUrlFlags_DocumentWidget(d, orig->mod.url, useCachedContentIfAvailable_DocumentWidgetSetUrlFlag); |
4663 | return d; | 4913 | return d; |
4664 | } | 4914 | } |
4665 | 4915 | ||
4666 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { | 4916 | void setUrl_DocumentWidget(iDocumentWidget *d, const iString *url) { |
4667 | setUrlFromCache_DocumentWidget(d, url, iFalse); | 4917 | setUrlFlags_DocumentWidget(d, url, 0); |
4668 | } | 4918 | } |
4669 | 4919 | ||
4670 | void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) { | 4920 | void setInitialScroll_DocumentWidget(iDocumentWidget *d, float normScrollY) { |
@@ -4675,6 +4925,11 @@ void setRedirectCount_DocumentWidget(iDocumentWidget *d, int count) { | |||
4675 | d->redirectCount = count; | 4925 | d->redirectCount = count; |
4676 | } | 4926 | } |
4677 | 4927 | ||
4928 | void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *d, iBool fromSidebar) { | ||
4929 | iChangeFlags(d->flags, openedFromSidebar_DocumentWidgetFlag, fromSidebar); | ||
4930 | // setCachedDocument_History(d->mod.history, d->doc, fromSidebar); | ||
4931 | } | ||
4932 | |||
4678 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { | 4933 | iBool isRequestOngoing_DocumentWidget(const iDocumentWidget *d) { |
4679 | return d->request != NULL; | 4934 | return d->request != NULL; |
4680 | } | 4935 | } |
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index c038f981..1921b25a 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h | |||
@@ -45,11 +45,17 @@ const iString * bookmarkTitle_DocumentWidget (const iDocumentWidget *); | |||
45 | const iString * feedTitle_DocumentWidget (const iDocumentWidget *); | 45 | const iString * feedTitle_DocumentWidget (const iDocumentWidget *); |
46 | int documentWidth_DocumentWidget (const iDocumentWidget *); | 46 | int documentWidth_DocumentWidget (const iDocumentWidget *); |
47 | 47 | ||
48 | enum iDocumentWidgetSetUrlFlags { | ||
49 | useCachedContentIfAvailable_DocumentWidgetSetUrlFlag = iBit(1), | ||
50 | openedFromSidebar_DocumentWidgetSetUrlFlag = iBit(2), | ||
51 | }; | ||
52 | |||
48 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); | 53 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); |
49 | void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache); | 54 | void setUrlFlags_DocumentWidget (iDocumentWidget *, const iString *url, int setUrlFlags); |
50 | void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); | 55 | void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); |
51 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ | 56 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ |
52 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); | 57 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); |
53 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); | 58 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); |
59 | void setOpenedFromSidebar_DocumentWidget(iDocumentWidget *, iBool fromSidebar); | ||
54 | 60 | ||
55 | void updateSize_DocumentWidget (iDocumentWidget *); | 61 | void updateSize_DocumentWidget (iDocumentWidget *); |
diff --git a/src/ui/indicatorwidget.c b/src/ui/indicatorwidget.c index 4a829ae3..bc0bd0fa 100644 --- a/src/ui/indicatorwidget.c +++ b/src/ui/indicatorwidget.c | |||
@@ -109,7 +109,7 @@ void draw_IndicatorWidget_(const iIndicatorWidget *d) { | |||
109 | colors[0] = black_ColorId; | 109 | colors[0] = black_ColorId; |
110 | } | 110 | } |
111 | fillRect_Paint(&p, | 111 | fillRect_Paint(&p, |
112 | (iRect){ topLeft_Rect(rect), init_I2(pos * width_Rect(rect), gap_UI / 4)}, | 112 | (iRect){ topLeft_Rect(rect), init_I2(pos * width_Rect(rect), gap_UI / 3)}, |
113 | colors[isCompleted_IndicatorWidget_(d) ? 1 : 0]); | 113 | colors[isCompleted_IndicatorWidget_(d) ? 1 : 0]); |
114 | } | 114 | } |
115 | } | 115 | } |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 39da1c3b..b108ee17 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -80,6 +80,7 @@ enum iInputWidgetFlag { | |||
80 | markWords_InputWidgetFlag = iBit(8), | 80 | markWords_InputWidgetFlag = iBit(8), |
81 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 81 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
82 | enterKeyEnabled_InputWidgetFlag = iBit(10), | 82 | enterKeyEnabled_InputWidgetFlag = iBit(10), |
83 | enterKeyInsertsLineFeed_InputWidgetFlag = iBit(11), | ||
83 | }; | 84 | }; |
84 | 85 | ||
85 | /*----------------------------------------------------------------------------------------------*/ | 86 | /*----------------------------------------------------------------------------------------------*/ |
@@ -347,8 +348,11 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
347 | d->lastCursor = 0; | 348 | d->lastCursor = 0; |
348 | d->cursorLine = 0; | 349 | d->cursorLine = 0; |
349 | d->lastUpdateWidth = 0; | 350 | d->lastUpdateWidth = 0; |
350 | d->verticalMoveX = -1; /* TODO: Use this. */ | 351 | d->verticalMoveX = -1; /* TODO: Use this. */ |
351 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; | 352 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; |
353 | if (deviceType_App() != desktop_AppDeviceType) { | ||
354 | d->inFlags |= enterKeyInsertsLineFeed_InputWidgetFlag; | ||
355 | } | ||
352 | iZap(d->mark); | 356 | iZap(d->mark); |
353 | setMaxLen_InputWidget(d, maxLen); | 357 | setMaxLen_InputWidget(d, maxLen); |
354 | d->maxLayoutLines = iInvalidSize; | 358 | d->maxLayoutLines = iInvalidSize; |
@@ -464,6 +468,10 @@ void setValidator_InputWidget(iInputWidget *d, iInputWidgetValidatorFunc validat | |||
464 | d->validatorContext = context; | 468 | d->validatorContext = context; |
465 | } | 469 | } |
466 | 470 | ||
471 | void setEnterInsertsLF_InputWidget(iInputWidget *d, iBool enterInsertsLF) { | ||
472 | iChangeFlags(d->inFlags, enterKeyInsertsLineFeed_InputWidgetFlag, enterInsertsLF); | ||
473 | } | ||
474 | |||
467 | void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) { | 475 | void setEnterKeyEnabled_InputWidget(iInputWidget *d, iBool enterKeyEnabled) { |
468 | iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled); | 476 | iChangeFlags(d->inFlags, enterKeyEnabled_InputWidgetFlag, enterKeyEnabled); |
469 | } | 477 | } |
@@ -714,12 +722,12 @@ iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine | |||
714 | } | 722 | } |
715 | 723 | ||
716 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 724 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { |
725 | size_t index = line->offset; | ||
717 | if (x <= 0) { | 726 | if (x <= 0) { |
718 | return line->offset; | 727 | return index; |
719 | } | 728 | } |
720 | const char *endPos; | 729 | const char *endPos; |
721 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); | 730 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); |
722 | size_t index = line->offset; | ||
723 | if (endPos == constEnd_String(&line->text)) { | 731 | if (endPos == constEnd_String(&line->text)) { |
724 | index += line->len; | 732 | index += line->len; |
725 | } | 733 | } |
@@ -1166,7 +1174,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1166 | case SDLK_KP_ENTER: | 1174 | case SDLK_KP_ENTER: |
1167 | if (mods == KMOD_SHIFT || (d->maxLen == 0 && | 1175 | if (mods == KMOD_SHIFT || (d->maxLen == 0 && |
1168 | ~d->inFlags & isUrl_InputWidgetFlag && | 1176 | ~d->inFlags & isUrl_InputWidgetFlag && |
1169 | deviceType_App() != desktop_AppDeviceType)) { | 1177 | d->inFlags & enterKeyInsertsLineFeed_InputWidgetFlag)) { |
1170 | pushUndo_InputWidget_(d); | 1178 | pushUndo_InputWidget_(d); |
1171 | deleteMarked_InputWidget_(d); | 1179 | deleteMarked_InputWidget_(d); |
1172 | insertChar_InputWidget_(d, '\n'); | 1180 | insertChar_InputWidget_(d, '\n'); |
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index 5c39aae0..c70d9ad6 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -41,20 +41,21 @@ struct Impl_InputWidgetContentPadding { | |||
41 | 41 | ||
42 | typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); | 42 | typedef void (*iInputWidgetValidatorFunc)(iInputWidget *, void *context); |
43 | 43 | ||
44 | void setHint_InputWidget (iInputWidget *, const char *hintText); | 44 | void setHint_InputWidget (iInputWidget *, const char *hintText); |
45 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); | 45 | void setMode_InputWidget (iInputWidget *, enum iInputMode mode); |
46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); | 46 | void setMaxLen_InputWidget (iInputWidget *, size_t maxLen); |
47 | void setText_InputWidget (iInputWidget *, const iString *text); | 47 | void setText_InputWidget (iInputWidget *, const iString *text); |
48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); | 48 | void setTextCStr_InputWidget (iInputWidget *, const char *cstr); |
49 | void setFont_InputWidget (iInputWidget *, int fontId); | 49 | void setFont_InputWidget (iInputWidget *, int fontId); |
50 | void setCursor_InputWidget (iInputWidget *, size_t pos); | 50 | void setCursor_InputWidget (iInputWidget *, size_t pos); |
51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ | 51 | void setContentPadding_InputWidget (iInputWidget *, int left, int right); /* only affects the text entry */ |
52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); | 52 | void setMaxLayoutLines_InputWidget (iInputWidget *, size_t maxLayoutLines); |
53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); | 53 | void setValidator_InputWidget (iInputWidget *, iInputWidgetValidatorFunc validator, void *context); |
54 | void setEnterInsertsLF_InputWidget (iInputWidget *, iBool enterInsertsLF); | ||
54 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); | 55 | void setEnterKeyEnabled_InputWidget (iInputWidget *, iBool enterKeyEnabled); |
55 | void begin_InputWidget (iInputWidget *); | 56 | void begin_InputWidget (iInputWidget *); |
56 | void end_InputWidget (iInputWidget *, iBool accept); | 57 | void end_InputWidget (iInputWidget *, iBool accept); |
57 | void selectAll_InputWidget (iInputWidget *); | 58 | void selectAll_InputWidget (iInputWidget *); |
58 | 59 | ||
59 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); | 60 | void setSelectAllOnFocus_InputWidget (iInputWidget *, iBool selectAllOnFocus); |
60 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); | 61 | void setSensitiveContent_InputWidget (iInputWidget *, iBool isSensitive); |
@@ -62,15 +63,13 @@ void setUrlContent_InputWidget (iInputWidget *, iBool isUrl); | |||
62 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); | 63 | void setNotifyEdits_InputWidget (iInputWidget *, iBool notifyEdits); |
63 | void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); | 64 | void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); |
64 | 65 | ||
65 | const iString * text_InputWidget (const iInputWidget *); | 66 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); |
67 | const iString * text_InputWidget (const iInputWidget *); | ||
66 | 68 | ||
67 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { | 69 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { |
68 | return cstr_String(text_InputWidget(d)); | 70 | return cstr_String(text_InputWidget(d)); |
69 | } | 71 | } |
70 | 72 | ||
71 | iInputWidgetContentPadding | ||
72 | contentPadding_InputWidget (const iInputWidget *); | ||
73 | |||
74 | iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { | 73 | iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { |
75 | iInputWidget *d = new_InputWidget(maxLen); | 74 | iInputWidget *d = new_InputWidget(maxLen); |
76 | setHint_InputWidget(d, hint); | 75 | setHint_InputWidget(d, hint); |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index 95f281be..b68ab793 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -44,6 +44,9 @@ struct Impl_LabelWidget { | |||
44 | struct { | 44 | struct { |
45 | uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ | 45 | uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ |
46 | uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ | 46 | uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ |
47 | uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ | ||
48 | uint8_t noTopFrame : 1; | ||
49 | uint8_t wrap : 1; | ||
47 | } flags; | 50 | } flags; |
48 | }; | 51 | }; |
49 | 52 | ||
@@ -206,7 +209,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
206 | } | 209 | } |
207 | } | 210 | } |
208 | int colorEscape = none_ColorId; | 211 | int colorEscape = none_ColorId; |
209 | if (startsWith_String(&d->label, "\r")) { | 212 | if (startsWith_String(&d->label, "\v")) { |
210 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ | 213 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ |
211 | } | 214 | } |
212 | if (isHover_LabelWidget_(d)) { | 215 | if (isHover_LabelWidget_(d)) { |
@@ -289,7 +292,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
289 | }; | 292 | }; |
290 | drawLines_Paint(&p, points + 2, 3, frame2); | 293 | drawLines_Paint(&p, points + 2, 3, frame2); |
291 | drawLines_Paint( | 294 | drawLines_Paint( |
292 | &p, points, !isHover && flags & noTopFrame_WidgetFlag ? 2 : 3, frame); | 295 | &p, points, !isHover && flags & d->flags.noTopFrame ? 2 : 3, frame); |
293 | } | 296 | } |
294 | } | 297 | } |
295 | setClip_Paint(&p, rect); | 298 | setClip_Paint(&p, rect); |
@@ -315,7 +318,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
315 | cstr_String(&str)); | 318 | cstr_String(&str)); |
316 | deinit_String(&str); | 319 | deinit_String(&str); |
317 | } | 320 | } |
318 | if (flags & wrapText_WidgetFlag) { | 321 | if (d->flags.wrap) { |
319 | const iRect inner = adjusted_Rect(innerBounds_Widget(w), init_I2(iconPad, 0), zero_I2()); | 322 | const iRect inner = adjusted_Rect(innerBounds_Widget(w), init_I2(iconPad, 0), zero_I2()); |
320 | const int wrap = inner.size.x; | 323 | const int wrap = inner.size.x; |
321 | drawWrapRange_Text(d->font, topLeft_Rect(inner), wrap, fg, range_String(&d->label)); | 324 | drawWrapRange_Text(d->font, topLeft_Rect(inner), wrap, fg, range_String(&d->label)); |
@@ -348,14 +351,14 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
348 | cstr_String(&d->label)); | 351 | cstr_String(&d->label)); |
349 | } | 352 | } |
350 | else { | 353 | else { |
351 | drawCentered_Text(d->font, | 354 | drawCenteredOutline_Text( |
352 | adjusted_Rect(bounds, | 355 | d->font, |
353 | add_I2(zero_I2(), init_I2(iconPad, 0)), | 356 | adjusted_Rect(bounds, add_I2(zero_I2(), init_I2(iconPad, 0)), neg_I2(zero_I2())), |
354 | neg_I2(zero_I2())), | 357 | d->flags.alignVisual, |
355 | d->flags.alignVisual, | 358 | d->flags.drawAsOutline ? fg : none_ColorId, |
356 | fg, | 359 | d->flags.drawAsOutline ? d->widget.bgColor : fg, |
357 | "%s", | 360 | "%s", |
358 | cstr_String(&d->label)); | 361 | cstr_String(&d->label)); |
359 | } | 362 | } |
360 | if (flags & chevron_WidgetFlag) { | 363 | if (flags & chevron_WidgetFlag) { |
361 | const iRect chRect = rect; | 364 | const iRect chRect = rect; |
@@ -370,7 +373,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
370 | 373 | ||
371 | static void sizeChanged_LabelWidget_(iLabelWidget *d) { | 374 | static void sizeChanged_LabelWidget_(iLabelWidget *d) { |
372 | iWidget *w = as_Widget(d); | 375 | iWidget *w = as_Widget(d); |
373 | if (flags_Widget(w) & wrapText_WidgetFlag) { | 376 | if (d->flags.wrap) { |
374 | if (flags_Widget(w) & fixedHeight_WidgetFlag) { | 377 | if (flags_Widget(w) & fixedHeight_WidgetFlag) { |
375 | /* Calculate a new height based on the wrapping. */ | 378 | /* Calculate a new height based on the wrapping. */ |
376 | w->rect.size.y = advanceWrapRange_Text( | 379 | w->rect.size.y = advanceWrapRange_Text( |
@@ -408,7 +411,7 @@ void updateSize_LabelWidget(iLabelWidget *d) { | |||
408 | w->minSize.y = size.y; /* vertically text must remain visible */ | 411 | w->minSize.y = size.y; /* vertically text must remain visible */ |
409 | } | 412 | } |
410 | /* Wrapped text implies that width must be defined by arrangement. */ | 413 | /* Wrapped text implies that width must be defined by arrangement. */ |
411 | if (!(flags & (fixedWidth_WidgetFlag | wrapText_WidgetFlag))) { | 414 | if (~flags & fixedWidth_WidgetFlag && !d->flags.wrap) { |
412 | w->rect.size.x = size.x; | 415 | w->rect.size.x = size.x; |
413 | } | 416 | } |
414 | if (~flags & fixedHeight_WidgetFlag) { | 417 | if (~flags & fixedHeight_WidgetFlag) { |
@@ -440,8 +443,11 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | |||
440 | d->kmods = 0; | 443 | d->kmods = 0; |
441 | init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); | 444 | init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); |
442 | setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); | 445 | setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); |
443 | d->flags.alignVisual = iFalse; | 446 | d->flags.alignVisual = iFalse; |
444 | d->flags.noAutoMinHeight = iFalse; | 447 | d->flags.noAutoMinHeight = iFalse; |
448 | d->flags.drawAsOutline = iFalse; | ||
449 | d->flags.noTopFrame = iFalse; | ||
450 | d->flags.wrap = iFalse; | ||
445 | updateSize_LabelWidget(d); | 451 | updateSize_LabelWidget(d); |
446 | updateKey_LabelWidget_(d); /* could be bound to another key */ | 452 | updateKey_LabelWidget_(d); /* could be bound to another key */ |
447 | } | 453 | } |
@@ -481,6 +487,20 @@ void setNoAutoMinHeight_LabelWidget(iLabelWidget *d, iBool noAutoMinHeight) { | |||
481 | } | 487 | } |
482 | } | 488 | } |
483 | 489 | ||
490 | void setNoTopFrame_LabelWidget(iLabelWidget *d, iBool noTopFrame) { | ||
491 | d->flags.noTopFrame = noTopFrame; | ||
492 | } | ||
493 | |||
494 | void setWrap_LabelWidget(iLabelWidget *d, iBool wrap) { | ||
495 | d->flags.wrap = wrap; | ||
496 | } | ||
497 | |||
498 | void setOutline_LabelWidget(iLabelWidget *d, iBool drawAsOutline) { | ||
499 | if (d) { | ||
500 | d->flags.drawAsOutline = drawAsOutline; | ||
501 | } | ||
502 | } | ||
503 | |||
484 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { | 504 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { |
485 | set_String(&d->label, text); | 505 | set_String(&d->label, text); |
486 | set_String(&d->srcLabel, text); | 506 | set_String(&d->srcLabel, text); |
@@ -495,6 +515,11 @@ void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | |||
495 | refresh_Widget(&d->widget); | 515 | refresh_Widget(&d->widget); |
496 | } | 516 | } |
497 | 517 | ||
518 | void updateTextAndResizeWidthCStr_LabelWidget(iLabelWidget *d, const char *text) { | ||
519 | updateTextCStr_LabelWidget(d, text); | ||
520 | d->widget.rect.size.x = defaultSize_LabelWidget(d).x; | ||
521 | } | ||
522 | |||
498 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | 523 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { |
499 | setCStr_String(&d->label, text); | 524 | setCStr_String(&d->label, text); |
500 | set_String(&d->srcLabel, &d->label); | 525 | set_String(&d->srcLabel, &d->label); |
@@ -537,6 +562,10 @@ iChar icon_LabelWidget(const iLabelWidget *d) { | |||
537 | return d->icon; | 562 | return d->icon; |
538 | } | 563 | } |
539 | 564 | ||
565 | iBool isWrapped_LabelWidget(const iLabelWidget *d) { | ||
566 | return d->flags.wrap; | ||
567 | } | ||
568 | |||
540 | const iString *text_LabelWidget(const iLabelWidget *d) { | 569 | const iString *text_LabelWidget(const iLabelWidget *d) { |
541 | if (!d) return collectNew_String(); | 570 | if (!d) return collectNew_String(); |
542 | return &d->label; | 571 | return &d->label; |
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h index e38a1dc8..b8b6fd87 100644 --- a/src/ui/labelwidget.h +++ b/src/ui/labelwidget.h | |||
@@ -31,6 +31,9 @@ iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *comma | |||
31 | 31 | ||
32 | void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); | 32 | void setAlignVisually_LabelWidget(iLabelWidget *, iBool alignVisual); |
33 | void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight); | 33 | void setNoAutoMinHeight_LabelWidget(iLabelWidget *, iBool noAutoMinHeight); |
34 | void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); | ||
35 | void setWrap_LabelWidget (iLabelWidget *, iBool wrap); | ||
36 | void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); | ||
34 | void setFont_LabelWidget (iLabelWidget *, int fontId); | 37 | void setFont_LabelWidget (iLabelWidget *, int fontId); |
35 | void setTextColor_LabelWidget (iLabelWidget *, int color); | 38 | void setTextColor_LabelWidget (iLabelWidget *, int color); |
36 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ | 39 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ |
@@ -43,12 +46,15 @@ void updateSize_LabelWidget (iLabelWidget *); | |||
43 | void updateText_LabelWidget (iLabelWidget *, const iString *text); /* not resized */ | 46 | void updateText_LabelWidget (iLabelWidget *, const iString *text); /* not resized */ |
44 | void updateTextCStr_LabelWidget (iLabelWidget *, const char *text); /* not resized */ | 47 | void updateTextCStr_LabelWidget (iLabelWidget *, const char *text); /* not resized */ |
45 | 48 | ||
49 | void updateTextAndResizeWidthCStr_LabelWidget (iLabelWidget *, const char *text); | ||
50 | |||
46 | iInt2 defaultSize_LabelWidget (const iLabelWidget *); | 51 | iInt2 defaultSize_LabelWidget (const iLabelWidget *); |
47 | int font_LabelWidget (const iLabelWidget *); | 52 | int font_LabelWidget (const iLabelWidget *); |
48 | const iString * text_LabelWidget (const iLabelWidget *); | 53 | const iString * text_LabelWidget (const iLabelWidget *); |
49 | const iString * sourceText_LabelWidget (const iLabelWidget *); /* untranslated */ | 54 | const iString * sourceText_LabelWidget (const iLabelWidget *); /* untranslated */ |
50 | const iString * command_LabelWidget (const iLabelWidget *); | 55 | const iString * command_LabelWidget (const iLabelWidget *); |
51 | iChar icon_LabelWidget (const iLabelWidget *); | 56 | iChar icon_LabelWidget (const iLabelWidget *); |
57 | iBool isWrapped_LabelWidget (const iLabelWidget *); | ||
52 | 58 | ||
53 | iLabelWidget *newKeyMods_LabelWidget(const char *label, int key, int kmods, const char *command); | 59 | iLabelWidget *newKeyMods_LabelWidget(const char *label, int key, int kmods, const char *command); |
54 | iLabelWidget *newColor_LabelWidget (const char *text, int color); | 60 | iLabelWidget *newColor_LabelWidget (const char *text, int color); |
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index bc417fc3..fa09b214 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c | |||
@@ -118,7 +118,7 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
118 | isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", | 118 | isPaused_Player(d->player) ? "\U0001f782" : "\u23f8", |
119 | uiContent_FontId); | 119 | uiContent_FontId); |
120 | drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); | 120 | drawPlayerButton_(p, d->rewindRect, "\u23ee", uiContent_FontId); |
121 | drawPlayerButton_(p, d->menuRect, "\U0001d362", uiContent_FontId); | 121 | drawPlayerButton_(p, d->menuRect, menu_Icon, uiContent_FontId); |
122 | if (!isAdjusting) { | 122 | if (!isAdjusting) { |
123 | drawPlayerButton_( | 123 | drawPlayerButton_( |
124 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); | 124 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 263fc141..0ff3fe85 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -120,7 +120,7 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
120 | } | 120 | } |
121 | iForEach(ObjectList, i, children_Widget(detailStack)) { | 121 | iForEach(ObjectList, i, children_Widget(detailStack)) { |
122 | iWidget *panel = i.object; | 122 | iWidget *panel = i.object; |
123 | setFlags_Widget(panel, edgeDraggable_WidgetFlag, !isSideBySide); | 123 | setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide); |
124 | if (isSideBySide) { | 124 | if (isSideBySide) { |
125 | setVisualOffset_Widget(panel, 0, 0, 0); | 125 | setVisualOffset_Widget(panel, 0, 0, 0); |
126 | } | 126 | } |
@@ -150,8 +150,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | 150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); |
151 | return iTrue; | 151 | return iTrue; |
152 | } | 152 | } |
153 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && | 153 | if (equal_Command(cmd, "swipe.back")) { |
154 | argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { | ||
155 | postCommand_App("panel.close"); | 154 | postCommand_App("panel.close"); |
156 | return iTrue; | 155 | return iTrue; |
157 | } | 156 | } |
@@ -201,6 +200,8 @@ static iBool isTwoColumnPage_(iWidget *d) { | |||
201 | 200 | ||
202 | static iBool isOmittedPref_(const iString *id) { | 201 | static iBool isOmittedPref_(const iString *id) { |
203 | static const char *omittedPrefs[] = { | 202 | static const char *omittedPrefs[] = { |
203 | "prefs.userfont", | ||
204 | "prefs.animate", | ||
204 | "prefs.smoothscroll", | 205 | "prefs.smoothscroll", |
205 | "prefs.imageloadscroll", | 206 | "prefs.imageloadscroll", |
206 | "prefs.pinsplit", | 207 | "prefs.pinsplit", |
@@ -413,7 +414,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
413 | setFlags_Widget(sheet, | 414 | setFlags_Widget(sheet, |
414 | frameless_WidgetFlag | | 415 | frameless_WidgetFlag | |
415 | //resizeWidthOfChildren_WidgetFlag | | 416 | //resizeWidthOfChildren_WidgetFlag | |
416 | edgeDraggable_WidgetFlag | | 417 | leftEdgeDraggable_WidgetFlag | |
417 | commandOnClick_WidgetFlag, | 418 | commandOnClick_WidgetFlag, |
418 | iTrue); | 419 | iTrue); |
419 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ | 420 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ |
@@ -446,7 +447,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
446 | } | 447 | } |
447 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | 448 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); |
448 | /* Slide top panel with detail panels. */ { | 449 | /* Slide top panel with detail panels. */ { |
449 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); | 450 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); |
450 | topPanel->offsetRef = detailStack; | 451 | topPanel->offsetRef = detailStack; |
451 | } | 452 | } |
452 | if (prefsTabs) { | 453 | if (prefsTabs) { |
@@ -472,7 +473,8 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
472 | 0x02699, /* gear */ | 473 | 0x02699, /* gear */ |
473 | 0x1f4f1, /* mobile phone */ | 474 | 0x1f4f1, /* mobile phone */ |
474 | 0x1f3a8, /* palette */ | 475 | 0x1f3a8, /* palette */ |
475 | 0x1f523, | 476 | 0x1f5da, /* aA */ |
477 | 0x1f660, /* pointing bud */ | ||
476 | 0x1f5a7, /* computer network */ | 478 | 0x1f5a7, /* computer network */ |
477 | }; | 479 | }; |
478 | setIcon_LabelWidget(panelButton, icons[i]); | 480 | setIcon_LabelWidget(panelButton, icons[i]); |
@@ -621,6 +623,12 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
621 | /* Additional elements for preferences. */ | 623 | /* Additional elements for preferences. */ |
622 | if (isPrefs) { | 624 | if (isPrefs) { |
623 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | 625 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); |
626 | /* Management. */ { | ||
627 | iLabelWidget *idManButton = addChildFlags_Widget(topPanel, | ||
628 | iClob(makePanelButton_(person_Icon " ${sidebar.identities}", "panel.open")), | ||
629 | chevron_WidgetFlag | borderTop_WidgetFlag); | ||
630 | } | ||
631 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
624 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, | 632 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, |
625 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), | 633 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), |
626 | chevron_WidgetFlag | borderTop_WidgetFlag); | 634 | chevron_WidgetFlag | borderTop_WidgetFlag); |
diff --git a/src/ui/root.c b/src/ui/root.c index 15548e74..5266978b 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -52,10 +52,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
52 | 52 | ||
53 | #include <SDL_timer.h> | 53 | #include <SDL_timer.h> |
54 | 54 | ||
55 | #if defined (iPlatformAppleDesktop) | ||
56 | # define iHaveNativeMenus | ||
57 | #endif | ||
58 | |||
59 | #if defined (iPlatformPcDesktop) | 55 | #if defined (iPlatformPcDesktop) |
60 | /* TODO: Submenus wouldn't hurt here. */ | 56 | /* TODO: Submenus wouldn't hurt here. */ |
61 | static const iMenuItem navMenuItems_[] = { | 57 | static const iMenuItem navMenuItems_[] = { |
@@ -90,6 +86,7 @@ static const iMenuItem navMenuItems_[] = { | |||
90 | #if defined (iPlatformAppleMobile) | 86 | #if defined (iPlatformAppleMobile) |
91 | /* Tablet menu. */ | 87 | /* Tablet menu. */ |
92 | static const iMenuItem tabletNavMenuItems_[] = { | 88 | static const iMenuItem tabletNavMenuItems_[] = { |
89 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, | ||
93 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 90 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
94 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 91 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
95 | { "---", 0, 0, NULL }, | 92 | { "---", 0, 0, NULL }, |
@@ -110,6 +107,7 @@ static const iMenuItem tabletNavMenuItems_[] = { | |||
110 | 107 | ||
111 | /* Phone menu. */ | 108 | /* Phone menu. */ |
112 | static const iMenuItem phoneNavMenuItems_[] = { | 109 | static const iMenuItem phoneNavMenuItems_[] = { |
110 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, | ||
113 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 111 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
114 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 112 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
115 | { "---", 0, 0, NULL }, | 113 | { "---", 0, 0, NULL }, |
@@ -273,7 +271,8 @@ void destroyPending_Root(iRoot *d) { | |||
273 | setCurrent_Root(d); | 271 | setCurrent_Root(d); |
274 | iForEach(PtrSet, i, d->pendingDestruction) { | 272 | iForEach(PtrSet, i, d->pendingDestruction) { |
275 | iWidget *widget = *i.value; | 273 | iWidget *widget = *i.value; |
276 | if (!isFinished_Anim(&widget->visualOffset)) { | 274 | if (!isFinished_Anim(&widget->visualOffset) || |
275 | isBeingVisuallyOffsetByReference_Widget(widget)) { | ||
277 | continue; | 276 | continue; |
278 | } | 277 | } |
279 | if (widget->flags & keepOnTop_WidgetFlag) { | 278 | if (widget->flags & keepOnTop_WidgetFlag) { |
@@ -282,7 +281,7 @@ void destroyPending_Root(iRoot *d) { | |||
282 | if (widget->parent) { | 281 | if (widget->parent) { |
283 | removeChild_Widget(widget->parent, widget); | 282 | removeChild_Widget(widget->parent, widget); |
284 | } | 283 | } |
285 | iAssert(widget->parent == NULL); | 284 | iAssert(widget->parent == NULL); |
286 | iRelease(widget); | 285 | iRelease(widget); |
287 | remove_PtrSetIterator(&i); | 286 | remove_PtrSetIterator(&i); |
288 | } | 287 | } |
@@ -433,9 +432,9 @@ static void updateNavBarIdentity_(iWidget *navBar) { | |||
433 | const iGmIdentity *ident = | 432 | const iGmIdentity *ident = |
434 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); | 433 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); |
435 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); | 434 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); |
436 | iWidget *tool = findWidget_App("toolbar.ident"); | 435 | iLabelWidget *toolButton = findWidget_App("toolbar.ident"); |
437 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); | 436 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); |
438 | setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); | 437 | setOutline_LabelWidget(toolButton, ident == NULL); |
439 | /* Update menu. */ | 438 | /* Update menu. */ |
440 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); | 439 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); |
441 | const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; | 440 | const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; |
@@ -444,6 +443,12 @@ static void updateNavBarIdentity_(iWidget *navBar) { | |||
444 | subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) | 443 | subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) |
445 | : "${menu.identity.notactive}"); | 444 | : "${menu.identity.notactive}"); |
446 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); | 445 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); |
446 | iLabelWidget *toolName = findWidget_App("toolbar.name"); | ||
447 | if (toolName) { | ||
448 | updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); | ||
449 | setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId); | ||
450 | arrange_Widget(parent_Widget(toolButton)); | ||
451 | } | ||
447 | } | 452 | } |
448 | 453 | ||
449 | static void updateNavDirButtons_(iWidget *navBar) { | 454 | static void updateNavDirButtons_(iWidget *navBar) { |
@@ -515,6 +520,26 @@ void updatePadding_Root(iRoot *d) { | |||
515 | #endif | 520 | #endif |
516 | } | 521 | } |
517 | 522 | ||
523 | void updateToolbarColors_Root(iRoot *d) { | ||
524 | #if defined (iPlatformMobile) | ||
525 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | ||
526 | if (toolBar) { | ||
527 | const iBool isSidebarVisible = isVisible_Widget(findChild_Widget(d->widget, "sidebar")); | ||
528 | const int bg = isSidebarVisible ? uiBackgroundSidebar_ColorId : | ||
529 | tmBannerBackground_ColorId; | ||
530 | setBackgroundColor_Widget(toolBar, bg); | ||
531 | iForEach(ObjectList, i, children_Widget(toolBar)) { | ||
532 | iLabelWidget *btn = i.object; | ||
533 | setTextColor_LabelWidget(i.object, isSidebarVisible ? uiTextDim_ColorId : | ||
534 | tmBannerIcon_ColorId); | ||
535 | setBackgroundColor_Widget(i.object, bg); /* using noBackground, but ident has outline */ | ||
536 | } | ||
537 | } | ||
538 | #else | ||
539 | iUnused(d); | ||
540 | #endif | ||
541 | } | ||
542 | |||
518 | void dismissPortraitPhoneSidebars_Root(iRoot *d) { | 543 | void dismissPortraitPhoneSidebars_Root(iRoot *d) { |
519 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { | 544 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { |
520 | iWidget *sidebar = findChild_Widget(d->widget, "sidebar"); | 545 | iWidget *sidebar = findChild_Widget(d->widget, "sidebar"); |
@@ -551,9 +576,16 @@ static void updateUrlInputContentPadding_(iWidget *navBar) { | |||
551 | } | 576 | } |
552 | 577 | ||
553 | static void showSearchQueryIndicator_(iBool show) { | 578 | static void showSearchQueryIndicator_(iBool show) { |
579 | iWidget *navBar = findWidget_Root("navbar"); | ||
554 | iWidget *indicator = findWidget_App("input.indicator.search"); | 580 | iWidget *indicator = findWidget_App("input.indicator.search"); |
581 | updateTextCStr_LabelWidget((iLabelWidget *) indicator, | ||
582 | (deviceType_App() == phone_AppDeviceType || | ||
583 | flags_Widget(navBar) & tight_WidgetFlag) | ||
584 | ? "${status.query.tight} " return_Icon | ||
585 | : "${status.query} " return_Icon); | ||
586 | indicator->rect.size.x = defaultSize_LabelWidget((iLabelWidget *) indicator).x; /* don't touch height */ | ||
555 | showCollapsed_Widget(indicator, show); | 587 | showCollapsed_Widget(indicator, show); |
556 | updateUrlInputContentPadding_(findWidget_Root("navbar")); | 588 | updateUrlInputContentPadding_(navBar); |
557 | } | 589 | } |
558 | 590 | ||
559 | static int navBarAvailableSpace_(iWidget *navBar) { | 591 | static int navBarAvailableSpace_(iWidget *navBar) { |
@@ -692,6 +724,7 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
692 | iInputWidget *url = findWidget_Root("url"); | 724 | iInputWidget *url = findWidget_Root("url"); |
693 | const iString *urlStr = collect_String(suffix_Command(cmd, "url")); | 725 | const iString *urlStr = collect_String(suffix_Command(cmd, "url")); |
694 | trimCache_App(); | 726 | trimCache_App(); |
727 | trimMemory_App(); | ||
695 | visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ | 728 | visitUrl_Visited(visited_App(), withSpacesEncoded_String(urlStr), 0); /* TODO: internal URI normalization */ |
696 | postCommand_App("visited.changed"); /* sidebar will update */ | 729 | postCommand_App("visited.changed"); /* sidebar will update */ |
697 | setText_InputWidget(url, urlStr); | 730 | setText_InputWidget(url, urlStr); |
@@ -827,20 +860,20 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | |||
827 | const int viewHeight = size_Root(get_Root()).y; | 860 | const int viewHeight = size_Root(get_Root()).y; |
828 | if (arg_Command(cmd) >= 0) { | 861 | if (arg_Command(cmd) >= 0) { |
829 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); | 862 | postCommandf_App("sidebar.mode arg:%d show:1", arg_Command(cmd)); |
830 | if (!isVisible) { | 863 | // if (!isVisible) { |
831 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | 864 | // setVisualOffset_Widget(sidebar, viewHeight, 0, 0); |
832 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | 865 | // setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); |
833 | } | 866 | // } |
834 | } | 867 | } |
835 | else { | 868 | else { |
836 | postCommandf_App("sidebar.toggle"); | 869 | postCommandf_App("sidebar.toggle"); |
837 | if (isVisible) { | 870 | // if (isVisible) { |
838 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | 871 | // setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); |
839 | } | 872 | // } |
840 | else { | 873 | // else { |
841 | setVisualOffset_Widget(sidebar, viewHeight, 0, 0); | 874 | // setVisualOffset_Widget(sidebar, viewHeight, 0, 0); |
842 | setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); | 875 | // setVisualOffset_Widget(sidebar, 0, 400, easeOut_AnimFlag | softer_AnimFlag); |
843 | } | 876 | // } |
844 | } | 877 | } |
845 | return iTrue; | 878 | return iTrue; |
846 | } | 879 | } |
@@ -848,7 +881,10 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | |||
848 | /* TODO: Clean this up. */ | 881 | /* TODO: Clean this up. */ |
849 | iWidget *sidebar = findWidget_App("sidebar"); | 882 | iWidget *sidebar = findWidget_App("sidebar"); |
850 | iWidget *sidebar2 = findWidget_App("sidebar2"); | 883 | iWidget *sidebar2 = findWidget_App("sidebar2"); |
851 | dismissSidebar_(sidebar, "toolbar.view"); | 884 | //dismissSidebar_(sidebar, "toolbar.view"); |
885 | if (isVisible_Widget(sidebar)) { | ||
886 | postCommandf_App("sidebar.toggle"); | ||
887 | } | ||
852 | const iBool isVisible = isVisible_Widget(sidebar2); | 888 | const iBool isVisible = isVisible_Widget(sidebar2); |
853 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, | 889 | // setFlags_Widget(findChild_Widget(toolBar, "toolbar.ident"), noBackground_WidgetFlag, |
854 | // isVisible); | 890 | // isVisible); |
@@ -921,6 +957,23 @@ void updateMetrics_Root(iRoot *d) { | |||
921 | updatePadding_Root(d); | 957 | updatePadding_Root(d); |
922 | arrange_Widget(d->widget); | 958 | arrange_Widget(d->widget); |
923 | updateUrlInputContentPadding_(navBar); | 959 | updateUrlInputContentPadding_(navBar); |
960 | /* Position the toolbar identity name label manually. */ { | ||
961 | iLabelWidget *idName = findChild_Widget(d->widget, "toolbar.name"); | ||
962 | if (idName) { | ||
963 | const iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | ||
964 | const iWidget *viewButton = findChild_Widget(d->widget, "toolbar.view"); | ||
965 | const iWidget *idButton = findChild_Widget(toolBar, "toolbar.ident"); | ||
966 | const int font = defaultSmall_FontId; | ||
967 | setFont_LabelWidget(idName, font); | ||
968 | setPos_Widget(as_Widget(idName), | ||
969 | windowToLocal_Widget(as_Widget(idName), | ||
970 | init_I2(left_Rect(bounds_Widget(idButton)), | ||
971 | bottom_Rect(bounds_Widget(viewButton)) - | ||
972 | lineHeight_Text(font) - gap_UI / 2))); | ||
973 | setFixedSize_Widget(as_Widget(idName), init_I2(width_Widget(idButton), | ||
974 | lineHeight_Text(font))); | ||
975 | } | ||
976 | } | ||
924 | postRefresh_App(); | 977 | postRefresh_App(); |
925 | } | 978 | } |
926 | 979 | ||
@@ -1048,9 +1101,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1048 | resizeHeightOfChildren_WidgetFlag | | 1101 | resizeHeightOfChildren_WidgetFlag | |
1049 | moveToParentRightEdge_WidgetFlag); | 1102 | moveToParentRightEdge_WidgetFlag); |
1050 | /* Feeds refresh indicator is inside the input field. */ { | 1103 | /* Feeds refresh indicator is inside the input field. */ { |
1051 | iLabelWidget *queryInd = | 1104 | iLabelWidget *queryInd = new_LabelWidget("${status.query} " return_Icon, NULL); |
1052 | new_LabelWidget(uiTextAction_ColorEscape "${status.query} " return_Icon, NULL); | ||
1053 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); | 1105 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); |
1106 | setTextColor_LabelWidget(queryInd, uiTextAction_ColorId); | ||
1054 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); | 1107 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); |
1055 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); | 1108 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); |
1056 | setAlignVisually_LabelWidget(queryInd, iTrue); | 1109 | setAlignVisually_LabelWidget(queryInd, iTrue); |
@@ -1060,9 +1113,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1060 | collapse_WidgetFlag | hidden_WidgetFlag); | 1113 | collapse_WidgetFlag | hidden_WidgetFlag); |
1061 | } | 1114 | } |
1062 | /* Feeds refresh indicator is inside the input field. */ { | 1115 | /* Feeds refresh indicator is inside the input field. */ { |
1063 | iLabelWidget *fprog = new_LabelWidget(uiTextCaution_ColorEscape | 1116 | iLabelWidget *fprog = new_LabelWidget("", NULL); |
1064 | "\u2605 ${status.feeds}", NULL); | ||
1065 | setId_Widget(as_Widget(fprog), "feeds.progress"); | 1117 | setId_Widget(as_Widget(fprog), "feeds.progress"); |
1118 | setTextColor_LabelWidget(fprog, uiTextCaution_ColorId); | ||
1066 | setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); | 1119 | setBackgroundColor_Widget(as_Widget(fprog), uiBackground_ColorId); |
1067 | setAlignVisually_LabelWidget(fprog, iTrue); | 1120 | setAlignVisually_LabelWidget(fprog, iTrue); |
1068 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); | 1121 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); |
@@ -1116,7 +1169,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1116 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); | 1169 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); |
1117 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); | 1170 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); |
1118 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); | 1171 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); |
1119 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags); | 1172 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags | tight_WidgetFlag); |
1120 | updateSize_LabelWidget(pageMenuButton); | 1173 | updateSize_LabelWidget(pageMenuButton); |
1121 | } | 1174 | } |
1122 | /* Reload button. */ { | 1175 | /* Reload button. */ { |
@@ -1146,11 +1199,11 @@ void createUserInterface_Root(iRoot *d) { | |||
1146 | #if !defined (iHaveNativeMenus) | 1199 | #if !defined (iHaveNativeMenus) |
1147 | # if defined (iPlatformAppleMobile) | 1200 | # if defined (iPlatformAppleMobile) |
1148 | iLabelWidget *navMenu = | 1201 | iLabelWidget *navMenu = |
1149 | makeMenuButton_LabelWidget("\U0001d362", isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, | 1202 | makeMenuButton_LabelWidget(menu_Icon, isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, |
1150 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); | 1203 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); |
1151 | # else | 1204 | # else |
1152 | iLabelWidget *navMenu = | 1205 | iLabelWidget *navMenu = |
1153 | makeMenuButton_LabelWidget("\U0001d362", navMenuItems_, iElemCount(navMenuItems_)); | 1206 | makeMenuButton_LabelWidget(menu_Icon, navMenuItems_, iElemCount(navMenuItems_)); |
1154 | # endif | 1207 | # endif |
1155 | setAlignVisually_LabelWidget(navMenu, iTrue); | 1208 | setAlignVisually_LabelWidget(navMenu, iTrue); |
1156 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); | 1209 | setId_Widget(addChildFlags_Widget(navBar, iClob(navMenu), collapse_WidgetFlag), "navbar.menu"); |
@@ -1161,11 +1214,11 @@ void createUserInterface_Root(iRoot *d) { | |||
1161 | setId_Widget(mainStack, "stack"); | 1214 | setId_Widget(mainStack, "stack"); |
1162 | addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag | | 1215 | addChildFlags_Widget(div, iClob(mainStack), resizeChildren_WidgetFlag | expand_WidgetFlag | |
1163 | unhittable_WidgetFlag); | 1216 | unhittable_WidgetFlag); |
1164 | iWidget *tabBar = makeTabs_Widget(mainStack); | 1217 | iWidget *docTabs = makeTabs_Widget(mainStack); |
1165 | setId_Widget(tabBar, "doctabs"); | 1218 | setId_Widget(docTabs, "doctabs"); |
1166 | setBackgroundColor_Widget(tabBar, uiBackground_ColorId); | 1219 | setBackgroundColor_Widget(docTabs, uiBackground_ColorId); |
1167 | appendTabPage_Widget(tabBar, iClob(new_DocumentWidget()), "Document", 0, 0); | 1220 | appendTabPage_Widget(docTabs, iClob(new_DocumentWidget()), "Document", 0, 0); |
1168 | iWidget *buttons = findChild_Widget(tabBar, "tabs.buttons"); | 1221 | iWidget *buttons = findChild_Widget(docTabs, "tabs.buttons"); |
1169 | setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag | | 1222 | setFlags_Widget(buttons, collapse_WidgetFlag | hidden_WidgetFlag | |
1170 | drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue); | 1223 | drawBackgroundToHorizontalSafeArea_WidgetFlag, iTrue); |
1171 | if (deviceType_App() == phone_AppDeviceType) { | 1224 | if (deviceType_App() == phone_AppDeviceType) { |
@@ -1177,9 +1230,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1177 | } | 1230 | } |
1178 | /* Sidebars. */ { | 1231 | /* Sidebars. */ { |
1179 | iWidget *content = findChild_Widget(root, "tabs.content"); | 1232 | iWidget *content = findChild_Widget(root, "tabs.content"); |
1180 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SideBarSide); | 1233 | iSidebarWidget *sidebar1 = new_SidebarWidget(left_SidebarSide); |
1181 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); | 1234 | addChildPos_Widget(content, iClob(sidebar1), front_WidgetAddPos); |
1182 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SideBarSide); | 1235 | iSidebarWidget *sidebar2 = new_SidebarWidget(right_SidebarSide); |
1183 | if (deviceType_App() != phone_AppDeviceType) { | 1236 | if (deviceType_App() != phone_AppDeviceType) { |
1184 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); | 1237 | addChildPos_Widget(content, iClob(sidebar2), back_WidgetAddPos); |
1185 | } | 1238 | } |
@@ -1218,6 +1271,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1218 | setHint_InputWidget(input, "${hint.findtext}"); | 1271 | setHint_InputWidget(input, "${hint.findtext}"); |
1219 | setSelectAllOnFocus_InputWidget(input, iTrue); | 1272 | setSelectAllOnFocus_InputWidget(input, iTrue); |
1220 | setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ | 1273 | setEatEscape_InputWidget(input, iFalse); /* unfocus and close with one keypress */ |
1274 | setEnterInsertsLF_InputWidget(input, iFalse); | ||
1221 | setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), | 1275 | setId_Widget(addChildFlags_Widget(searchBar, iClob(input), expand_WidgetFlag), |
1222 | "find.input"); | 1276 | "find.input"); |
1223 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); | 1277 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9f ", 'g', KMOD_PRIMARY, "find.next"))); |
@@ -1237,7 +1291,6 @@ void createUserInterface_Root(iRoot *d) { | |||
1237 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag | | 1291 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag | |
1238 | commandOnClick_WidgetFlag | | 1292 | commandOnClick_WidgetFlag | |
1239 | drawBackgroundToBottom_WidgetFlag, iTrue); | 1293 | drawBackgroundToBottom_WidgetFlag, iTrue); |
1240 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); | ||
1241 | setId_Widget(addChildFlags_Widget(toolBar, | 1294 | setId_Widget(addChildFlags_Widget(toolBar, |
1242 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), | 1295 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), |
1243 | frameless_WidgetFlag), | 1296 | frameless_WidgetFlag), |
@@ -1254,7 +1307,16 @@ void createUserInterface_Root(iRoot *d) { | |||
1254 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), | 1307 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), |
1255 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | 1308 | frameless_WidgetFlag | commandOnClick_WidgetFlag), |
1256 | "toolbar.view"); | 1309 | "toolbar.view"); |
1257 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, | 1310 | setId_Widget(addChildFlags_Widget(toolBar, |
1311 | iClob(new_LabelWidget("", "toolbar.showident")), | ||
1312 | frameless_WidgetFlag | | ||
1313 | noBackground_WidgetFlag | | ||
1314 | fixedPosition_WidgetFlag | | ||
1315 | fixedSize_WidgetFlag | | ||
1316 | ignoreForParentWidth_WidgetFlag | | ||
1317 | ignoreForParentHeight_WidgetFlag), | ||
1318 | "toolbar.name"); | ||
1319 | iLabelWidget *menuButton = makeMenuButton_LabelWidget(menu_Icon, phoneNavMenuItems_, | ||
1258 | iElemCount(phoneNavMenuItems_)); | 1320 | iElemCount(phoneNavMenuItems_)); |
1259 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); | 1321 | setFont_LabelWidget(menuButton, uiLabelLarge_FontId); |
1260 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); | 1322 | setId_Widget(as_Widget(menuButton), "toolbar.navmenu"); |
@@ -1262,9 +1324,8 @@ void createUserInterface_Root(iRoot *d) { | |||
1262 | iForEach(ObjectList, i, children_Widget(toolBar)) { | 1324 | iForEach(ObjectList, i, children_Widget(toolBar)) { |
1263 | iLabelWidget *btn = i.object; | 1325 | iLabelWidget *btn = i.object; |
1264 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | 1326 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); |
1265 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); | ||
1266 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); | ||
1267 | } | 1327 | } |
1328 | updateToolbarColors_Root(d); | ||
1268 | const iMenuItem items[] = { | 1329 | const iMenuItem items[] = { |
1269 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | 1330 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, |
1270 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | 1331 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, |
@@ -1346,7 +1407,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1346 | } | 1407 | } |
1347 | } | 1408 | } |
1348 | 1409 | ||
1349 | void showToolbars_Root(iRoot *d, iBool show) { | 1410 | void showToolbar_Root(iRoot *d, iBool show) { |
1350 | /* The toolbar is only used on phone portrait layout. */ | 1411 | /* The toolbar is only used on phone portrait layout. */ |
1351 | if (isLandscape_App()) return; | 1412 | if (isLandscape_App()) return; |
1352 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | 1413 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); |
diff --git a/src/ui/root.h b/src/ui/root.h index 96864a15..740e97c9 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -34,7 +34,8 @@ void postArrange_Root (iRoot *); | |||
34 | void updateMetrics_Root (iRoot *); | 34 | void updateMetrics_Root (iRoot *); |
35 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ | 35 | void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ |
36 | void dismissPortraitPhoneSidebars_Root (iRoot *); | 36 | void dismissPortraitPhoneSidebars_Root (iRoot *); |
37 | void showToolbars_Root (iRoot *, iBool show); | 37 | void showToolbar_Root (iRoot *, iBool show); |
38 | void updateToolbarColors_Root (iRoot *); | ||
38 | 39 | ||
39 | iInt2 size_Root (const iRoot *); | 40 | iInt2 size_Root (const iRoot *); |
40 | iRect rect_Root (const iRoot *); | 41 | iRect rect_Root (const iRoot *); |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 27646b22..c0a22e99 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -523,15 +523,17 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
523 | addChild_Widget(div, iClob(makePadding_Widget(gap_UI))); | 523 | addChild_Widget(div, iClob(makePadding_Widget(gap_UI))); |
524 | addChild_Widget(div, iClob(new_LabelWidget("${menu.identity.import}", "ident.import"))); | 524 | addChild_Widget(div, iClob(new_LabelWidget("${menu.identity.import}", "ident.import"))); |
525 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ | 525 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); /* pad */ |
526 | iLabelWidget *linkLabel; | ||
526 | setBackgroundColor_Widget( | 527 | setBackgroundColor_Widget( |
527 | addChildFlags_Widget( | 528 | addChildFlags_Widget( |
528 | div, | 529 | div, |
529 | iClob(new_LabelWidget(format_CStr(cstr_Lang("ident.gotohelp"), | 530 | iClob(linkLabel = new_LabelWidget(format_CStr(cstr_Lang("ident.gotohelp"), |
530 | uiTextStrong_ColorEscape, | 531 | uiTextStrong_ColorEscape, |
531 | restore_ColorEscape), | 532 | restore_ColorEscape), |
532 | "!open newtab:1 gotoheading:1.6 url:about:help")), | 533 | "!open newtab:1 gotoheading:1.6 url:about:help")), |
533 | frameless_WidgetFlag | fixedHeight_WidgetFlag | wrapText_WidgetFlag), | 534 | frameless_WidgetFlag | fixedHeight_WidgetFlag), |
534 | uiBackgroundSidebar_ColorId); | 535 | uiBackgroundSidebar_ColorId); |
536 | setWrap_LabelWidget(linkLabel, iTrue); | ||
535 | addChild_Widget(d->blank, iClob(div)); | 537 | addChild_Widget(d->blank, iClob(div)); |
536 | } | 538 | } |
537 | // arrange_Widget(d->blank); | 539 | // arrange_Widget(d->blank); |
@@ -625,13 +627,14 @@ static void updateMetrics_SidebarWidget_(iSidebarWidget *d) { | |||
625 | void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | 627 | void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { |
626 | iWidget *w = as_Widget(d); | 628 | iWidget *w = as_Widget(d); |
627 | init_Widget(w); | 629 | init_Widget(w); |
628 | setId_Widget(w, side == left_SideBarSide ? "sidebar" : "sidebar2"); | 630 | setId_Widget(w, side == left_SidebarSide ? "sidebar" : "sidebar2"); |
629 | initCopy_String(&d->cmdPrefix, id_Widget(w)); | 631 | initCopy_String(&d->cmdPrefix, id_Widget(w)); |
630 | appendChar_String(&d->cmdPrefix, '.'); | 632 | appendChar_String(&d->cmdPrefix, '.'); |
631 | setBackgroundColor_Widget(w, none_ColorId); | 633 | setBackgroundColor_Widget(w, none_ColorId); |
632 | setFlags_Widget(w, | 634 | setFlags_Widget(w, |
633 | collapse_WidgetFlag | hidden_WidgetFlag | arrangeHorizontal_WidgetFlag | | 635 | collapse_WidgetFlag | hidden_WidgetFlag | arrangeHorizontal_WidgetFlag | |
634 | resizeWidthOfChildren_WidgetFlag, | 636 | resizeWidthOfChildren_WidgetFlag | noFadeBackground_WidgetFlag | |
637 | noShadowBorder_WidgetFlag, | ||
635 | iTrue); | 638 | iTrue); |
636 | iZap(d->modeScroll); | 639 | iZap(d->modeScroll); |
637 | d->side = side; | 640 | d->side = side; |
@@ -659,7 +662,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
659 | d->actions = NULL; | 662 | d->actions = NULL; |
660 | /* On a phone, the right sidebar is used exclusively for Identities. */ | 663 | /* On a phone, the right sidebar is used exclusively for Identities. */ |
661 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | 664 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; |
662 | if (!isPhone || d->side == left_SideBarSide) { | 665 | if (!isPhone || d->side == left_SidebarSide) { |
663 | iWidget *buttons = new_Widget(); | 666 | iWidget *buttons = new_Widget(); |
664 | setId_Widget(buttons, "buttons"); | 667 | setId_Widget(buttons, "buttons"); |
665 | for (int i = 0; i < max_SidebarMode; i++) { | 668 | for (int i = 0; i < max_SidebarMode; i++) { |
@@ -678,8 +681,8 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
678 | iClob(buttons), | 681 | iClob(buttons), |
679 | arrangeHorizontal_WidgetFlag | | 682 | arrangeHorizontal_WidgetFlag | |
680 | resizeWidthOfChildren_WidgetFlag | | 683 | resizeWidthOfChildren_WidgetFlag | |
681 | arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag | | 684 | arrangeHeight_WidgetFlag | resizeToParentWidth_WidgetFlag); // | |
682 | drawBackgroundToHorizontalSafeArea_WidgetFlag); | 685 | // drawBackgroundToHorizontalSafeArea_WidgetFlag); |
683 | setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); | 686 | setBackgroundColor_Widget(buttons, uiBackgroundSidebar_ColorId); |
684 | } | 687 | } |
685 | else { | 688 | else { |
@@ -700,13 +703,13 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
700 | setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); | 703 | setPadding_Widget(as_Widget(d->list), 0, gap_UI, 0, gap_UI); |
701 | addChildFlags_Widget(listAndActions, | 704 | addChildFlags_Widget(listAndActions, |
702 | iClob(d->list), | 705 | iClob(d->list), |
703 | expand_WidgetFlag | drawBackgroundToHorizontalSafeArea_WidgetFlag); | 706 | expand_WidgetFlag); // | drawBackgroundToHorizontalSafeArea_WidgetFlag); |
704 | setId_Widget(addChildPosFlags_Widget(listAndActions, | 707 | setId_Widget(addChildPosFlags_Widget(listAndActions, |
705 | iClob(d->actions = new_Widget()), | 708 | iClob(d->actions = new_Widget()), |
706 | isPhone ? front_WidgetAddPos : back_WidgetAddPos, | 709 | isPhone ? front_WidgetAddPos : back_WidgetAddPos, |
707 | arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | | 710 | arrangeHorizontal_WidgetFlag | arrangeHeight_WidgetFlag | |
708 | resizeWidthOfChildren_WidgetFlag | | 711 | resizeWidthOfChildren_WidgetFlag), // | |
709 | drawBackgroundToHorizontalSafeArea_WidgetFlag), | 712 | // drawBackgroundToHorizontalSafeArea_WidgetFlag), |
710 | "actions"); | 713 | "actions"); |
711 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); | 714 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); |
712 | d->contextItem = NULL; | 715 | d->contextItem = NULL; |
@@ -715,24 +718,24 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
715 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); | 718 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); |
716 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); | 719 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); |
717 | setMode_SidebarWidget(d, | 720 | setMode_SidebarWidget(d, |
718 | deviceType_App() == phone_AppDeviceType && d->side == right_SideBarSide ? | 721 | deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide ? |
719 | identities_SidebarMode : bookmarks_SidebarMode); | 722 | identities_SidebarMode : bookmarks_SidebarMode); |
720 | d->resizer = | 723 | d->resizer = |
721 | addChildFlags_Widget(w, | 724 | addChildFlags_Widget(w, |
722 | iClob(new_Widget()), | 725 | iClob(new_Widget()), |
723 | hover_WidgetFlag | commandOnClick_WidgetFlag | fixedWidth_WidgetFlag | | 726 | hover_WidgetFlag | commandOnClick_WidgetFlag | fixedWidth_WidgetFlag | |
724 | resizeToParentHeight_WidgetFlag | | 727 | resizeToParentHeight_WidgetFlag | |
725 | (side == left_SideBarSide ? moveToParentRightEdge_WidgetFlag | 728 | (side == left_SidebarSide ? moveToParentRightEdge_WidgetFlag |
726 | : moveToParentLeftEdge_WidgetFlag)); | 729 | : moveToParentLeftEdge_WidgetFlag)); |
727 | if (deviceType_App() == phone_AppDeviceType) { | 730 | if (deviceType_App() == phone_AppDeviceType) { |
728 | setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | 731 | setFlags_Widget(d->resizer, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); |
729 | } | 732 | } |
730 | setId_Widget(d->resizer, side == left_SideBarSide ? "sidebar.grab" : "sidebar2.grab"); | 733 | setId_Widget(d->resizer, side == left_SidebarSide ? "sidebar.grab" : "sidebar2.grab"); |
731 | setBackgroundColor_Widget(d->resizer, none_ColorId); | 734 | setBackgroundColor_Widget(d->resizer, none_ColorId); |
732 | d->menu = NULL; | 735 | d->menu = NULL; |
733 | addAction_Widget(w, SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh"); | 736 | addAction_Widget(w, SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh"); |
734 | updateMetrics_SidebarWidget_(d); | 737 | updateMetrics_SidebarWidget_(d); |
735 | if (side == left_SideBarSide) { | 738 | if (side == left_SidebarSide) { |
736 | postCommand_App("~sidebar.update"); /* unread count */ | 739 | postCommand_App("~sidebar.update"); /* unread count */ |
737 | } | 740 | } |
738 | } | 741 | } |
@@ -773,6 +776,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
773 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); | 776 | const iGmHeading *head = constAt_Array(headings_GmDocument(doc), item->id); |
774 | postCommandf_App("document.goto loc:%p", head->text.start); | 777 | postCommandf_App("document.goto loc:%p", head->text.start); |
775 | dismissPortraitPhoneSidebars_Root(as_Widget(d)->root); | 778 | dismissPortraitPhoneSidebars_Root(as_Widget(d)->root); |
779 | setOpenedFromSidebar_DocumentWidget(document_App(), iTrue); | ||
776 | break; | 780 | break; |
777 | } | 781 | } |
778 | case feeds_SidebarMode: { | 782 | case feeds_SidebarMode: { |
@@ -783,7 +787,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
783 | case bookmarks_SidebarMode: | 787 | case bookmarks_SidebarMode: |
784 | case history_SidebarMode: { | 788 | case history_SidebarMode: { |
785 | if (!isEmpty_String(&item->url)) { | 789 | if (!isEmpty_String(&item->url)) { |
786 | postCommandf_Root(get_Root(), "open newtab:%d url:%s", | 790 | postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", |
787 | openTabMode_Sym(modState_Keys()), | 791 | openTabMode_Sym(modState_Keys()), |
788 | cstr_String(&item->url)); | 792 | cstr_String(&item->url)); |
789 | } | 793 | } |
@@ -799,7 +803,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
799 | updateContextMenu_SidebarWidget_(d); | 803 | updateContextMenu_SidebarWidget_(d); |
800 | arrange_Widget(d->menu); | 804 | arrange_Widget(d->menu); |
801 | openMenu_Widget(d->menu, | 805 | openMenu_Widget(d->menu, |
802 | d->side == left_SideBarSide | 806 | d->side == left_SidebarSide |
803 | ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex)) | 807 | ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex)) |
804 | : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)), | 808 | : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)), |
805 | -width_Widget(d->menu))); | 809 | -width_Widget(d->menu))); |
@@ -857,7 +861,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | |||
857 | if (!isFixedWidth) { | 861 | if (!isFixedWidth) { |
858 | /* Even less space if the other sidebar is visible, too. */ | 862 | /* Even less space if the other sidebar is visible, too. */ |
859 | const int otherWidth = | 863 | const int otherWidth = |
860 | width_Widget(findWidget_App(d->side == left_SideBarSide ? "sidebar2" : "sidebar")); | 864 | width_Widget(findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar")); |
861 | width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); | 865 | width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); |
862 | } | 866 | } |
863 | d->widthAsGaps = (float) width / (float) gap_UI; | 867 | d->widthAsGaps = (float) width / (float) gap_UI; |
@@ -938,33 +942,39 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
938 | } | 942 | } |
939 | const iBool isAnimated = prefs_App()->uiAnimations && | 943 | const iBool isAnimated = prefs_App()->uiAnimations && |
940 | argLabel_Command(cmd, "noanim") == 0 && | 944 | argLabel_Command(cmd, "noanim") == 0 && |
941 | (deviceType_App() != phone_AppDeviceType); | 945 | (d->side == left_SidebarSide || deviceType_App() != phone_AppDeviceType); |
942 | int visX = 0; | 946 | int visX = 0; |
943 | if (isVisible_Widget(w)) { | 947 | if (isVisible_Widget(w)) { |
944 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); | 948 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); |
945 | } | 949 | } |
946 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); | 950 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); |
951 | /* Safe area inset for mobile. */ | ||
952 | const int safePad = (d->side == left_SidebarSide ? left_Rect(safeRect_Root(w->root)) : 0); | ||
947 | if (isVisible_Widget(w)) { | 953 | if (isVisible_Widget(w)) { |
948 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); | 954 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); |
949 | w->rect.size.x = d->widthAsGaps * gap_UI; | 955 | w->rect.size.x = d->widthAsGaps * gap_UI; |
950 | invalidate_ListWidget(d->list); | 956 | invalidate_ListWidget(d->list); |
951 | if (isAnimated) { | 957 | if (isAnimated) { |
952 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | 958 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); |
953 | setVisualOffset_Widget(w, (d->side == left_SideBarSide ? -1 : 1) * w->rect.size.x, 0, 0); | 959 | setVisualOffset_Widget( |
960 | w, (d->side == left_SidebarSide ? -1 : 1) * (w->rect.size.x + safePad), 0, 0); | ||
954 | setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); | 961 | setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); |
955 | } | 962 | } |
956 | } | 963 | } |
957 | else if (isAnimated) { | 964 | else if (isAnimated) { |
958 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | 965 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); |
959 | if (d->side == right_SideBarSide) { | 966 | if (d->side == right_SidebarSide) { |
960 | setVisualOffset_Widget(w, visX, 0, 0); | 967 | setVisualOffset_Widget(w, visX, 0, 0); |
961 | setVisualOffset_Widget(w, visX + w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | 968 | setVisualOffset_Widget( |
969 | w, visX + w->rect.size.x + safePad, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
962 | } | 970 | } |
963 | else { | 971 | else { |
964 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | 972 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); |
965 | setVisualOffset_Widget(w, -w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | 973 | setVisualOffset_Widget( |
974 | w, -w->rect.size.x - safePad, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
966 | } | 975 | } |
967 | } | 976 | } |
977 | updateToolbarColors_Root(w->root); | ||
968 | arrange_Widget(w->parent); | 978 | arrange_Widget(w->parent); |
969 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ | 979 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ |
970 | arrange_Widget(w); | 980 | arrange_Widget(w); |
@@ -984,11 +994,21 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
984 | /* Handle commands. */ | 994 | /* Handle commands. */ |
985 | if (isResize_UserEvent(ev)) { | 995 | if (isResize_UserEvent(ev)) { |
986 | checkModeButtonLayout_SidebarWidget_(d); | 996 | checkModeButtonLayout_SidebarWidget_(d); |
987 | if (deviceType_App() == phone_AppDeviceType && d->side == left_SideBarSide) { | 997 | if (deviceType_App() == phone_AppDeviceType && d->side == left_SidebarSide) { |
998 | setFlags_Widget(w, rightEdgeDraggable_WidgetFlag, isPortrait_App()); | ||
988 | /* In landscape, visibility of the toolbar is controlled separately. */ | 999 | /* In landscape, visibility of the toolbar is controlled separately. */ |
989 | if (isVisible_Widget(w)) { | 1000 | if (isVisible_Widget(w)) { |
990 | postCommand_Widget(w, "sidebar.toggle"); | 1001 | postCommand_Widget(w, "sidebar.toggle"); |
991 | } | 1002 | } |
1003 | setFlags_Widget(findChild_Widget(w, "buttons"), | ||
1004 | drawBackgroundToHorizontalSafeArea_WidgetFlag, | ||
1005 | isLandscape_App()); | ||
1006 | setFlags_Widget(findChild_Widget(w, "actions"), | ||
1007 | drawBackgroundToHorizontalSafeArea_WidgetFlag, | ||
1008 | isLandscape_App()); | ||
1009 | setFlags_Widget(as_Widget(d->list), | ||
1010 | drawBackgroundToHorizontalSafeArea_WidgetFlag, | ||
1011 | isLandscape_App()); | ||
992 | return iFalse; | 1012 | return iFalse; |
993 | } | 1013 | } |
994 | } | 1014 | } |
@@ -1029,6 +1049,11 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1029 | postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode); | 1049 | postCommandf_App("sidebar.mode arg:%d toggle:1", identities_SidebarMode); |
1030 | return iTrue; | 1050 | return iTrue; |
1031 | } | 1051 | } |
1052 | else if (isPortraitPhone_App() && isVisible_Widget(w) && d->side == left_SidebarSide && | ||
1053 | equal_Command(cmd, "swipe.forward")) { | ||
1054 | postCommand_App("sidebar.toggle"); | ||
1055 | return iTrue; | ||
1056 | } | ||
1032 | else if (startsWith_CStr(cmd, cstr_String(&d->cmdPrefix))) { | 1057 | else if (startsWith_CStr(cmd, cstr_String(&d->cmdPrefix))) { |
1033 | if (handleSidebarCommand_SidebarWidget_(d, cmd + size_String(&d->cmdPrefix))) { | 1058 | if (handleSidebarCommand_SidebarWidget_(d, cmd + size_String(&d->cmdPrefix))) { |
1034 | return iTrue; | 1059 | return iTrue; |
@@ -1059,7 +1084,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1059 | const int resMid = d->resizer->rect.size.x / 2; | 1084 | const int resMid = d->resizer->rect.size.x / 2; |
1060 | setWidth_SidebarWidget( | 1085 | setWidth_SidebarWidget( |
1061 | d, | 1086 | d, |
1062 | ((d->side == left_SideBarSide | 1087 | ((d->side == left_SidebarSide |
1063 | ? inner.x | 1088 | ? inner.x |
1064 | : (right_Rect(rect_Root(w->root)) - coord_Command(cmd).x)) + | 1089 | : (right_Rect(rect_Root(w->root)) - coord_Command(cmd).x)) + |
1065 | resMid) / (float) gap_UI); | 1090 | resMid) / (float) gap_UI); |
@@ -1528,15 +1553,17 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1528 | const iRect bounds = bounds_Widget(w); | 1553 | const iRect bounds = bounds_Widget(w); |
1529 | iPaint p; | 1554 | iPaint p; |
1530 | init_Paint(&p); | 1555 | init_Paint(&p); |
1531 | if (flags_Widget(w) & visualOffset_WidgetFlag && | 1556 | if (deviceType_App() != phone_AppDeviceType) { |
1532 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { | 1557 | if (flags_Widget(w) & visualOffset_WidgetFlag && |
1533 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | 1558 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { |
1559 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | ||
1560 | } | ||
1534 | } | 1561 | } |
1535 | draw_Widget(w); | 1562 | draw_Widget(w); |
1536 | if (isVisible_Widget(w)) { | 1563 | if (isVisible_Widget(w)) { |
1537 | drawVLine_Paint( | 1564 | drawVLine_Paint( |
1538 | &p, | 1565 | &p, |
1539 | addX_I2(d->side == left_SideBarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1), | 1566 | addX_I2(d->side == left_SidebarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1), |
1540 | height_Rect(bounds), | 1567 | height_Rect(bounds), |
1541 | uiSeparator_ColorId); | 1568 | uiSeparator_ColorId); |
1542 | } | 1569 | } |
@@ -1782,13 +1809,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1782 | : uiTextFramelessHover_ColorId) | 1809 | : uiTextFramelessHover_ColorId) |
1783 | : uiTextDim_ColorId; | 1810 | : uiTextDim_ColorId; |
1784 | if (!d->listItem.isSelected && !isUsedOnDomain) { | 1811 | if (!d->listItem.isSelected && !isUsedOnDomain) { |
1785 | /* Draw an outline of the icon. */ | 1812 | drawOutline_Text(font, cPos, metaFg, none_ColorId, range_String(&icon)); |
1786 | for (int off = 0; off < 4; ++off) { | ||
1787 | drawRange_Text(font, | ||
1788 | add_I2(cPos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)), | ||
1789 | metaFg, | ||
1790 | range_String(&icon)); | ||
1791 | } | ||
1792 | } | 1813 | } |
1793 | drawRange_Text(font, | 1814 | drawRange_Text(font, |
1794 | cPos, | 1815 | cPos, |
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h index 2e418aa4..130242ab 100644 --- a/src/ui/sidebarwidget.h +++ b/src/ui/sidebarwidget.h | |||
@@ -36,8 +36,8 @@ enum iSidebarMode { | |||
36 | const char * icon_SidebarMode (enum iSidebarMode mode); | 36 | const char * icon_SidebarMode (enum iSidebarMode mode); |
37 | 37 | ||
38 | enum iSidebarSide { | 38 | enum iSidebarSide { |
39 | left_SideBarSide, | 39 | left_SidebarSide, |
40 | right_SideBarSide, | 40 | right_SidebarSide, |
41 | }; | 41 | }; |
42 | 42 | ||
43 | enum iFeedsMode { | 43 | enum iFeedsMode { |
diff --git a/src/ui/text.c b/src/ui/text.c index 51865654..edbc6583 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -316,6 +316,7 @@ static void initFonts_Text_(iText *d) { | |||
316 | { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, | 316 | { &fontSourceSans3Regular_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, |
317 | { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize }, | 317 | { &fontSourceSans3Regular_Embedded, uiSize * 1.333f, 1.0f, uiBig_FontSize }, |
318 | { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, | 318 | { &fontSourceSans3Regular_Embedded, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, |
319 | { &fontSourceSans3Semibold_Embedded, uiSize * 0.8f, 1.0f, uiNormal_FontSize }, | ||
319 | /* UI fonts: bold weight */ | 320 | /* UI fonts: bold weight */ |
320 | { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize }, | 321 | { &fontSourceSans3Bold_Embedded, uiSize, 1.0f, uiNormal_FontSize }, |
321 | { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, | 322 | { &fontSourceSans3Bold_Embedded, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, |
@@ -399,7 +400,9 @@ static void initCache_Text_(iText *d) { | |||
399 | d->cacheRowAllocStep = iMax(2, textSize / 6); | 400 | d->cacheRowAllocStep = iMax(2, textSize / 6); |
400 | /* Allocate initial (empty) rows. These will be assigned actual locations in the cache | 401 | /* Allocate initial (empty) rows. These will be assigned actual locations in the cache |
401 | once at least one glyph is stored. */ | 402 | once at least one glyph is stored. */ |
402 | for (int h = d->cacheRowAllocStep; h <= 2 * textSize + d->cacheRowAllocStep; h += d->cacheRowAllocStep) { | 403 | for (int h = d->cacheRowAllocStep; |
404 | h <= 2.5 * textSize + d->cacheRowAllocStep; | ||
405 | h += d->cacheRowAllocStep) { | ||
403 | pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 }); | 406 | pushBack_Array(&d->cacheRows, &(iCacheRow){ .height = 0 }); |
404 | } | 407 | } |
405 | d->cacheBottom = 0; | 408 | d->cacheBottom = 0; |
@@ -1020,10 +1023,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1020 | prevCh = 0; | 1023 | prevCh = 0; |
1021 | continue; | 1024 | continue; |
1022 | } | 1025 | } |
1023 | if (ch == '\r') { /* color change */ | 1026 | if (ch == '\v') { /* color change */ |
1024 | iChar esc = nextChar_(&chPos, args->text.end); | 1027 | iChar esc = nextChar_(&chPos, args->text.end); |
1025 | int colorNum = args->color; | 1028 | int colorNum = args->color; |
1026 | if (esc == '\r') { /* Extended range. */ | 1029 | if (esc == '\v') { /* Extended range. */ |
1027 | esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; | 1030 | esc = nextChar_(&chPos, args->text.end) + asciiExtended_ColorEscape; |
1028 | colorNum = esc - asciiBase_ColorEscape; | 1031 | colorNum = esc - asciiBase_ColorEscape; |
1029 | } | 1032 | } |
@@ -1335,6 +1338,18 @@ void drawRangeN_Text(int fontId, iInt2 pos, int color, iRangecc text, size_t max | |||
1335 | drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars); | 1338 | drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars); |
1336 | } | 1339 | } |
1337 | 1340 | ||
1341 | void drawOutline_Text(int fontId, iInt2 pos, int outlineColor, int fillColor, iRangecc text) { | ||
1342 | for (int off = 0; off < 4; ++off) { | ||
1343 | drawRange_Text(fontId, | ||
1344 | add_I2(pos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)), | ||
1345 | outlineColor, | ||
1346 | text); | ||
1347 | } | ||
1348 | if (fillColor != none_ColorId) { | ||
1349 | drawRange_Text(fontId, pos, fillColor, text); | ||
1350 | } | ||
1351 | } | ||
1352 | |||
1338 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | 1353 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { |
1339 | iInt2 size = zero_I2(); | 1354 | iInt2 size = zero_I2(); |
1340 | const char *endp; | 1355 | const char *endp; |
@@ -1376,6 +1391,31 @@ void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, con | |||
1376 | deinit_Block(&chars); | 1391 | deinit_Block(&chars); |
1377 | } | 1392 | } |
1378 | 1393 | ||
1394 | void drawCenteredOutline_Text(int fontId, iRect rect, iBool alignVisual, int outlineColor, | ||
1395 | int fillColor, const char *format, ...) { | ||
1396 | iBlock chars; | ||
1397 | init_Block(&chars, 0); { | ||
1398 | va_list args; | ||
1399 | va_start(args, format); | ||
1400 | vprintf_Block(&chars, format, args); | ||
1401 | va_end(args); | ||
1402 | } | ||
1403 | if (outlineColor != none_ColorId) { | ||
1404 | for (int off = 0; off < 4; ++off) { | ||
1405 | drawCenteredRange_Text( | ||
1406 | fontId, | ||
1407 | moved_Rect(rect, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)), | ||
1408 | alignVisual, | ||
1409 | outlineColor, | ||
1410 | range_Block(&chars)); | ||
1411 | } | ||
1412 | } | ||
1413 | if (fillColor != none_ColorId) { | ||
1414 | drawCenteredRange_Text(fontId, rect, alignVisual, fillColor, range_Block(&chars)); | ||
1415 | } | ||
1416 | deinit_Block(&chars); | ||
1417 | } | ||
1418 | |||
1379 | void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { | 1419 | void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { |
1380 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) | 1420 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) |
1381 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; | 1421 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; |
diff --git a/src/ui/text.h b/src/ui/text.h index 768713ee..5a099142 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -50,6 +50,7 @@ enum iFontId { | |||
50 | defaultMedium_FontId, | 50 | defaultMedium_FontId, |
51 | defaultBig_FontId, | 51 | defaultBig_FontId, |
52 | defaultLarge_FontId, | 52 | defaultLarge_FontId, |
53 | defaultSmall_FontId, | ||
53 | /* UI fonts: bold weight */ | 54 | /* UI fonts: bold weight */ |
54 | defaultBold_FontId, | 55 | defaultBold_FontId, |
55 | defaultMediumBold_FontId, | 56 | defaultMediumBold_FontId, |
@@ -163,9 +164,11 @@ void draw_Text (int fontId, iInt2 pos, int color, const char *t | |||
163 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); | 164 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); |
164 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); | 165 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); |
165 | void drawCenteredRange_Text (int fontId, iRect rect, iBool alignVisual, int color, iRangecc text); | 166 | void drawCenteredRange_Text (int fontId, iRect rect, iBool alignVisual, int color, iRangecc text); |
167 | void drawCenteredOutline_Text(int fontId, iRect rect, iBool alignVisual, int outlineColor, int fillColor, const char *text, ...); | ||
166 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); | 168 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); |
167 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); | 169 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); |
168 | void drawRangeN_Text (int fontId, iInt2 pos, int color, iRangecc text, size_t maxLen); | 170 | void drawRangeN_Text (int fontId, iInt2 pos, int color, iRangecc text, size_t maxLen); |
171 | void drawOutline_Text (int fontId, iInt2 pos, int outlineColor, int fillColor, iRangecc text); | ||
169 | void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */ | 172 | void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */ |
170 | int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ | 173 | int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ |
171 | 174 | ||
diff --git a/src/ui/touch.c b/src/ui/touch.c index 74a22baf..dac1152e 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -40,6 +40,10 @@ iDeclareType(TouchState) | |||
40 | #define numHistory_Touch_ 5 | 40 | #define numHistory_Touch_ 5 |
41 | #define lastIndex_Touch_ (numHistory_Touch_ - 1) | 41 | #define lastIndex_Touch_ (numHistory_Touch_ - 1) |
42 | 42 | ||
43 | static const uint32_t longPressSpanMs_ = 500; | ||
44 | static const uint32_t shortPressSpanMs_ = 250; | ||
45 | static const int tapRadiusPt_ = 10; | ||
46 | |||
43 | enum iTouchEdge { | 47 | enum iTouchEdge { |
44 | none_TouchEdge, | 48 | none_TouchEdge, |
45 | left_TouchEdge, | 49 | left_TouchEdge, |
@@ -55,12 +59,13 @@ enum iTouchAxis { | |||
55 | struct Impl_Touch { | 59 | struct Impl_Touch { |
56 | SDL_FingerID id; | 60 | SDL_FingerID id; |
57 | iWidget *affinity; /* widget on which the touch started */ | 61 | iWidget *affinity; /* widget on which the touch started */ |
58 | iWidget *edgeDragging; | 62 | // iWidget *edgeDragging; |
59 | iBool hasMoved; | 63 | iBool hasMoved; |
60 | iBool isTapBegun; | 64 | iBool isTapBegun; |
61 | iBool isLeftDown; | 65 | iBool isLeftDown; |
62 | iBool isTouchDrag; | 66 | iBool isTouchDrag; |
63 | iBool isTapAndHold; | 67 | iBool isTapAndHold; |
68 | iBool didPostEdgeMove; | ||
64 | iBool didBeginOnTouchDrag; | 69 | iBool didBeginOnTouchDrag; |
65 | int pinchId; | 70 | int pinchId; |
66 | enum iTouchEdge edge; | 71 | enum iTouchEdge edge; |
@@ -132,9 +137,6 @@ static iTouch *find_TouchState_(iTouchState *d, SDL_FingerID id) { | |||
132 | return NULL; | 137 | return NULL; |
133 | } | 138 | } |
134 | 139 | ||
135 | static const uint32_t longPressSpanMs_ = 500; | ||
136 | static const int tapRadiusPt_ = 10; | ||
137 | |||
138 | iLocalDef float distance_Touch_(const iTouch *d) { | 140 | iLocalDef float distance_Touch_(const iTouch *d) { |
139 | return length_F3(sub_F3(d->pos[0], d->startPos)); | 141 | return length_F3(sub_F3(d->pos[0], d->startPos)); |
140 | } | 142 | } |
@@ -246,6 +248,11 @@ iLocalDef double accurateTicks_(void) { | |||
246 | return 1000.0 * (double) count / (double) freq; | 248 | return 1000.0 * (double) count / (double) freq; |
247 | } | 249 | } |
248 | 250 | ||
251 | static iFloat3 gestureVector_Touch_(const iTouch *d) { | ||
252 | const size_t lastIndex = iMin(d->posCount - 1, lastIndex_Touch_); | ||
253 | return sub_F3(d->pos[0], d->pos[lastIndex]); | ||
254 | } | ||
255 | |||
249 | static void update_TouchState_(void *ptr) { | 256 | static void update_TouchState_(void *ptr) { |
250 | iTouchState *d = ptr; | 257 | iTouchState *d = ptr; |
251 | /* Check for long presses to simulate right clicks. */ | 258 | /* Check for long presses to simulate right clicks. */ |
@@ -255,6 +262,24 @@ static void update_TouchState_(void *ptr) { | |||
255 | if (touch->pinchId || touch->isTouchDrag) { | 262 | if (touch->pinchId || touch->isTouchDrag) { |
256 | continue; | 263 | continue; |
257 | } | 264 | } |
265 | if (touch->edge) { | ||
266 | const iFloat3 pos = touch->pos[0]; | ||
267 | /* Cancel the swipe if the finger doesn't move or moves mostly vertically. */ | ||
268 | const iFloat3 gestureVector = gestureVector_Touch_(touch); | ||
269 | if (fabsf(2 * x_F3(gestureVector)) < fabsf(y_F3(gestureVector)) || | ||
270 | (isStationary_Touch_(touch) && nowTime - touch->startTime > shortPressSpanMs_)) { | ||
271 | //const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; | ||
272 | //dispatchClick_Touch_(touch, | ||
273 | // touch->edge == left_TouchEdge && swipeDir > 0 ? SDL_BUTTON_X1 : | ||
274 | // touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); | ||
275 | // setHover_Widget(NULL); | ||
276 | postCommandf_App("edgeswipe.ended abort:1 side:%d id:%llu", touch->edge, touch->id); | ||
277 | touch->edge = none_TouchEdge; | ||
278 | /* May be a regular drag along the edge so don't remove. */ | ||
279 | //remove_ArrayIterator(&i); | ||
280 | } | ||
281 | continue; | ||
282 | } | ||
258 | /* Holding a touch will reset previous momentum for this widget. */ | 283 | /* Holding a touch will reset previous momentum for this widget. */ |
259 | if (isStationary_Touch_(touch)) { | 284 | if (isStationary_Touch_(touch)) { |
260 | const int elapsed = nowTime - touch->startTime; | 285 | const int elapsed = nowTime - touch->startTime; |
@@ -340,6 +365,7 @@ static void update_TouchState_(void *ptr) { | |||
340 | } | 365 | } |
341 | } | 366 | } |
342 | 367 | ||
368 | #if 0 | ||
343 | static iWidget *findSlidePanel_Widget_(iWidget *d) { | 369 | static iWidget *findSlidePanel_Widget_(iWidget *d) { |
344 | for (iWidget *w = d; w; w = parent_Widget(w)) { | 370 | for (iWidget *w = d; w; w = parent_Widget(w)) { |
345 | if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) { | 371 | if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) { |
@@ -348,6 +374,7 @@ static iWidget *findSlidePanel_Widget_(iWidget *d) { | |||
348 | } | 374 | } |
349 | return NULL; | 375 | return NULL; |
350 | } | 376 | } |
377 | #endif | ||
351 | 378 | ||
352 | static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) { | 379 | static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) { |
353 | iWidget *affinity = newTouch->affinity; | 380 | iWidget *affinity = newTouch->affinity; |
@@ -365,6 +392,12 @@ static void checkNewPinch_TouchState_(iTouchState *d, iTouch *newTouch) { | |||
365 | pinch.touchIds[1] = other->id; | 392 | pinch.touchIds[1] = other->id; |
366 | newTouch->pinchId = other->pinchId = pinch.id; | 393 | newTouch->pinchId = other->pinchId = pinch.id; |
367 | clearWidgetMomentum_TouchState_(d, affinity); | 394 | clearWidgetMomentum_TouchState_(d, affinity); |
395 | if (other->edge && other->didPostEdgeMove) { | ||
396 | postCommandf_App("edgeswipe.ended abort:1 side:%d id:%llu", other->edge, other->id); | ||
397 | other->didPostEdgeMove = iFalse; | ||
398 | } | ||
399 | other->edge = none_TouchEdge; | ||
400 | newTouch->edge = none_TouchEdge; | ||
368 | /* Remember current positions to determine pinch amount. */ | 401 | /* Remember current positions to determine pinch amount. */ |
369 | newTouch->startPos = newTouch->pos[0]; | 402 | newTouch->startPos = newTouch->pos[0]; |
370 | other->startPos = other->pos[0]; | 403 | other->startPos = other->pos[0]; |
@@ -452,6 +485,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
452 | edge = right_TouchEdge; | 485 | edge = right_TouchEdge; |
453 | } | 486 | } |
454 | iWidget *aff = hitChild_Window(window, init_I2(iRound(x), iRound(y_F3(pos)))); | 487 | iWidget *aff = hitChild_Window(window, init_I2(iRound(x), iRound(y_F3(pos)))); |
488 | #if 0 | ||
455 | if (edge == left_TouchEdge) { | 489 | if (edge == left_TouchEdge) { |
456 | dragging = findSlidePanel_Widget_(aff); | 490 | dragging = findSlidePanel_Widget_(aff); |
457 | if (dragging) { | 491 | if (dragging) { |
@@ -460,6 +494,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
460 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); | 494 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); |
461 | } | 495 | } |
462 | } | 496 | } |
497 | #endif | ||
463 | /* TODO: We must retain a reference to the affinity widget, or otherwise it might | 498 | /* TODO: We must retain a reference to the affinity widget, or otherwise it might |
464 | be destroyed during the gesture. */ | 499 | be destroyed during the gesture. */ |
465 | // printf("aff:[%p] %s:'%s'\n", aff, aff ? class_Widget(aff)->name : "-", | 500 | // printf("aff:[%p] %s:'%s'\n", aff, aff ? class_Widget(aff)->name : "-", |
@@ -469,7 +504,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
469 | iTouch newTouch = { | 504 | iTouch newTouch = { |
470 | .id = fing->fingerId, | 505 | .id = fing->fingerId, |
471 | .affinity = aff, | 506 | .affinity = aff, |
472 | .edgeDragging = dragging, | 507 | // .edgeDragging = dragging, |
473 | .didBeginOnTouchDrag = (flags_Widget(aff) & touchDrag_WidgetFlag) != 0, | 508 | .didBeginOnTouchDrag = (flags_Widget(aff) & touchDrag_WidgetFlag) != 0, |
474 | .edge = edge, | 509 | .edge = edge, |
475 | .startTime = nowTime, | 510 | .startTime = nowTime, |
@@ -487,6 +522,16 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
487 | } | 522 | } |
488 | else if (ev->type == SDL_FINGERMOTION) { | 523 | else if (ev->type == SDL_FINGERMOTION) { |
489 | iTouch *touch = find_TouchState_(d, fing->fingerId); | 524 | iTouch *touch = find_TouchState_(d, fing->fingerId); |
525 | if (touch && touch->edge) { | ||
526 | clear_Array(d->moms); | ||
527 | pushPos_Touch_(touch, pos, nowTime); | ||
528 | postCommandf_App("edgeswipe.moved arg:%d side:%d id:%llu", | ||
529 | (int) (x_F3(pos) - x_F3(touch->startPos)), | ||
530 | touch->edge, | ||
531 | touch->id); | ||
532 | touch->didPostEdgeMove = iTrue; | ||
533 | return iTrue; | ||
534 | } | ||
490 | if (touch && touch->affinity) { | 535 | if (touch && touch->affinity) { |
491 | if (touch->isTouchDrag) { | 536 | if (touch->isTouchDrag) { |
492 | dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK); | 537 | dispatchMotion_Touch_(pos, SDL_BUTTON_LMASK); |
@@ -556,36 +601,18 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
556 | touch->axis = y_TouchAxis; | 601 | touch->axis = y_TouchAxis; |
557 | } | 602 | } |
558 | } | 603 | } |
559 | /* Edge swipe aborted? */ | 604 | iAssert(touch->edge == none_TouchEdge); |
560 | if (touch->edge == left_TouchEdge) { | ||
561 | if (fing->dx < 0 && x_F3(touch->pos[0]) < tapRadiusPt_ * window->pixelRatio) { | ||
562 | touch->edge = none_TouchEdge; | ||
563 | if (touch->edgeDragging) { | ||
564 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); | ||
565 | setVisualOffset_Widget(touch->edgeDragging, 0, 200, easeOut_AnimFlag); | ||
566 | touch->edgeDragging = NULL; | ||
567 | } | ||
568 | } | ||
569 | else if (touch->edgeDragging) { | ||
570 | setVisualOffset_Widget(touch->edgeDragging, x_F3(pos) - x_F3(touch->startPos), 10, 0); | ||
571 | } | ||
572 | } | ||
573 | if (touch->edge == right_TouchEdge && fing->dx > 0) { | ||
574 | touch->edge = none_TouchEdge; | ||
575 | } | ||
576 | if (touch->edge) { | ||
577 | pixels.y = 0; | ||
578 | } | ||
579 | if (touch->axis == x_TouchAxis) { | 605 | if (touch->axis == x_TouchAxis) { |
580 | pixels.y = 0; | 606 | pixels.y = 0; |
581 | } | 607 | } |
582 | if (touch->axis == y_TouchAxis) { | 608 | if (touch->axis == y_TouchAxis) { |
583 | pixels.x = 0; | 609 | pixels.x = 0; |
584 | } | 610 | } |
585 | // printf("%p (%s) py: %i wy: %f acc: %f\n", | 611 | // printf("%p (%s) py: %i wy: %f acc: %f edge: %d\n", |
586 | // touch->affinity, | 612 | // touch->affinity, |
587 | // class_Widget(touch->affinity)->name, | 613 | // class_Widget(touch->affinity)->name, |
588 | // pixels.y, y_F3(amount), y_F3(touch->accum)); | 614 | // pixels.y, y_F3(amount), y_F3(touch->accum), |
615 | // touch->edge); | ||
589 | if (pixels.x || pixels.y) { | 616 | if (pixels.x || pixels.y) { |
590 | setFocus_Widget(NULL); | 617 | setFocus_Widget(NULL); |
591 | dispatchMotion_Touch_(touch->pos[0], 0); | 618 | dispatchMotion_Touch_(touch->pos[0], 0); |
@@ -612,9 +639,21 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
612 | endPinch_TouchState_(d, touch->pinchId); | 639 | endPinch_TouchState_(d, touch->pinchId); |
613 | break; | 640 | break; |
614 | } | 641 | } |
642 | #if 0 | ||
615 | if (touch->edgeDragging) { | 643 | if (touch->edgeDragging) { |
616 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); | 644 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); |
617 | } | 645 | } |
646 | #endif | ||
647 | if (touch->edge && !isStationary_Touch_(touch)) { | ||
648 | const iFloat3 gesture = gestureVector_Touch_(touch); | ||
649 | const float pixel = window->pixelRatio; | ||
650 | const int moveDir = x_F3(gesture) < -pixel ? -1 : x_F3(gesture) > pixel ? +1 : 0; | ||
651 | const int didAbort = (touch->edge == left_TouchEdge && moveDir < 0) || | ||
652 | (touch->edge == right_TouchEdge && moveDir > 0); | ||
653 | postCommandf_App("edgeswipe.ended abort:%d side:%d id:%llu", didAbort, touch->edge, touch->id); | ||
654 | remove_ArrayIterator(&i); | ||
655 | continue; | ||
656 | } | ||
618 | if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { | 657 | if (flags_Widget(touch->affinity) & touchDrag_WidgetFlag) { |
619 | if (!touch->isLeftDown && !touch->isTapAndHold) { | 658 | if (!touch->isLeftDown && !touch->isTapAndHold) { |
620 | /* This will be a click on a touchDrag widget. */ | 659 | /* This will be a click on a touchDrag widget. */ |
@@ -638,6 +677,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
638 | const uint32_t duration = nowTime - touch->startTime; | 677 | const uint32_t duration = nowTime - touch->startTime; |
639 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); | 678 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); |
640 | iFloat3 velocity = zero_F3(); | 679 | iFloat3 velocity = zero_F3(); |
680 | #if 0 | ||
641 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && | 681 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && |
642 | !isStationary_Touch_(touch)) { | 682 | !isStationary_Touch_(touch)) { |
643 | const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; | 683 | const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; |
@@ -646,7 +686,9 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
646 | touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); | 686 | touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); |
647 | setHover_Widget(NULL); | 687 | setHover_Widget(NULL); |
648 | } | 688 | } |
649 | else { | 689 | else |
690 | #endif | ||
691 | { | ||
650 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; | 692 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; |
651 | const float minVelocity = 400.0f; | 693 | const float minVelocity = 400.0f; |
652 | if (elapsed < 150) { | 694 | if (elapsed < 150) { |
diff --git a/src/ui/util.c b/src/ui/util.c index c4fb8886..e0b05a44 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -710,7 +710,8 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | |||
710 | menu, | 710 | menu, |
711 | iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), | 711 | iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), |
712 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | 712 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | |
713 | drawKey_WidgetFlag | (isInfo ? wrapText_WidgetFlag : 0) | itemFlags); | 713 | drawKey_WidgetFlag | itemFlags); |
714 | setWrap_LabelWidget(label, isInfo); | ||
714 | haveIcons |= checkIcon_LabelWidget(label); | 715 | haveIcons |= checkIcon_LabelWidget(label); |
715 | updateSize_LabelWidget(label); /* drawKey was set */ | 716 | updateSize_LabelWidget(label); /* drawKey was set */ |
716 | if (isInfo) { | 717 | if (isInfo) { |
@@ -776,7 +777,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
776 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | 777 | if (isInstance_Object(i.object, &Class_LabelWidget)) { |
777 | iLabelWidget *label = i.object; | 778 | iLabelWidget *label = i.object; |
778 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); | 779 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); |
779 | if (flags_Widget(as_Widget(label)) & wrapText_WidgetFlag) { | 780 | if (isWrapped_LabelWidget(label)) { |
780 | continue; | 781 | continue; |
781 | } | 782 | } |
782 | if (deviceType_App() == desktop_AppDeviceType) { | 783 | if (deviceType_App() == desktop_AppDeviceType) { |
@@ -979,8 +980,8 @@ static void addTabPage_Widget_(iWidget *tabs, enum iWidgetAddPos addPos, iWidget | |||
979 | iClob(newKeyMods_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))), | 980 | iClob(newKeyMods_LabelWidget(label, key, kmods, format_CStr("tabs.switch page:%p", page))), |
980 | addPos); | 981 | addPos); |
981 | setFlags_Widget(button, selected_WidgetFlag, isSel); | 982 | setFlags_Widget(button, selected_WidgetFlag, isSel); |
982 | setFlags_Widget( | 983 | setFlags_Widget(button, commandOnClick_WidgetFlag | expand_WidgetFlag, iTrue); |
983 | button, noTopFrame_WidgetFlag | commandOnClick_WidgetFlag | expand_WidgetFlag, iTrue); | 984 | setNoTopFrame_LabelWidget((iLabelWidget *) button, iTrue); |
984 | addChildPos_Widget(pages, page, addPos); | 985 | addChildPos_Widget(pages, page, addPos); |
985 | if (tabCount_Widget(tabs) > 1) { | 986 | if (tabCount_Widget(tabs) > 1) { |
986 | setFlags_Widget(buttons, hidden_WidgetFlag, iFalse); | 987 | setFlags_Widget(buttons, hidden_WidgetFlag, iFalse); |
@@ -1317,7 +1318,7 @@ void updateValueInput_Widget(iWidget *d, const char *title, const char *prompt) | |||
1317 | 1318 | ||
1318 | static iBool messageHandler_(iWidget *msg, const char *cmd) { | 1319 | static iBool messageHandler_(iWidget *msg, const char *cmd) { |
1319 | /* Almost any command dismisses the sheet. */ | 1320 | /* Almost any command dismisses the sheet. */ |
1320 | /* TODO: Use a "notification" prefix (like `) to ignore all types of commands line this? */ | 1321 | /* TODO: Add a "notification" type of user events to separate them from user actions. */ |
1321 | if (!(equal_Command(cmd, "media.updated") || | 1322 | if (!(equal_Command(cmd, "media.updated") || |
1322 | equal_Command(cmd, "media.player.update") || | 1323 | equal_Command(cmd, "media.player.update") || |
1323 | equal_Command(cmd, "bookmarks.request.finished") || | 1324 | equal_Command(cmd, "bookmarks.request.finished") || |
@@ -1326,6 +1327,7 @@ static iBool messageHandler_(iWidget *msg, const char *cmd) { | |||
1326 | equal_Command(cmd, "document.request.updated") || | 1327 | equal_Command(cmd, "document.request.updated") || |
1327 | equal_Command(cmd, "scrollbar.fade") || | 1328 | equal_Command(cmd, "scrollbar.fade") || |
1328 | equal_Command(cmd, "widget.overflow") || | 1329 | equal_Command(cmd, "widget.overflow") || |
1330 | equal_Command(cmd, "edgeswipe.ended") || | ||
1329 | startsWith_CStr(cmd, "window."))) { | 1331 | startsWith_CStr(cmd, "window."))) { |
1330 | setupSheetTransition_Mobile(msg, iFalse); | 1332 | setupSheetTransition_Mobile(msg, iFalse); |
1331 | destroy_Widget(msg); | 1333 | destroy_Widget(msg); |
@@ -1599,8 +1601,6 @@ iWidget *makePreferences_Widget(void) { | |||
1599 | /* General preferences. */ { | 1601 | /* General preferences. */ { |
1600 | appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values); | 1602 | appendTwoColumnPage_(tabs, "${heading.prefs.general}", '1', &headings, &values); |
1601 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 1603 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
1602 | //addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.downloads}"))); | ||
1603 | //setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads"); | ||
1604 | addPrefsInputWithHeading_(headings, values, "prefs.downloads", iClob(new_InputWidget(0))); | 1604 | addPrefsInputWithHeading_(headings, values, "prefs.downloads", iClob(new_InputWidget(0))); |
1605 | #endif | 1605 | #endif |
1606 | iInputWidget *searchUrl; | 1606 | iInputWidget *searchUrl; |
@@ -1608,12 +1608,6 @@ iWidget *makePreferences_Widget(void) { | |||
1608 | setUrlContent_InputWidget(searchUrl, iTrue); | 1608 | setUrlContent_InputWidget(searchUrl, iTrue); |
1609 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 1609 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
1610 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 1610 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
1611 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}"))); | ||
1612 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.collapsepreonload"))); | ||
1613 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.plaintext.wrap}"))); | ||
1614 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.plaintext.wrap"))); | ||
1615 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.centershort}"))); | ||
1616 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort"))); | ||
1617 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hoverlink}"))); | 1611 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hoverlink}"))); |
1618 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); | 1612 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); |
1619 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); | 1613 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); |
@@ -1674,26 +1668,6 @@ iWidget *makePreferences_Widget(void) { | |||
1674 | } | 1668 | } |
1675 | /* User Interface. */ { | 1669 | /* User Interface. */ { |
1676 | appendTwoColumnPage_(tabs, "${heading.prefs.interface}", '2', &headings, &values); | 1670 | appendTwoColumnPage_(tabs, "${heading.prefs.interface}", '2', &headings, &values); |
1677 | #if defined (iPlatformApple) || defined (iPlatformMSys) | ||
1678 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}"))); | ||
1679 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); | ||
1680 | #endif | ||
1681 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}"))); | ||
1682 | iWidget *themes = new_Widget(); | ||
1683 | /* Themes. */ { | ||
1684 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.black}", "theme.set arg:0"))), "prefs.theme.0"); | ||
1685 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.dark}", "theme.set arg:1"))), "prefs.theme.1"); | ||
1686 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.light}", "theme.set arg:2"))), "prefs.theme.2"); | ||
1687 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.white}", "theme.set arg:3"))), "prefs.theme.3"); | ||
1688 | } | ||
1689 | addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1690 | /* Accents. */ | ||
1691 | iWidget *accent = new_Widget(); { | ||
1692 | setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.teal}", "accent.set arg:0"))), "prefs.accent.0"); | ||
1693 | setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.orange}", "accent.set arg:1"))), "prefs.accent.1"); | ||
1694 | } | ||
1695 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.accent}"))); | ||
1696 | addChildFlags_Widget(values, iClob(accent), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1697 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 1671 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
1698 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); | 1672 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); |
1699 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); | 1673 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); |
@@ -1718,6 +1692,27 @@ iWidget *makePreferences_Widget(void) { | |||
1718 | } | 1692 | } |
1719 | /* Colors. */ { | 1693 | /* Colors. */ { |
1720 | appendTwoColumnPage_(tabs, "${heading.prefs.colors}", '3', &headings, &values); | 1694 | appendTwoColumnPage_(tabs, "${heading.prefs.colors}", '3', &headings, &values); |
1695 | makeTwoColumnHeading_("${heading.prefs.uitheme}", headings, values); | ||
1696 | #if defined (iPlatformApple) || defined (iPlatformMSys) | ||
1697 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}"))); | ||
1698 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); | ||
1699 | #endif | ||
1700 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}"))); | ||
1701 | iWidget *themes = new_Widget(); | ||
1702 | /* Themes. */ { | ||
1703 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.black}", "theme.set arg:0"))), "prefs.theme.0"); | ||
1704 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.dark}", "theme.set arg:1"))), "prefs.theme.1"); | ||
1705 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.light}", "theme.set arg:2"))), "prefs.theme.2"); | ||
1706 | setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("${prefs.theme.white}", "theme.set arg:3"))), "prefs.theme.3"); | ||
1707 | } | ||
1708 | addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1709 | /* Accents. */ | ||
1710 | iWidget *accent = new_Widget(); { | ||
1711 | setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.teal}", "accent.set arg:0"))), "prefs.accent.0"); | ||
1712 | setId_Widget(addChild_Widget(accent, iClob(new_LabelWidget("${prefs.accent.orange}", "accent.set arg:1"))), "prefs.accent.1"); | ||
1713 | } | ||
1714 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.accent}"))); | ||
1715 | addChildFlags_Widget(values, iClob(accent), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1721 | makeTwoColumnHeading_("${heading.prefs.pagecontent}", headings, values); | 1716 | makeTwoColumnHeading_("${heading.prefs.pagecontent}", headings, values); |
1722 | for (int i = 0; i < 2; ++i) { | 1717 | for (int i = 0; i < 2; ++i) { |
1723 | const iBool isDark = (i == 0); | 1718 | const iBool isDark = (i == 0); |
@@ -1750,14 +1745,15 @@ iWidget *makePreferences_Widget(void) { | |||
1750 | } | 1745 | } |
1751 | addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1746 | addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1752 | } | 1747 | } |
1753 | /* Layout. */ { | 1748 | /* Fonts. */ { |
1754 | setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.style}", '4', &headings, &values), "prefs.page.style"); | 1749 | setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); |
1755 | makeTwoColumnHeading_("${heading.prefs.fonts}", headings, values); | ||
1756 | /* Fonts. */ { | 1750 | /* Fonts. */ { |
1757 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}"))); | 1751 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.headingfont}"))); |
1758 | addFontButtons_(values, "headingfont"); | 1752 | addFontButtons_(values, "headingfont"); |
1759 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); | 1753 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); |
1760 | addFontButtons_(values, "font"); | 1754 | addFontButtons_(values, "font"); |
1755 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | ||
1756 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
1761 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); | 1757 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); |
1762 | iWidget *mono = new_Widget(); { | 1758 | iWidget *mono = new_Widget(); { |
1763 | iWidget *tog; | 1759 | iWidget *tog; |
@@ -1789,9 +1785,18 @@ iWidget *makePreferences_Widget(void) { | |||
1789 | updateSize_LabelWidget((iLabelWidget *) tog); | 1785 | updateSize_LabelWidget((iLabelWidget *) tog); |
1790 | } | 1786 | } |
1791 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1787 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1792 | addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(new_InputWidget(0))); | 1788 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
1793 | } | 1789 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
1794 | makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); | 1790 | /* Custom font. */ { |
1791 | iInputWidget *customFont = new_InputWidget(0); | ||
1792 | setHint_InputWidget(customFont, "${hint.prefs.userfont}"); | ||
1793 | addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(customFont)); | ||
1794 | } | ||
1795 | } | ||
1796 | } | ||
1797 | /* Style. */ { | ||
1798 | setId_Widget(appendTwoColumnPage_(tabs, "${heading.prefs.style}", '5', &headings, &values), "prefs.page.style"); | ||
1799 | // makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); | ||
1795 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); | 1800 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); |
1796 | iWidget *widths = new_Widget(); | 1801 | iWidget *widths = new_Widget(); |
1797 | /* Line widths. */ { | 1802 | /* Line widths. */ { |
@@ -1811,14 +1816,20 @@ iWidget *makePreferences_Widget(void) { | |||
1811 | addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1816 | addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1812 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.biglede}"))); | 1817 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.biglede}"))); |
1813 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede"))); | 1818 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede"))); |
1819 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.plaintext.wrap}"))); | ||
1820 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.plaintext.wrap"))); | ||
1821 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}"))); | ||
1822 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.collapsepreonload"))); | ||
1814 | // makeTwoColumnHeading_("${heading.prefs.widelayout}", headings, values); | 1823 | // makeTwoColumnHeading_("${heading.prefs.widelayout}", headings, values); |
1815 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 1824 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
1816 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 1825 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
1817 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.sideicon}"))); | 1826 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.sideicon}"))); |
1818 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon"))); | 1827 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon"))); |
1828 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.centershort}"))); | ||
1829 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort"))); | ||
1819 | } | 1830 | } |
1820 | /* Network. */ { | 1831 | /* Network. */ { |
1821 | appendTwoColumnPage_(tabs, "${heading.prefs.network}", '5', &headings, &values); | 1832 | appendTwoColumnPage_(tabs, "${heading.prefs.network}", '6', &headings, &values); |
1822 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}"))); | 1833 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.decodeurls}"))); |
1823 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); | 1834 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.decodeurls"))); |
1824 | /* Cache size. */ { | 1835 | /* Cache size. */ { |
@@ -1832,6 +1843,17 @@ iWidget *makePreferences_Widget(void) { | |||
1832 | resizeToParentHeight_WidgetFlag); | 1843 | resizeToParentHeight_WidgetFlag); |
1833 | setContentPadding_InputWidget(cache, 0, width_Widget(unit) - 4 * gap_UI); | 1844 | setContentPadding_InputWidget(cache, 0, width_Widget(unit) - 4 * gap_UI); |
1834 | } | 1845 | } |
1846 | /* Memory size. */ { | ||
1847 | iInputWidget *mem = new_InputWidget(4); | ||
1848 | setSelectAllOnFocus_InputWidget(mem, iTrue); | ||
1849 | addPrefsInputWithHeading_(headings, values, "prefs.memorysize", iClob(mem)); | ||
1850 | iWidget *unit = | ||
1851 | addChildFlags_Widget(as_Widget(mem), | ||
1852 | iClob(new_LabelWidget("${mb}", NULL)), | ||
1853 | frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag | | ||
1854 | resizeToParentHeight_WidgetFlag); | ||
1855 | setContentPadding_InputWidget(mem, 0, width_Widget(unit) - 4 * gap_UI); | ||
1856 | } | ||
1835 | makeTwoColumnHeading_("${heading.prefs.certs}", headings, values); | 1857 | makeTwoColumnHeading_("${heading.prefs.certs}", headings, values); |
1836 | addPrefsInputWithHeading_(headings, values, "prefs.ca.file", iClob(new_InputWidget(0))); | 1858 | addPrefsInputWithHeading_(headings, values, "prefs.ca.file", iClob(new_InputWidget(0))); |
1837 | addPrefsInputWithHeading_(headings, values, "prefs.ca.path", iClob(new_InputWidget(0))); | 1859 | addPrefsInputWithHeading_(headings, values, "prefs.ca.path", iClob(new_InputWidget(0))); |
@@ -1843,7 +1865,7 @@ iWidget *makePreferences_Widget(void) { | |||
1843 | /* Keybindings. */ | 1865 | /* Keybindings. */ |
1844 | if (deviceType_App() == desktop_AppDeviceType) { | 1866 | if (deviceType_App() == desktop_AppDeviceType) { |
1845 | iBindingsWidget *bind = new_BindingsWidget(); | 1867 | iBindingsWidget *bind = new_BindingsWidget(); |
1846 | appendFramelessTabPage_(tabs, iClob(bind), "${heading.prefs.keys}", '6', KMOD_PRIMARY); | 1868 | appendFramelessTabPage_(tabs, iClob(bind), "${heading.prefs.keys}", '7', KMOD_PRIMARY); |
1847 | } | 1869 | } |
1848 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 1870 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
1849 | updatePreferencesLayout_Widget(dlg); | 1871 | updatePreferencesLayout_Widget(dlg); |
diff --git a/src/ui/widget.c b/src/ui/widget.c index 543b8bc9..992f115d 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -82,10 +82,10 @@ static void visualOffsetAnimation_Widget_(void *ptr) { | |||
82 | 82 | ||
83 | void deinit_Widget(iWidget *d) { | 83 | void deinit_Widget(iWidget *d) { |
84 | releaseChildren_Widget(d); | 84 | releaseChildren_Widget(d); |
85 | //#if !defined (NDEBUG) | 85 | #if 0 && !defined (NDEBUG) |
86 | // printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), | 86 | printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), |
87 | // d->flags & keepOnTop_WidgetFlag ? 1 : 0); | 87 | d->flags & keepOnTop_WidgetFlag ? 1 : 0); |
88 | //#endif | 88 | #endif |
89 | deinit_String(&d->id); | 89 | deinit_String(&d->id); |
90 | if (d->flags & keepOnTop_WidgetFlag) { | 90 | if (d->flags & keepOnTop_WidgetFlag) { |
91 | removeAll_PtrArray(onTop_Root(d->root), d); | 91 | removeAll_PtrArray(onTop_Root(d->root), d); |
@@ -321,7 +321,6 @@ static iBool setWidth_Widget_(iWidget *d, int width) { | |||
321 | d->rect.size.x = width; | 321 | d->rect.size.x = width; |
322 | TRACE(d, "width has changed to %d", width); | 322 | TRACE(d, "width has changed to %d", width); |
323 | if (class_Widget(d)->sizeChanged) { | 323 | if (class_Widget(d)->sizeChanged) { |
324 | const int oldHeight = d->rect.size.y; | ||
325 | class_Widget(d)->sizeChanged(d); | 324 | class_Widget(d)->sizeChanged(d); |
326 | } | 325 | } |
327 | return iTrue; | 326 | return iTrue; |
@@ -407,6 +406,11 @@ static void boundsOfChildren_Widget_(const iWidget *d, iRect *bounds_out) { | |||
407 | iRect childRect = child->rect; | 406 | iRect childRect = child->rect; |
408 | if (child->flags & ignoreForParentWidth_WidgetFlag) { | 407 | if (child->flags & ignoreForParentWidth_WidgetFlag) { |
409 | childRect.size.x = 0; | 408 | childRect.size.x = 0; |
409 | childRect.pos.x = bounds_out->pos.x; | ||
410 | } | ||
411 | if (child->flags & ignoreForParentHeight_WidgetFlag) { | ||
412 | childRect.size.y = 0; | ||
413 | childRect.pos.y = bounds_out->pos.y; | ||
410 | } | 414 | } |
411 | if (isEmpty_Rect(*bounds_out)) { | 415 | if (isEmpty_Rect(*bounds_out)) { |
412 | *bounds_out = childRect; | 416 | *bounds_out = childRect; |
@@ -760,6 +764,27 @@ void arrange_Widget(iWidget *d) { | |||
760 | } | 764 | } |
761 | } | 765 | } |
762 | 766 | ||
767 | iBool isBeingVisuallyOffsetByReference_Widget(const iWidget *d) { | ||
768 | return visualOffsetByReference_Widget(d) != 0; | ||
769 | } | ||
770 | |||
771 | int visualOffsetByReference_Widget(const iWidget *d) { | ||
772 | if (d->offsetRef && d->flags & refChildrenOffset_WidgetFlag) { | ||
773 | int offX = 0; | ||
774 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { | ||
775 | const iWidget *child = i.object; | ||
776 | if (child == d) continue; | ||
777 | if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
778 | // const float factor = width_Widget(d) / (float) size_Root(d->root).x; | ||
779 | const int invOff = width_Widget(d) - iRound(value_Anim(&child->visualOffset)); | ||
780 | offX -= invOff / 4; | ||
781 | } | ||
782 | } | ||
783 | return offX; | ||
784 | } | ||
785 | return 0; | ||
786 | } | ||
787 | |||
763 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | 788 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { |
764 | if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | 789 | if (d->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { |
765 | const int off = iRound(value_Anim(&d->visualOffset)); | 790 | const int off = iRound(value_Anim(&d->visualOffset)); |
@@ -774,14 +799,7 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | |||
774 | pos->y -= value_Anim(d->animOffsetRef); | 799 | pos->y -= value_Anim(d->animOffsetRef); |
775 | } | 800 | } |
776 | if (d->flags & refChildrenOffset_WidgetFlag) { | 801 | if (d->flags & refChildrenOffset_WidgetFlag) { |
777 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { | 802 | pos->x += visualOffsetByReference_Widget(d); |
778 | const iWidget *child = i.object; | ||
779 | if (child == d) continue; | ||
780 | if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
781 | const int invOff = size_Root(d->root).x - iRound(value_Anim(&child->visualOffset)); | ||
782 | pos->x -= invOff / 4; | ||
783 | } | ||
784 | } | ||
785 | } | 803 | } |
786 | } | 804 | } |
787 | 805 | ||
@@ -1057,10 +1075,38 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1057 | isCommand_UserEvent(ev, "widget.overflow")) { | 1075 | isCommand_UserEvent(ev, "widget.overflow")) { |
1058 | scrollOverflow_Widget(d, 0); /* check bounds */ | 1076 | scrollOverflow_Widget(d, 0); /* check bounds */ |
1059 | } | 1077 | } |
1060 | if (ev->user.code == command_UserEventCode && d->commandHandler && | 1078 | if (ev->user.code == command_UserEventCode) { |
1061 | d->commandHandler(d, ev->user.data1)) { | 1079 | const char *cmd = command_UserEvent(ev); |
1062 | iAssert(get_Root() == d->root); | 1080 | if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && |
1063 | return iTrue; | 1081 | isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && |
1082 | equal_Command(cmd, "edgeswipe.moved")) { | ||
1083 | /* Check the side. */ | ||
1084 | const int side = argLabel_Command(cmd, "side"); | ||
1085 | if ((side == 1 && d->flags & leftEdgeDraggable_WidgetFlag) || | ||
1086 | (side == 2 && d->flags & rightEdgeDraggable_WidgetFlag)) { | ||
1087 | if (~d->flags & dragged_WidgetFlag) { | ||
1088 | setFlags_Widget(d, dragged_WidgetFlag, iTrue); | ||
1089 | } | ||
1090 | setVisualOffset_Widget(d, arg_Command(command_UserEvent(ev)) * | ||
1091 | width_Widget(d) / size_Root(d->root).x, | ||
1092 | 10, 0); | ||
1093 | return iTrue; | ||
1094 | } | ||
1095 | } | ||
1096 | if (d->flags & dragged_WidgetFlag && equal_Command(cmd, "edgeswipe.ended")) { | ||
1097 | if (argLabel_Command(cmd, "abort")) { | ||
1098 | setVisualOffset_Widget(d, 0, 200, easeOut_AnimFlag); | ||
1099 | } | ||
1100 | else { | ||
1101 | postCommand_Widget( | ||
1102 | d, argLabel_Command(cmd, "side") == 1 ? "swipe.back" : "swipe.forward"); | ||
1103 | } | ||
1104 | setFlags_Widget(d, dragged_WidgetFlag, iFalse); | ||
1105 | } | ||
1106 | if (d->commandHandler && d->commandHandler(d, ev->user.data1)) { | ||
1107 | iAssert(get_Root() == d->root); | ||
1108 | return iTrue; | ||
1109 | } | ||
1064 | } | 1110 | } |
1065 | break; | 1111 | break; |
1066 | } | 1112 | } |
@@ -1091,6 +1137,17 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1091 | return iFalse; | 1137 | return iFalse; |
1092 | } | 1138 | } |
1093 | 1139 | ||
1140 | int backgroundFadeColor_Widget(void) { | ||
1141 | switch (colorTheme_App()) { | ||
1142 | case light_ColorTheme: | ||
1143 | return gray25_ColorId; | ||
1144 | case pureWhite_ColorTheme: | ||
1145 | return gray50_ColorId; | ||
1146 | default: | ||
1147 | return black_ColorId; | ||
1148 | } | ||
1149 | } | ||
1150 | |||
1094 | void drawBackground_Widget(const iWidget *d) { | 1151 | void drawBackground_Widget(const iWidget *d) { |
1095 | if (d->flags & noBackground_WidgetFlag) { | 1152 | if (d->flags & noBackground_WidgetFlag) { |
1096 | return; | 1153 | return; |
@@ -1107,14 +1164,13 @@ void drawBackground_Widget(const iWidget *d) { | |||
1107 | shadowBorder = iFalse; | 1164 | shadowBorder = iFalse; |
1108 | } | 1165 | } |
1109 | } | 1166 | } |
1110 | if (shadowBorder) { | 1167 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { |
1111 | iPaint p; | 1168 | iPaint p; |
1112 | init_Paint(&p); | 1169 | init_Paint(&p); |
1113 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1170 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1114 | } | 1171 | } |
1115 | const iBool isFaded = fadeBackground && | 1172 | const iBool isFaded = fadeBackground && |
1116 | ~d->flags & noFadeBackground_WidgetFlag;/* && | 1173 | ~d->flags & noFadeBackground_WidgetFlag; |
1117 | ~d->flags & destroyPending_WidgetFlag;*/ | ||
1118 | if (isFaded) { | 1174 | if (isFaded) { |
1119 | iPaint p; | 1175 | iPaint p; |
1120 | init_Paint(&p); | 1176 | init_Paint(&p); |
@@ -1125,19 +1181,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1125 | p.alpha *= (area > 0 ? visibleArea / area : 0.0f); | 1181 | p.alpha *= (area > 0 ? visibleArea / area : 0.0f); |
1126 | } | 1182 | } |
1127 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | 1183 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); |
1128 | int fadeColor; | 1184 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); |
1129 | switch (colorTheme_App()) { | ||
1130 | default: | ||
1131 | fadeColor = black_ColorId; | ||
1132 | break; | ||
1133 | case light_ColorTheme: | ||
1134 | fadeColor = gray25_ColorId; | ||
1135 | break; | ||
1136 | case pureWhite_ColorTheme: | ||
1137 | fadeColor = gray50_ColorId; | ||
1138 | break; | ||
1139 | } | ||
1140 | fillRect_Paint(&p, rect_Root(d->root), fadeColor); | ||
1141 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 1185 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
1142 | } | 1186 | } |
1143 | if (d->bgColor >= 0 || d->frameColor >= 0) { | 1187 | if (d->bgColor >= 0 || d->frameColor >= 0) { |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 8de62b7a..41784b99 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -87,12 +87,12 @@ enum iWidgetFlag { | |||
87 | }; | 87 | }; |
88 | 88 | ||
89 | /* 64-bit extended flags */ | 89 | /* 64-bit extended flags */ |
90 | //#define wasCollapsed_WidgetFlag iBit64(32) | 90 | #define rightEdgeDraggable_WidgetFlag iBit64(31) |
91 | #define disabledWhenHidden_WidgetFlag iBit64(32) | 91 | #define disabledWhenHidden_WidgetFlag iBit64(32) |
92 | #define centerHorizontal_WidgetFlag iBit64(33) | 92 | #define centerHorizontal_WidgetFlag iBit64(33) |
93 | #define moveToParentLeftEdge_WidgetFlag iBit64(34) | 93 | #define moveToParentLeftEdge_WidgetFlag iBit64(34) |
94 | #define moveToParentRightEdge_WidgetFlag iBit64(35) | 94 | #define moveToParentRightEdge_WidgetFlag iBit64(35) |
95 | #define wrapText_WidgetFlag iBit64(36) | 95 | #define noShadowBorder_WidgetFlag iBit64(36) |
96 | #define borderTop_WidgetFlag iBit64(37) | 96 | #define borderTop_WidgetFlag iBit64(37) |
97 | #define overflowScrollable_WidgetFlag iBit64(38) | 97 | #define overflowScrollable_WidgetFlag iBit64(38) |
98 | #define focusRoot_WidgetFlag iBit64(39) | 98 | #define focusRoot_WidgetFlag iBit64(39) |
@@ -103,7 +103,7 @@ enum iWidgetFlag { | |||
103 | #define drawBackgroundToVerticalSafeArea_WidgetFlag iBit64(44) | 103 | #define drawBackgroundToVerticalSafeArea_WidgetFlag iBit64(44) |
104 | #define visualOffset_WidgetFlag iBit64(45) | 104 | #define visualOffset_WidgetFlag iBit64(45) |
105 | #define parentCannotResize_WidgetFlag iBit64(46) | 105 | #define parentCannotResize_WidgetFlag iBit64(46) |
106 | #define noTopFrame_WidgetFlag iBit64(47) | 106 | #define ignoreForParentHeight_WidgetFlag iBit64(47) |
107 | #define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */ | 107 | #define unpadded_WidgetFlag iBit64(48) /* ignore parent's padding */ |
108 | #define extraPadding_WidgetFlag iBit64(49) | 108 | #define extraPadding_WidgetFlag iBit64(49) |
109 | #define borderBottom_WidgetFlag iBit64(50) | 109 | #define borderBottom_WidgetFlag iBit64(50) |
@@ -117,8 +117,8 @@ enum iWidgetFlag { | |||
117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) | 117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) |
118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) | 118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) |
119 | #define noFadeBackground_WidgetFlag iBit64(60) | 119 | #define noFadeBackground_WidgetFlag iBit64(60) |
120 | #define destroyPending_WidgetFlag iBit64(61) /* TODO: needed? */ | 120 | #define destroyPending_WidgetFlag iBit64(61) |
121 | #define edgeDraggable_WidgetFlag iBit64(62) | 121 | #define leftEdgeDraggable_WidgetFlag iBit64(62) |
122 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ | 122 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ |
123 | 123 | ||
124 | enum iWidgetAddPos { | 124 | enum iWidgetAddPos { |
@@ -242,8 +242,9 @@ iBool isSelected_Widget (const iAnyObject *); | |||
242 | iBool isUnderKeyRoot_Widget (const iAnyObject *); | 242 | iBool isUnderKeyRoot_Widget (const iAnyObject *); |
243 | iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); | 243 | iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); |
244 | iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); | 244 | iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); |
245 | iBool isAffectedByVisualOffset_Widget | 245 | iBool isAffectedByVisualOffset_Widget (const iWidget *); |
246 | (const iWidget *); | 246 | iBool isBeingVisuallyOffsetByReference_Widget (const iWidget *); |
247 | int visualOffsetByReference_Widget (const iWidget *); | ||
247 | void setId_Widget (iWidget *, const char *id); | 248 | void setId_Widget (iWidget *, const char *id); |
248 | void setFlags_Widget (iWidget *, int64_t flags, iBool set); | 249 | void setFlags_Widget (iWidget *, int64_t flags, iBool set); |
249 | void setPos_Widget (iWidget *, iInt2 pos); | 250 | void setPos_Widget (iWidget *, iInt2 pos); |
@@ -276,6 +277,8 @@ void refresh_Widget (const iAnyObject *); | |||
276 | 277 | ||
277 | iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand); | 278 | iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand); |
278 | 279 | ||
280 | int backgroundFadeColor_Widget (void); | ||
281 | |||
279 | void setFocus_Widget (iWidget *); | 282 | void setFocus_Widget (iWidget *); |
280 | iWidget * focus_Widget (void); | 283 | iWidget * focus_Widget (void); |
281 | void setHover_Widget (iWidget *); | 284 | void setHover_Widget (iWidget *); |
diff --git a/src/ui/window.c b/src/ui/window.c index 96a22fee..f71d8102 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -69,10 +69,6 @@ iDefineTypeConstructionArgs(Window, (iRect rect), rect) | |||
69 | 69 | ||
70 | /* TODO: Define menus per platform. */ | 70 | /* TODO: Define menus per platform. */ |
71 | 71 | ||
72 | #if defined (iPlatformAppleDesktop) | ||
73 | # define iHaveNativeMenus | ||
74 | #endif | ||
75 | |||
76 | #if defined (iHaveNativeMenus) | 72 | #if defined (iHaveNativeMenus) |
77 | /* Using native menus. */ | 73 | /* Using native menus. */ |
78 | static const iMenuItem fileMenuItems_[] = { | 74 | static const iMenuItem fileMenuItems_[] = { |
@@ -202,7 +198,7 @@ static void windowSizeChanged_Window_(iWindow *d) { | |||
202 | } | 198 | } |
203 | 199 | ||
204 | static void setupUserInterface_Window(iWindow *d) { | 200 | static void setupUserInterface_Window(iWindow *d) { |
205 | #if defined (iPlatformAppleDesktop) | 201 | #if defined (iHaveNativeMenus) |
206 | insertMacMenus_(); | 202 | insertMacMenus_(); |
207 | #endif | 203 | #endif |
208 | /* One root is created by default. */ | 204 | /* One root is created by default. */ |
@@ -913,7 +909,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
913 | } | 909 | } |
914 | } | 910 | } |
915 | if (isCommand_UserEvent(&event, "lang.changed")) { | 911 | if (isCommand_UserEvent(&event, "lang.changed")) { |
916 | #if defined (iPlatformAppleDesktop) | 912 | #if defined (iHaveNativeMenus) |
917 | /* Retranslate the menus. */ | 913 | /* Retranslate the menus. */ |
918 | removeMacMenus_(); | 914 | removeMacMenus_(); |
919 | insertMacMenus_(); | 915 | insertMacMenus_(); |