diff options
Diffstat (limited to 'src')
55 files changed, 5508 insertions, 1806 deletions
@@ -117,7 +117,8 @@ struct Impl_App { | |||
117 | iGmCerts * certs; | 117 | iGmCerts * certs; |
118 | iVisited * visited; | 118 | iVisited * visited; |
119 | iBookmarks * bookmarks; | 119 | iBookmarks * bookmarks; |
120 | iWindow * window; | 120 | iMainWindow *window; |
121 | iPtrArray popupWindows; | ||
121 | iSortedArray tickers; /* per-frame callbacks, used for animations */ | 122 | iSortedArray tickers; /* per-frame callbacks, used for animations */ |
122 | uint32_t lastTickerTime; | 123 | uint32_t lastTickerTime; |
123 | uint32_t elapsedSinceLastTicker; | 124 | uint32_t elapsedSinceLastTicker; |
@@ -188,7 +189,7 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
188 | /* On macOS, maximization should be applied at creation time or the window will take | 189 | /* On macOS, maximization should be applied at creation time or the window will take |
189 | a moment to animate to its maximized size. */ | 190 | a moment to animate to its maximized size. */ |
190 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 191 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
191 | if (snap_Window(d->window)) { | 192 | if (snap_MainWindow(d->window)) { |
192 | if (~SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MINIMIZED) { | 193 | if (~SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MINIMIZED) { |
193 | /* Save the actual visible window position, too, because snapped windows may | 194 | /* Save the actual visible window position, too, because snapped windows may |
194 | still be resized/moved without affecting normalRect. */ | 195 | still be resized/moved without affecting normalRect. */ |
@@ -196,17 +197,17 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
196 | SDL_GetWindowSize(d->window->win, &w, &h); | 197 | SDL_GetWindowSize(d->window->win, &w, &h); |
197 | appendFormat_String( | 198 | appendFormat_String( |
198 | str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", | 199 | str, "~window.setrect snap:%d width:%d height:%d coord:%d %d\n", |
199 | snap_Window(d->window), w, h, x, y); | 200 | snap_MainWindow(d->window), w, h, x, y); |
200 | } | 201 | } |
201 | } | 202 | } |
202 | #elif !defined (iPlatformApple) | 203 | #elif !defined (iPlatformApple) |
203 | if (snap_Window(d->window) == maximized_WindowSnap) { | 204 | if (snap_MainWindow(d->window) == maximized_WindowSnap) { |
204 | appendFormat_String(str, "~window.maximize\n"); | 205 | appendFormat_String(str, "~window.maximize\n"); |
205 | } | 206 | } |
206 | #endif | 207 | #endif |
207 | } | 208 | } |
208 | appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.uiLanguage)); | 209 | appendFormat_String(str, "uilang id:%s\n", cstr_String(&d->prefs.uiLanguage)); |
209 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); | 210 | appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(as_Window(d->window))); |
210 | appendFormat_String(str, "prefs.dialogtab arg:%d\n", d->prefs.dialogTab); | 211 | appendFormat_String(str, "prefs.dialogtab arg:%d\n", d->prefs.dialogTab); |
211 | appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); | 212 | appendFormat_String(str, "font.set arg:%d\n", d->prefs.font); |
212 | appendFormat_String(str, "font.user path:%s\n", cstr_String(&d->prefs.symbolFontPath)); | 213 | appendFormat_String(str, "font.user path:%s\n", cstr_String(&d->prefs.symbolFontPath)); |
@@ -242,12 +243,15 @@ static iString *serializePrefs_App_(const iApp *d) { | |||
242 | appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark); | 243 | appendFormat_String(str, "doctheme.dark.set arg:%d\n", d->prefs.docThemeDark); |
243 | appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight); | 244 | appendFormat_String(str, "doctheme.light.set arg:%d\n", d->prefs.docThemeLight); |
244 | appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f)); | 245 | appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f)); |
246 | appendFormat_String(str, "imagestyle.set arg:%d\n", d->prefs.imageStyle); | ||
245 | appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.caFile)); | 247 | appendFormat_String(str, "ca.file noset:1 path:%s\n", cstr_String(&d->prefs.caFile)); |
246 | appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.caPath)); | 248 | appendFormat_String(str, "ca.path path:%s\n", cstr_String(&d->prefs.caPath)); |
247 | appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy)); | 249 | appendFormat_String(str, "proxy.gemini address:%s\n", cstr_String(&d->prefs.geminiProxy)); |
248 | appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy)); | 250 | appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy)); |
249 | appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy)); | 251 | appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy)); |
252 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | ||
250 | appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir)); | 253 | appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir)); |
254 | #endif | ||
251 | appendFormat_String(str, "searchurl address:%s\n", cstr_String(&d->prefs.searchUrl)); | 255 | appendFormat_String(str, "searchurl address:%s\n", cstr_String(&d->prefs.searchUrl)); |
252 | appendFormat_String(str, "translation.languages from:%d to:%d\n", d->prefs.langFrom, d->prefs.langTo); | 256 | appendFormat_String(str, "translation.languages from:%d to:%d\n", d->prefs.langFrom, d->prefs.langTo); |
253 | return str; | 257 | return str; |
@@ -411,8 +415,8 @@ static iBool loadState_App_(iApp *d) { | |||
411 | const int splitMode = read32_File(f); | 415 | const int splitMode = read32_File(f); |
412 | const int keyRoot = read32_File(f); | 416 | const int keyRoot = read32_File(f); |
413 | d->window->pendingSplitMode = splitMode; | 417 | d->window->pendingSplitMode = splitMode; |
414 | setSplitMode_Window(d->window, splitMode | noEvents_WindowSplit); | 418 | setSplitMode_MainWindow(d->window, splitMode | noEvents_WindowSplit); |
415 | d->window->keyRoot = d->window->roots[keyRoot]; | 419 | d->window->base.keyRoot = d->window->base.roots[keyRoot]; |
416 | } | 420 | } |
417 | else if (!memcmp(magic, magicSidebar_App_, 4)) { | 421 | else if (!memcmp(magic, magicSidebar_App_, 4)) { |
418 | const uint16_t bits = readU16_File(f); | 422 | const uint16_t bits = readU16_File(f); |
@@ -421,12 +425,22 @@ static iBool loadState_App_(iApp *d) { | |||
421 | readf_Stream(stream_File(f)), | 425 | readf_Stream(stream_File(f)), |
422 | readf_Stream(stream_File(f)) | 426 | readf_Stream(stream_File(f)) |
423 | }; | 427 | }; |
428 | iIntSet *closedFolders[2] = { | ||
429 | collectNew_IntSet(), | ||
430 | collectNew_IntSet() | ||
431 | }; | ||
432 | if (version >= bookmarkFolderState_FileVersion) { | ||
433 | deserialize_IntSet(closedFolders[0], stream_File(f)); | ||
434 | deserialize_IntSet(closedFolders[1], stream_File(f)); | ||
435 | } | ||
424 | const uint8_t rootIndex = bits & 0xff; | 436 | const uint8_t rootIndex = bits & 0xff; |
425 | const uint8_t flags = bits >> 8; | 437 | const uint8_t flags = bits >> 8; |
426 | iRoot *root = d->window->roots[rootIndex]; | 438 | iRoot *root = d->window->base.roots[rootIndex]; |
427 | if (root) { | 439 | if (root) { |
428 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 440 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
429 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); | 441 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); |
442 | setClosedFolders_SidebarWidget(sidebar, closedFolders[0]); | ||
443 | setClosedFolders_SidebarWidget(sidebar2, closedFolders[1]); | ||
430 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); | 444 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); |
431 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); | 445 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); |
432 | if (deviceType_App() != phone_AppDeviceType) { | 446 | if (deviceType_App() != phone_AppDeviceType) { |
@@ -440,10 +454,10 @@ static iBool loadState_App_(iApp *d) { | |||
440 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { | 454 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { |
441 | const int8_t flags = read8_File(f); | 455 | const int8_t flags = read8_File(f); |
442 | int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; | 456 | int rootIndex = flags & rootIndex1_DocumentStateFlag ? 1 : 0; |
443 | if (rootIndex > numRoots_Window(d->window) - 1) { | 457 | if (rootIndex > numRoots_Window(as_Window(d->window)) - 1) { |
444 | rootIndex = 0; | 458 | rootIndex = 0; |
445 | } | 459 | } |
446 | setCurrent_Root(d->window->roots[rootIndex]); | 460 | setCurrent_Root(d->window->base.roots[rootIndex]); |
447 | if (isFirstTab[rootIndex]) { | 461 | if (isFirstTab[rootIndex]) { |
448 | isFirstTab[rootIndex] = iFalse; | 462 | isFirstTab[rootIndex] = iFalse; |
449 | /* There is one pre-created tab in each root. */ | 463 | /* There is one pre-created tab in each root. */ |
@@ -466,7 +480,7 @@ static iBool loadState_App_(iApp *d) { | |||
466 | } | 480 | } |
467 | if (d->window->splitMode) { | 481 | if (d->window->splitMode) { |
468 | /* Update root placement. */ | 482 | /* Update root placement. */ |
469 | resize_Window(d->window, -1, -1); | 483 | resize_MainWindow(d->window, -1, -1); |
470 | } | 484 | } |
471 | iForIndices(i, current) { | 485 | iForIndices(i, current) { |
472 | postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); | 486 | postCommandf_Root(NULL, "tabs.switch page:%p", current[i]); |
@@ -480,7 +494,7 @@ static iBool loadState_App_(iApp *d) { | |||
480 | static void saveState_App_(const iApp *d) { | 494 | static void saveState_App_(const iApp *d) { |
481 | iUnused(d); | 495 | iUnused(d); |
482 | trimCache_App(); | 496 | trimCache_App(); |
483 | iWindow *win = d->window; | 497 | iMainWindow *win = d->window; |
484 | /* UI state is saved in binary because it is quite complex (e.g., | 498 | /* UI state is saved in binary because it is quite complex (e.g., |
485 | navigation history, cached content) and depends closely on the widget | 499 | navigation history, cached content) and depends closely on the widget |
486 | tree. The data is largely not reorderable and should not be modified | 500 | tree. The data is largely not reorderable and should not be modified |
@@ -492,11 +506,11 @@ static void saveState_App_(const iApp *d) { | |||
492 | /* Begin with window state. */ { | 506 | /* Begin with window state. */ { |
493 | writeData_File(f, magicWindow_App_, 4); | 507 | writeData_File(f, magicWindow_App_, 4); |
494 | writeU32_File(f, win->splitMode); | 508 | writeU32_File(f, win->splitMode); |
495 | writeU32_File(f, win->keyRoot == win->roots[0] ? 0 : 1); | 509 | writeU32_File(f, win->base.keyRoot == win->base.roots[0] ? 0 : 1); |
496 | } | 510 | } |
497 | /* State of UI elements. */ { | 511 | /* State of UI elements. */ { |
498 | iForIndices(i, win->roots) { | 512 | iForIndices(i, win->base.roots) { |
499 | const iRoot *root = win->roots[i]; | 513 | const iRoot *root = win->base.roots[i]; |
500 | if (root) { | 514 | if (root) { |
501 | writeData_File(f, magicSidebar_App_, 4); | 515 | writeData_File(f, magicSidebar_App_, 4); |
502 | const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 516 | const iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
@@ -509,6 +523,8 @@ static void saveState_App_(const iApp *d) { | |||
509 | (mode_SidebarWidget(sidebar2) << 4)); | 523 | (mode_SidebarWidget(sidebar2) << 4)); |
510 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); | 524 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar)); |
511 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); | 525 | writef_Stream(stream_File(f), width_SidebarWidget(sidebar2)); |
526 | serialize_IntSet(closedFolders_SidebarWidget(sidebar), stream_File(f)); | ||
527 | serialize_IntSet(closedFolders_SidebarWidget(sidebar2), stream_File(f)); | ||
512 | } | 528 | } |
513 | } | 529 | } |
514 | } | 530 | } |
@@ -517,7 +533,7 @@ static void saveState_App_(const iApp *d) { | |||
517 | const iWidget *widget = constAs_Widget(i.object); | 533 | const iWidget *widget = constAs_Widget(i.object); |
518 | writeData_File(f, magicTabDocument_App_, 4); | 534 | writeData_File(f, magicTabDocument_App_, 4); |
519 | int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); | 535 | int8_t flags = (document_Root(widget->root) == i.object ? current_DocumentStateFlag : 0); |
520 | if (widget->root == win->roots[1]) { | 536 | if (widget->root == win->base.roots[1]) { |
521 | flags |= rootIndex1_DocumentStateFlag; | 537 | flags |= rootIndex1_DocumentStateFlag; |
522 | } | 538 | } |
523 | write8_File(f, flags); | 539 | write8_File(f, flags); |
@@ -667,6 +683,8 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
667 | defineValues_CommandLine(&d->args, "help", 0); | 683 | defineValues_CommandLine(&d->args, "help", 0); |
668 | defineValues_CommandLine(&d->args, listTabUrls_CommandLineOption, 0); | 684 | defineValues_CommandLine(&d->args, listTabUrls_CommandLineOption, 0); |
669 | defineValues_CommandLine(&d->args, openUrlOrSearch_CommandLineOption, 1); | 685 | defineValues_CommandLine(&d->args, openUrlOrSearch_CommandLineOption, 1); |
686 | defineValues_CommandLine(&d->args, windowWidth_CommandLineOption, 1); | ||
687 | defineValues_CommandLine(&d->args, windowHeight_CommandLineOption, 1); | ||
670 | defineValuesN_CommandLine(&d->args, "new-tab", 0, 1); | 688 | defineValuesN_CommandLine(&d->args, "new-tab", 0, 1); |
671 | defineValues_CommandLine(&d->args, "tab-url", 0); | 689 | defineValues_CommandLine(&d->args, "tab-url", 0); |
672 | defineValues_CommandLine(&d->args, "sw", 0); | 690 | defineValues_CommandLine(&d->args, "sw", 0); |
@@ -756,7 +774,7 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
756 | mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32()); | 774 | mulfv_I2(&d->initialWindowRect.size, desktopDPI_Win32()); |
757 | #endif | 775 | #endif |
758 | #if defined (iPlatformLinux) | 776 | #if defined (iPlatformLinux) |
759 | /* Scale by the primary (?) monitor DPI. */ | 777 | /* Scale by the primary (?) monitor DPI. */ |
760 | if (isRunningUnderWindowSystem_App()) { | 778 | if (isRunningUnderWindowSystem_App()) { |
761 | float vdpi; | 779 | float vdpi; |
762 | SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); | 780 | SDL_GetDisplayDPI(0, NULL, NULL, &vdpi); |
@@ -786,7 +804,18 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
786 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ | 804 | setThemePalette_Color(d->prefs.theme); /* default UI colors */ |
787 | loadPrefs_App_(d); | 805 | loadPrefs_App_(d); |
788 | load_Keys(dataDir_App_()); | 806 | load_Keys(dataDir_App_()); |
789 | d->window = new_Window(d->initialWindowRect); | 807 | /* See if the user wants to override the window size. */ { |
808 | iCommandLineArg *arg = iClob(checkArgument_CommandLine(&d->args, windowWidth_CommandLineOption)); | ||
809 | if (arg) { | ||
810 | d->initialWindowRect.size.x = toInt_String(value_CommandLineArg(arg, 0)); | ||
811 | } | ||
812 | arg = iClob(checkArgument_CommandLine(&d->args, windowHeight_CommandLineOption)); | ||
813 | if (arg) { | ||
814 | d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); | ||
815 | } | ||
816 | } | ||
817 | init_PtrArray(&d->popupWindows); | ||
818 | d->window = new_MainWindow(d->initialWindowRect); | ||
790 | load_Visited(d->visited, dataDir_App_()); | 819 | load_Visited(d->visited, dataDir_App_()); |
791 | load_Bookmarks(d->bookmarks, dataDir_App_()); | 820 | load_Bookmarks(d->bookmarks, dataDir_App_()); |
792 | load_MimeHooks(d->mimehooks, dataDir_App_()); | 821 | load_MimeHooks(d->mimehooks, dataDir_App_()); |
@@ -833,11 +862,16 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
833 | fetchRemote_Bookmarks(d->bookmarks); | 862 | fetchRemote_Bookmarks(d->bookmarks); |
834 | if (deviceType_App() != desktop_AppDeviceType) { | 863 | if (deviceType_App() != desktop_AppDeviceType) { |
835 | /* HACK: Force a resize so widgets update their state. */ | 864 | /* HACK: Force a resize so widgets update their state. */ |
836 | resize_Window(d->window, -1, -1); | 865 | resize_MainWindow(d->window, -1, -1); |
837 | } | 866 | } |
838 | } | 867 | } |
839 | 868 | ||
840 | static void deinit_App(iApp *d) { | 869 | static void deinit_App(iApp *d) { |
870 | iReverseForEach(PtrArray, i, &d->popupWindows) { | ||
871 | delete_Window(i.ptr); | ||
872 | } | ||
873 | iAssert(isEmpty_PtrArray(&d->popupWindows)); | ||
874 | deinit_PtrArray(&d->popupWindows); | ||
841 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 875 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
842 | SDL_RemoveTimer(d->sleepTimer); | 876 | SDL_RemoveTimer(d->sleepTimer); |
843 | #endif | 877 | #endif |
@@ -856,7 +890,7 @@ static void deinit_App(iApp *d) { | |||
856 | delete_GmCerts(d->certs); | 890 | delete_GmCerts(d->certs); |
857 | save_MimeHooks(d->mimehooks); | 891 | save_MimeHooks(d->mimehooks); |
858 | delete_MimeHooks(d->mimehooks); | 892 | delete_MimeHooks(d->mimehooks); |
859 | delete_Window(d->window); | 893 | delete_MainWindow(d->window); |
860 | d->window = NULL; | 894 | d->window = NULL; |
861 | deinit_CommandLine(&d->args); | 895 | deinit_CommandLine(&d->args); |
862 | iRelease(d->launchCommands); | 896 | iRelease(d->launchCommands); |
@@ -1039,7 +1073,7 @@ void trimMemory_App(void) { | |||
1039 | init_ObjectListIterator(&i, docs); | 1073 | init_ObjectListIterator(&i, docs); |
1040 | } | 1074 | } |
1041 | } | 1075 | } |
1042 | iRelease(docs); | 1076 | iRelease(docs); |
1043 | } | 1077 | } |
1044 | 1078 | ||
1045 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | 1079 | iLocalDef iBool isWaitingAllowed_App_(iApp *d) { |
@@ -1054,11 +1088,6 @@ iLocalDef iBool isWaitingAllowed_App_(iApp *d) { | |||
1054 | return iFalse; | 1088 | return iFalse; |
1055 | } | 1089 | } |
1056 | #endif | 1090 | #endif |
1057 | #if defined (iPlatformMobile) | ||
1058 | if (!isFinished_Anim(&d->window->rootOffset)) { | ||
1059 | return iFalse; | ||
1060 | } | ||
1061 | #endif | ||
1062 | return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); | 1091 | return !value_Atomic(&d->pendingRefresh) && isEmpty_SortedArray(&d->tickers); |
1063 | } | 1092 | } |
1064 | 1093 | ||
@@ -1076,6 +1105,15 @@ static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev | |||
1076 | return SDL_PollEvent(event); | 1105 | return SDL_PollEvent(event); |
1077 | } | 1106 | } |
1078 | 1107 | ||
1108 | static const iPtrArray *listWindows_App_(const iApp *d) { | ||
1109 | iPtrArray *list = collectNew_PtrArray(); | ||
1110 | iReverseConstForEach(PtrArray, i, &d->popupWindows) { | ||
1111 | pushBack_PtrArray(list, i.ptr); | ||
1112 | } | ||
1113 | pushBack_PtrArray(list, d->window); | ||
1114 | return list; | ||
1115 | } | ||
1116 | |||
1079 | void processEvents_App(enum iAppEventMode eventMode) { | 1117 | void processEvents_App(enum iAppEventMode eventMode) { |
1080 | iApp *d = &app_; | 1118 | iApp *d = &app_; |
1081 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ | 1119 | iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ |
@@ -1100,7 +1138,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1100 | clearCache_App_(); | 1138 | clearCache_App_(); |
1101 | break; | 1139 | break; |
1102 | case SDL_APP_WILLENTERFOREGROUND: | 1140 | case SDL_APP_WILLENTERFOREGROUND: |
1103 | invalidate_Window(d->window); | 1141 | invalidate_Window(as_Window(d->window)); |
1104 | break; | 1142 | break; |
1105 | case SDL_APP_DIDENTERFOREGROUND: | 1143 | case SDL_APP_DIDENTERFOREGROUND: |
1106 | gotEvents = iTrue; | 1144 | gotEvents = iTrue; |
@@ -1115,17 +1153,17 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1115 | #if defined (iPlatformAppleMobile) | 1153 | #if defined (iPlatformAppleMobile) |
1116 | updateNowPlayingInfo_iOS(); | 1154 | updateNowPlayingInfo_iOS(); |
1117 | #endif | 1155 | #endif |
1118 | setFreezeDraw_Window(d->window, iTrue); | 1156 | setFreezeDraw_MainWindow(d->window, iTrue); |
1119 | savePrefs_App_(d); | 1157 | savePrefs_App_(d); |
1120 | saveState_App_(d); | 1158 | saveState_App_(d); |
1121 | break; | 1159 | break; |
1122 | case SDL_APP_TERMINATING: | 1160 | case SDL_APP_TERMINATING: |
1123 | setFreezeDraw_Window(d->window, iTrue); | 1161 | setFreezeDraw_MainWindow(d->window, iTrue); |
1124 | savePrefs_App_(d); | 1162 | savePrefs_App_(d); |
1125 | saveState_App_(d); | 1163 | saveState_App_(d); |
1126 | break; | 1164 | break; |
1127 | case SDL_DROPFILE: { | 1165 | case SDL_DROPFILE: { |
1128 | iBool wasUsed = processEvent_Window(d->window, &ev); | 1166 | iBool wasUsed = processEvent_Window(as_Window(d->window), &ev); |
1129 | if (!wasUsed) { | 1167 | if (!wasUsed) { |
1130 | iBool newTab = iFalse; | 1168 | iBool newTab = iFalse; |
1131 | if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { | 1169 | if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { |
@@ -1165,23 +1203,6 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1165 | } | 1203 | } |
1166 | d->isIdling = iFalse; | 1204 | d->isIdling = iFalse; |
1167 | #endif | 1205 | #endif |
1168 | if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | ||
1169 | printf("[App] rearrange\n"); | ||
1170 | resize_Window(d->window, -1, -1); | ||
1171 | iForIndices(i, d->window->roots) { | ||
1172 | if (d->window->roots[i]) { | ||
1173 | d->window->roots[i]->pendingArrange = iFalse; | ||
1174 | } | ||
1175 | } | ||
1176 | // if (ev.user.data2 == d->window->roots[0]) { | ||
1177 | // arrange_Widget(d->window->roots[0]->widget); | ||
1178 | // } | ||
1179 | // else if (d->window->roots[1]) { | ||
1180 | // arrange_Widget(d->window->roots[1]->widget); | ||
1181 | // } | ||
1182 | // postRefresh_App(); | ||
1183 | continue; | ||
1184 | } | ||
1185 | gotEvents = iTrue; | 1206 | gotEvents = iTrue; |
1186 | /* Keyboard modifier mapping. */ | 1207 | /* Keyboard modifier mapping. */ |
1187 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { | 1208 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { |
@@ -1199,8 +1220,8 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1199 | if (ev.wheel.which == 0) { | 1220 | if (ev.wheel.which == 0) { |
1200 | /* Trackpad with precise scrolling w/inertia (points). */ | 1221 | /* Trackpad with precise scrolling w/inertia (points). */ |
1201 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); | 1222 | setPerPixel_MouseWheelEvent(&ev.wheel, iTrue); |
1202 | ev.wheel.x *= -d->window->pixelRatio; | 1223 | ev.wheel.x *= -d->window->base.pixelRatio; |
1203 | ev.wheel.y *= d->window->pixelRatio; | 1224 | ev.wheel.y *= d->window->base.pixelRatio; |
1204 | /* Only scroll on one axis at a time. */ | 1225 | /* Only scroll on one axis at a time. */ |
1205 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { | 1226 | if (iAbs(ev.wheel.x) > iAbs(ev.wheel.y)) { |
1206 | ev.wheel.y = 0; | 1227 | ev.wheel.y = 0; |
@@ -1240,7 +1261,7 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1240 | } | 1261 | } |
1241 | else if (ev.type == SDL_MOUSEMOTION) { | 1262 | else if (ev.type == SDL_MOUSEMOTION) { |
1242 | if (~ev.motion.state & SDL_BUTTON(SDL_BUTTON_LEFT)) { | 1263 | if (~ev.motion.state & SDL_BUTTON(SDL_BUTTON_LEFT)) { |
1243 | continue; /* only when pressing a button */ | 1264 | continue; /* only when pressing a button */ |
1244 | } | 1265 | } |
1245 | const float xf = (d->window->pixelRatio * ev.motion.x) / (float) d->window->size.x; | 1266 | const float xf = (d->window->pixelRatio * ev.motion.x) / (float) d->window->size.x; |
1246 | const float yf = (d->window->pixelRatio * ev.motion.y) / (float) d->window->size.y; | 1267 | const float yf = (d->window->pixelRatio * ev.motion.y) / (float) d->window->size.y; |
@@ -1254,13 +1275,26 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1254 | ev.tfinger.fingerId = 0x1234; | 1275 | ev.tfinger.fingerId = 0x1234; |
1255 | ev.tfinger.pressure = 1.0f; | 1276 | ev.tfinger.pressure = 1.0f; |
1256 | ev.tfinger.timestamp = SDL_GetTicks(); | 1277 | ev.tfinger.timestamp = SDL_GetTicks(); |
1257 | ev.tfinger.touchId = SDL_TOUCH_MOUSEID; | 1278 | ev.tfinger.touchId = SDL_TOUCH_MOUSEID; |
1258 | } | 1279 | } |
1259 | } | 1280 | } |
1260 | #endif | 1281 | #endif |
1261 | iBool wasUsed = processEvent_Window(d->window, &ev); | 1282 | /* Per-window processing. */ |
1283 | iBool wasUsed = iFalse; | ||
1284 | const iPtrArray *windows = listWindows_App_(d); | ||
1285 | iConstForEach(PtrArray, iter, windows) { | ||
1286 | iWindow *window = iter.ptr; | ||
1287 | setCurrent_Window(window); | ||
1288 | window->lastHover = window->hover; | ||
1289 | wasUsed = processEvent_Window(window, &ev); | ||
1290 | if (ev.type == SDL_MOUSEMOTION || ev.type == SDL_MOUSEBUTTONDOWN) { | ||
1291 | break; | ||
1292 | } | ||
1293 | if (wasUsed) break; | ||
1294 | } | ||
1295 | setCurrent_Window(d->window); | ||
1262 | if (!wasUsed) { | 1296 | if (!wasUsed) { |
1263 | /* There may be a key bindings for this. */ | 1297 | /* There may be a key binding for this. */ |
1264 | wasUsed = processEvent_Keys(&ev); | 1298 | wasUsed = processEvent_Keys(&ev); |
1265 | } | 1299 | } |
1266 | if (!wasUsed) { | 1300 | if (!wasUsed) { |
@@ -1278,26 +1312,39 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1278 | handleCommand_MacOS(command_UserEvent(&ev)); | 1312 | handleCommand_MacOS(command_UserEvent(&ev)); |
1279 | #endif | 1313 | #endif |
1280 | if (isMetricsChange_UserEvent(&ev)) { | 1314 | if (isMetricsChange_UserEvent(&ev)) { |
1281 | iForIndices(i, d->window->roots) { | 1315 | iConstForEach(PtrArray, iter, windows) { |
1282 | iRoot *root = d->window->roots[i]; | 1316 | iWindow *window = iter.ptr; |
1283 | if (root) { | 1317 | iForIndices(i, window->roots) { |
1284 | arrange_Widget(root->widget); | 1318 | iRoot *root = window->roots[i]; |
1319 | if (root) { | ||
1320 | arrange_Widget(root->widget); | ||
1321 | } | ||
1285 | } | 1322 | } |
1286 | } | 1323 | } |
1287 | } | 1324 | } |
1288 | if (!wasUsed) { | 1325 | if (!wasUsed) { |
1289 | /* No widget handled the command, so we'll do it. */ | 1326 | /* No widget handled the command, so we'll do it. */ |
1327 | setCurrent_Window(d->window); | ||
1290 | handleCommand_App(ev.user.data1); | 1328 | handleCommand_App(ev.user.data1); |
1291 | } | 1329 | } |
1292 | /* Allocated by postCommand_Apps(). */ | 1330 | /* Allocated by postCommand_Apps(). */ |
1293 | free(ev.user.data1); | 1331 | free(ev.user.data1); |
1294 | } | 1332 | } |
1333 | /* Refresh after hover changes. */ { | ||
1334 | iConstForEach(PtrArray, iter, windows) { | ||
1335 | iWindow *window = iter.ptr; | ||
1336 | if (window->lastHover != window->hover) { | ||
1337 | refresh_Widget(window->lastHover); | ||
1338 | refresh_Widget(window->hover); | ||
1339 | } | ||
1340 | } | ||
1341 | } | ||
1295 | break; | 1342 | break; |
1296 | } | 1343 | } |
1297 | } | 1344 | } |
1298 | } | 1345 | } |
1299 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1346 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1300 | if (d->isIdling && !gotEvents && isFinished_Anim(&d->window->rootOffset)) { | 1347 | if (d->isIdling && !gotEvents) { |
1301 | /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we | 1348 | /* This is where we spend most of our time when idle. 60 Hz still quite a lot but we |
1302 | can't wait too long after the user tries to interact again with the app. In any | 1349 | can't wait too long after the user tries to interact again with the app. In any |
1303 | case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ | 1350 | case, on macOS SDL_WaitEvent() seems to use 10x more CPU time than sleeping. */ |
@@ -1339,22 +1386,23 @@ static int resizeWatcher_(void *user, SDL_Event *event) { | |||
1339 | if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { | 1386 | if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { |
1340 | const SDL_WindowEvent *winev = &event->window; | 1387 | const SDL_WindowEvent *winev = &event->window; |
1341 | #if defined (iPlatformMsys) | 1388 | #if defined (iPlatformMsys) |
1342 | resetFonts_Text(); { | 1389 | resetFonts_Text(text_Window(d->window)); { |
1343 | SDL_Event u = { .type = SDL_USEREVENT }; | 1390 | SDL_Event u = { .type = SDL_USEREVENT }; |
1344 | u.user.code = command_UserEventCode; | 1391 | u.user.code = command_UserEventCode; |
1345 | u.user.data1 = strdup("theme.changed auto:1"); | 1392 | u.user.data1 = strdup("theme.changed auto:1"); |
1346 | dispatchEvent_Window(d->window, &u); | 1393 | dispatchEvent_Window(as_Window(d->window), &u); |
1347 | } | 1394 | } |
1348 | #endif | 1395 | #endif |
1349 | drawWhileResizing_Window(d->window, winev->data1, winev->data2); | 1396 | drawWhileResizing_MainWindow(d->window, winev->data1, winev->data2); |
1350 | } | 1397 | } |
1351 | return 0; | 1398 | return 0; |
1352 | } | 1399 | } |
1353 | 1400 | ||
1354 | static int run_App_(iApp *d) { | 1401 | static int run_App_(iApp *d) { |
1355 | iForIndices(i, d->window->roots) { | 1402 | /* Initial arrangement. */ |
1356 | if (d->window->roots[i]) { | 1403 | iForIndices(i, d->window->base.roots) { |
1357 | arrange_Widget(d->window->roots[i]->widget); | 1404 | if (d->window->base.roots[i]) { |
1405 | arrange_Widget(d->window->base.roots[i]->widget); | ||
1358 | } | 1406 | } |
1359 | } | 1407 | } |
1360 | d->isRunning = iTrue; | 1408 | d->isRunning = iTrue; |
@@ -1368,7 +1416,7 @@ static int run_App_(iApp *d) { | |||
1368 | runTickers_App_(d); | 1416 | runTickers_App_(d); |
1369 | refresh_App(); | 1417 | refresh_App(); |
1370 | /* Change the widget tree while we are not iterating through it. */ | 1418 | /* Change the widget tree while we are not iterating through it. */ |
1371 | checkPendingSplit_Window(d->window); | 1419 | checkPendingSplit_MainWindow(d->window); |
1372 | recycle_Garbage(); | 1420 | recycle_Garbage(); |
1373 | } | 1421 | } |
1374 | SDL_DelEventWatch(resizeWatcher_, d); | 1422 | SDL_DelEventWatch(resizeWatcher_, d); |
@@ -1377,28 +1425,46 @@ static int run_App_(iApp *d) { | |||
1377 | 1425 | ||
1378 | void refresh_App(void) { | 1426 | void refresh_App(void) { |
1379 | iApp *d = &app_; | 1427 | iApp *d = &app_; |
1380 | iForIndices(i, d->window->roots) { | ||
1381 | iRoot *root = d->window->roots[i]; | ||
1382 | if (root) { | ||
1383 | destroyPending_Root(root); | ||
1384 | } | ||
1385 | } | ||
1386 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) | 1428 | #if defined (LAGRANGE_ENABLE_IDLE_SLEEP) |
1387 | if (d->warmupFrames == 0 && d->isIdling) { | 1429 | if (d->warmupFrames == 0 && d->isIdling) { |
1388 | return; | 1430 | return; |
1389 | } | 1431 | } |
1390 | #endif | 1432 | #endif |
1433 | const iPtrArray *windows = listWindows_App_(d); | ||
1434 | /* Destroy pending widgets. */ { | ||
1435 | iConstForEach(PtrArray, j, windows) { | ||
1436 | iWindow *win = j.ptr; | ||
1437 | setCurrent_Window(win); | ||
1438 | iForIndices(i, win->roots) { | ||
1439 | iRoot *root = win->roots[i]; | ||
1440 | if (root) { | ||
1441 | destroyPending_Root(root); | ||
1442 | } | ||
1443 | } | ||
1444 | } | ||
1445 | } | ||
1446 | /* TODO: Pending refresh is window-specific. */ | ||
1391 | if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { | 1447 | if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { |
1392 | /* Refreshing wasn't pending. */ | 1448 | return; |
1393 | if (isFinished_Anim(&d->window->rootOffset)) { | 1449 | } |
1394 | return; | 1450 | /* Draw each window. */ { |
1451 | iConstForEach(PtrArray, j, windows) { | ||
1452 | iWindow *win = j.ptr; | ||
1453 | setCurrent_Window(win); | ||
1454 | switch (win->type) { | ||
1455 | case main_WindowType: | ||
1456 | // iTime draw; | ||
1457 | // initCurrent_Time(&draw); | ||
1458 | draw_MainWindow(as_MainWindow(win)); | ||
1459 | // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); | ||
1460 | // fflush(stdout); | ||
1461 | break; | ||
1462 | default: | ||
1463 | draw_Window(win); | ||
1464 | break; | ||
1465 | } | ||
1395 | } | 1466 | } |
1396 | } | 1467 | } |
1397 | // iTime draw; | ||
1398 | // initCurrent_Time(&draw); | ||
1399 | draw_Window(d->window); | ||
1400 | // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); | ||
1401 | // fflush(stdout); | ||
1402 | if (d->warmupFrames > 0) { | 1468 | if (d->warmupFrames > 0) { |
1403 | d->warmupFrames--; | 1469 | d->warmupFrames--; |
1404 | } | 1470 | } |
@@ -1471,12 +1537,6 @@ void postRefresh_App(void) { | |||
1471 | } | 1537 | } |
1472 | } | 1538 | } |
1473 | 1539 | ||
1474 | void postImmediateRefresh_App(void) { | ||
1475 | SDL_Event ev = { .type = SDL_USEREVENT }; | ||
1476 | ev.user.code = immediateRefresh_UserEventCode; | ||
1477 | SDL_PushEvent(&ev); | ||
1478 | } | ||
1479 | |||
1480 | void postCommand_Root(iRoot *d, const char *command) { | 1540 | void postCommand_Root(iRoot *d, const char *command) { |
1481 | iAssert(command); | 1541 | iAssert(command); |
1482 | if (strlen(command) == 0) { | 1542 | if (strlen(command) == 0) { |
@@ -1532,7 +1592,7 @@ void postCommandf_App(const char *command, ...) { | |||
1532 | } | 1592 | } |
1533 | 1593 | ||
1534 | void rootOrder_App(iRoot *roots[2]) { | 1594 | void rootOrder_App(iRoot *roots[2]) { |
1535 | const iWindow *win = app_.window; | 1595 | const iWindow *win = get_Window(); |
1536 | roots[0] = win->keyRoot; | 1596 | roots[0] = win->keyRoot; |
1537 | roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]); | 1597 | roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]); |
1538 | } | 1598 | } |
@@ -1569,6 +1629,16 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) { | |||
1569 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); | 1629 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); |
1570 | } | 1630 | } |
1571 | 1631 | ||
1632 | void addPopup_App(iWindow *popup) { | ||
1633 | iApp *d = &app_; | ||
1634 | pushBack_PtrArray(&d->popupWindows, popup); | ||
1635 | } | ||
1636 | |||
1637 | void removePopup_App(iWindow *popup) { | ||
1638 | iApp *d = &app_; | ||
1639 | removeOne_PtrArray(&d->popupWindows, popup); | ||
1640 | } | ||
1641 | |||
1572 | iMimeHooks *mimeHooks_App(void) { | 1642 | iMimeHooks *mimeHooks_App(void) { |
1573 | return app_.mimehooks; | 1643 | return app_.mimehooks; |
1574 | } | 1644 | } |
@@ -1583,7 +1653,11 @@ iBool isLandscape_App(void) { | |||
1583 | } | 1653 | } |
1584 | 1654 | ||
1585 | enum iAppDeviceType deviceType_App(void) { | 1655 | enum iAppDeviceType deviceType_App(void) { |
1586 | #if defined (iPlatformAppleMobile) | 1656 | #if defined (iPlatformMobilePhone) |
1657 | return phone_AppDeviceType; | ||
1658 | #elif defined (iPlatformMobileTablet) | ||
1659 | return tablet_AppDeviceType; | ||
1660 | #elif defined (iPlatformAppleMobile) | ||
1587 | return isPhone_iOS() ? phone_AppDeviceType : tablet_AppDeviceType; | 1661 | return isPhone_iOS() ? phone_AppDeviceType : tablet_AppDeviceType; |
1588 | #else | 1662 | #else |
1589 | return desktop_AppDeviceType; | 1663 | return desktop_AppDeviceType; |
@@ -1636,28 +1710,20 @@ static void updateScrollSpeedButtons_(iWidget *d, enum iScrollType type, const i | |||
1636 | } | 1710 | } |
1637 | } | 1711 | } |
1638 | 1712 | ||
1639 | static void updateDropdownSelection_(iLabelWidget *dropButton, const char *selectedCommand) { | ||
1640 | iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu"); | ||
1641 | iForEach(ObjectList, i, children_Widget(menu)) { | ||
1642 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
1643 | iLabelWidget *item = i.object; | ||
1644 | const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); | ||
1645 | setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); | ||
1646 | if (isSelected) { | ||
1647 | updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); | ||
1648 | } | ||
1649 | } | ||
1650 | } | ||
1651 | } | ||
1652 | |||
1653 | static void updateColorThemeButton_(iLabelWidget *button, int theme) { | 1713 | static void updateColorThemeButton_(iLabelWidget *button, int theme) { |
1714 | /* TODO: These three functions are all the same? Cleanup? */ | ||
1654 | if (!button) return; | 1715 | if (!button) return; |
1655 | updateDropdownSelection_(button, format_CStr(".set arg:%d", theme)); | 1716 | updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", theme)); |
1656 | } | 1717 | } |
1657 | 1718 | ||
1658 | static void updateFontButton_(iLabelWidget *button, int font) { | 1719 | static void updateFontButton_(iLabelWidget *button, int font) { |
1659 | if (!button) return; | 1720 | if (!button) return; |
1660 | updateDropdownSelection_(button, format_CStr(".set arg:%d", font)); | 1721 | updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", font)); |
1722 | } | ||
1723 | |||
1724 | static void updateImageStyleButton_(iLabelWidget *button, int style) { | ||
1725 | if (!button) return; | ||
1726 | updateDropdownSelection_LabelWidget(button, format_CStr(".set arg:%d", style)); | ||
1661 | } | 1727 | } |
1662 | 1728 | ||
1663 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | 1729 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { |
@@ -1710,8 +1776,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1710 | return iTrue; | 1776 | return iTrue; |
1711 | } | 1777 | } |
1712 | else if (equal_Command(cmd, "uilang")) { | 1778 | else if (equal_Command(cmd, "uilang")) { |
1713 | updateDropdownSelection_(findChild_Widget(d, "prefs.uilang"), | 1779 | updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.uilang"), |
1714 | cstr_String(string_Command(cmd, "id"))); | 1780 | cstr_String(string_Command(cmd, "id"))); |
1715 | return iFalse; | 1781 | return iFalse; |
1716 | } | 1782 | } |
1717 | else if (equal_Command(cmd, "quoteicon.set")) { | 1783 | else if (equal_Command(cmd, "quoteicon.set")) { |
@@ -1721,8 +1787,8 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1721 | return iFalse; | 1787 | return iFalse; |
1722 | } | 1788 | } |
1723 | else if (equal_Command(cmd, "returnkey.set")) { | 1789 | else if (equal_Command(cmd, "returnkey.set")) { |
1724 | updateDropdownSelection_(findChild_Widget(d, "prefs.returnkey"), | 1790 | updateDropdownSelection_LabelWidget(findChild_Widget(d, "prefs.returnkey"), |
1725 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); | 1791 | format_CStr("returnkey.set arg:%d", arg_Command(cmd))); |
1726 | return iFalse; | 1792 | return iFalse; |
1727 | } | 1793 | } |
1728 | else if (equal_Command(cmd, "pinsplit.set")) { | 1794 | else if (equal_Command(cmd, "pinsplit.set")) { |
@@ -1741,6 +1807,10 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1741 | updateColorThemeButton_(findChild_Widget(d, "prefs.doctheme.light"), arg_Command(cmd)); | 1807 | updateColorThemeButton_(findChild_Widget(d, "prefs.doctheme.light"), arg_Command(cmd)); |
1742 | return iFalse; | 1808 | return iFalse; |
1743 | } | 1809 | } |
1810 | else if (equal_Command(cmd, "imagestyle.set")) { | ||
1811 | updateImageStyleButton_(findChild_Widget(d, "prefs.imagestyle"), arg_Command(cmd)); | ||
1812 | return iFalse; | ||
1813 | } | ||
1744 | else if (equal_Command(cmd, "font.set")) { | 1814 | else if (equal_Command(cmd, "font.set")) { |
1745 | updateFontButton_(findChild_Widget(d, "prefs.font"), arg_Command(cmd)); | 1815 | updateFontButton_(findChild_Widget(d, "prefs.font"), arg_Command(cmd)); |
1746 | return iFalse; | 1816 | return iFalse; |
@@ -1749,7 +1819,7 @@ static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | |||
1749 | updateFontButton_(findChild_Widget(d, "prefs.headingfont"), arg_Command(cmd)); | 1819 | updateFontButton_(findChild_Widget(d, "prefs.headingfont"), arg_Command(cmd)); |
1750 | return iFalse; | 1820 | return iFalse; |
1751 | } | 1821 | } |
1752 | else if (startsWith_CStr(cmd, "input.ended id:prefs.linespacing")) { | 1822 | else if (startsWith_CStr(cmd, "input.ended id:prefs.linespacing")) { |
1753 | /* Apply line spacing changes immediately. */ | 1823 | /* Apply line spacing changes immediately. */ |
1754 | const iInputWidget *lineSpacing = findWidget_App("prefs.linespacing"); | 1824 | const iInputWidget *lineSpacing = findWidget_App("prefs.linespacing"); |
1755 | postCommandf_App("linespacing.set arg:%f", toFloat_String(text_InputWidget(lineSpacing))); | 1825 | postCommandf_App("linespacing.set arg:%f", toFloat_String(text_InputWidget(lineSpacing))); |
@@ -1822,7 +1892,10 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe | |||
1822 | static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { | 1892 | static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { |
1823 | iApp *d = &app_; | 1893 | iApp *d = &app_; |
1824 | if (equal_Command(cmd, "ident.showmore")) { | 1894 | if (equal_Command(cmd, "ident.showmore")) { |
1825 | iForEach(ObjectList, i, children_Widget(findChild_Widget(dlg, "headings"))) { | 1895 | iForEach(ObjectList, |
1896 | i, | ||
1897 | children_Widget(findChild_Widget( | ||
1898 | dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) { | ||
1826 | if (flags_Widget(i.object) & collapse_WidgetFlag) { | 1899 | if (flags_Widget(i.object) & collapse_WidgetFlag) { |
1827 | setFlags_Widget(i.object, hidden_WidgetFlag, iFalse); | 1900 | setFlags_Widget(i.object, hidden_WidgetFlag, iFalse); |
1828 | } | 1901 | } |
@@ -1832,10 +1905,9 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { | |||
1832 | setFlags_Widget(j.object, hidden_WidgetFlag, iFalse); | 1905 | setFlags_Widget(j.object, hidden_WidgetFlag, iFalse); |
1833 | } | 1906 | } |
1834 | } | 1907 | } |
1835 | setFlags_Widget(child_Widget(findChild_Widget(dlg, "dialogbuttons"), 0), disabled_WidgetFlag, | 1908 | setFlags_Widget(pointer_Command(cmd), disabled_WidgetFlag, iTrue); |
1836 | iTrue); | ||
1837 | arrange_Widget(dlg); | 1909 | arrange_Widget(dlg); |
1838 | refresh_Widget(dlg); | 1910 | refresh_Widget(dlg); |
1839 | return iTrue; | 1911 | return iTrue; |
1840 | } | 1912 | } |
1841 | if (equal_Command(cmd, "ident.scope")) { | 1913 | if (equal_Command(cmd, "ident.scope")) { |
@@ -1843,6 +1915,7 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { | |||
1843 | setText_LabelWidget(scope, | 1915 | setText_LabelWidget(scope, |
1844 | text_LabelWidget(child_Widget( | 1916 | text_LabelWidget(child_Widget( |
1845 | findChild_Widget(as_Widget(scope), "menu"), arg_Command(cmd)))); | 1917 | findChild_Widget(as_Widget(scope), "menu"), arg_Command(cmd)))); |
1918 | arrange_Widget(findWidget_App("ident")); | ||
1846 | return iTrue; | 1919 | return iTrue; |
1847 | } | 1920 | } |
1848 | if (equal_Command(cmd, "ident.temp.changed")) { | 1921 | if (equal_Command(cmd, "ident.temp.changed")) { |
@@ -1963,9 +2036,16 @@ const iString *searchQueryUrl_App(const iString *queryStringUnescaped) { | |||
1963 | return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped)); | 2036 | return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped)); |
1964 | } | 2037 | } |
1965 | 2038 | ||
2039 | static void resetFonts_App_(iApp *d) { | ||
2040 | iConstForEach(PtrArray, win, listWindows_App_(d)) { | ||
2041 | resetFonts_Text(text_Window(win.ptr)); | ||
2042 | } | ||
2043 | } | ||
2044 | |||
1966 | iBool handleCommand_App(const char *cmd) { | 2045 | iBool handleCommand_App(const char *cmd) { |
1967 | iApp *d = &app_; | 2046 | iApp *d = &app_; |
1968 | const iBool isFrozen = !d->window || d->window->isDrawFrozen; | 2047 | const iBool isFrozen = !d->window || d->window->isDrawFrozen; |
2048 | /* TODO: Maybe break this up a little bit? There's a very long list of ifs here. */ | ||
1969 | if (equal_Command(cmd, "config.error")) { | 2049 | if (equal_Command(cmd, "config.error")) { |
1970 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", | 2050 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", |
1971 | format_CStr("Error in config file: %s\n" | 2051 | format_CStr("Error in config file: %s\n" |
@@ -1997,13 +2077,13 @@ iBool handleCommand_App(const char *cmd) { | |||
1997 | } | 2077 | } |
1998 | else if (equal_Command(cmd, "ui.split")) { | 2078 | else if (equal_Command(cmd, "ui.split")) { |
1999 | if (argLabel_Command(cmd, "swap")) { | 2079 | if (argLabel_Command(cmd, "swap")) { |
2000 | swapRoots_Window(d->window); | 2080 | swapRoots_MainWindow(d->window); |
2001 | return iTrue; | 2081 | return iTrue; |
2002 | } | 2082 | } |
2003 | d->window->pendingSplitMode = | 2083 | d->window->pendingSplitMode = |
2004 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); | 2084 | (argLabel_Command(cmd, "axis") ? vertical_WindowSplit : 0) | (arg_Command(cmd) << 1); |
2005 | const char *url = suffixPtr_Command(cmd, "url"); | 2085 | const char *url = suffixPtr_Command(cmd, "url"); |
2006 | setCStr_String(get_Window()->pendingSplitUrl, url ? url : ""); | 2086 | setCStr_String(d->window->pendingSplitUrl, url ? url : ""); |
2007 | postRefresh_App(); | 2087 | postRefresh_App(); |
2008 | return iTrue; | 2088 | return iTrue; |
2009 | } | 2089 | } |
@@ -2017,33 +2097,33 @@ iBool handleCommand_App(const char *cmd) { | |||
2017 | } | 2097 | } |
2018 | else if (equal_Command(cmd, "window.maximize")) { | 2098 | else if (equal_Command(cmd, "window.maximize")) { |
2019 | if (!argLabel_Command(cmd, "toggle")) { | 2099 | if (!argLabel_Command(cmd, "toggle")) { |
2020 | setSnap_Window(d->window, maximized_WindowSnap); | 2100 | setSnap_MainWindow(d->window, maximized_WindowSnap); |
2021 | } | 2101 | } |
2022 | else { | 2102 | else { |
2023 | setSnap_Window(d->window, snap_Window(d->window) == maximized_WindowSnap ? 0 : | 2103 | setSnap_MainWindow(d->window, snap_MainWindow(d->window) == maximized_WindowSnap ? 0 : |
2024 | maximized_WindowSnap); | 2104 | maximized_WindowSnap); |
2025 | } | 2105 | } |
2026 | return iTrue; | 2106 | return iTrue; |
2027 | } | 2107 | } |
2028 | else if (equal_Command(cmd, "window.fullscreen")) { | 2108 | else if (equal_Command(cmd, "window.fullscreen")) { |
2029 | const iBool wasFull = snap_Window(d->window) == fullscreen_WindowSnap; | 2109 | const iBool wasFull = snap_MainWindow(d->window) == fullscreen_WindowSnap; |
2030 | setSnap_Window(d->window, wasFull ? 0 : fullscreen_WindowSnap); | 2110 | setSnap_MainWindow(d->window, wasFull ? 0 : fullscreen_WindowSnap); |
2031 | postCommandf_App("window.fullscreen.changed arg:%d", !wasFull); | 2111 | postCommandf_App("window.fullscreen.changed arg:%d", !wasFull); |
2032 | return iTrue; | 2112 | return iTrue; |
2033 | } | 2113 | } |
2034 | else if (equal_Command(cmd, "font.reset")) { | 2114 | else if (equal_Command(cmd, "font.reset")) { |
2035 | resetFonts_Text(); | 2115 | resetFonts_App_(d); |
2036 | return iTrue; | 2116 | return iTrue; |
2037 | } | 2117 | } |
2038 | else if (equal_Command(cmd, "font.user")) { | 2118 | else if (equal_Command(cmd, "font.user")) { |
2039 | const char *path = suffixPtr_Command(cmd, "path"); | 2119 | const char *path = suffixPtr_Command(cmd, "path"); |
2040 | if (cmp_String(&d->prefs.symbolFontPath, path)) { | 2120 | if (cmp_String(&d->prefs.symbolFontPath, path)) { |
2041 | if (!isFrozen) { | 2121 | if (!isFrozen) { |
2042 | setFreezeDraw_Window(get_Window(), iTrue); | 2122 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); |
2043 | } | 2123 | } |
2044 | setCStr_String(&d->prefs.symbolFontPath, path); | 2124 | setCStr_String(&d->prefs.symbolFontPath, path); |
2045 | loadUserFonts_Text(); | 2125 | loadUserFonts_Text(); |
2046 | resetFonts_Text(); | 2126 | resetFonts_App_(d); |
2047 | if (!isFrozen) { | 2127 | if (!isFrozen) { |
2048 | postCommand_App("font.changed"); | 2128 | postCommand_App("font.changed"); |
2049 | postCommand_App("window.unfreeze"); | 2129 | postCommand_App("window.unfreeze"); |
@@ -2053,10 +2133,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2053 | } | 2133 | } |
2054 | else if (equal_Command(cmd, "font.set")) { | 2134 | else if (equal_Command(cmd, "font.set")) { |
2055 | if (!isFrozen) { | 2135 | if (!isFrozen) { |
2056 | setFreezeDraw_Window(get_Window(), iTrue); | 2136 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); |
2057 | } | 2137 | } |
2058 | d->prefs.font = arg_Command(cmd); | 2138 | d->prefs.font = arg_Command(cmd); |
2059 | setContentFont_Text(d->prefs.font); | 2139 | setContentFont_Text(text_Window(d->window), d->prefs.font); |
2060 | if (!isFrozen) { | 2140 | if (!isFrozen) { |
2061 | postCommand_App("font.changed"); | 2141 | postCommand_App("font.changed"); |
2062 | postCommand_App("window.unfreeze"); | 2142 | postCommand_App("window.unfreeze"); |
@@ -2065,10 +2145,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2065 | } | 2145 | } |
2066 | else if (equal_Command(cmd, "headingfont.set")) { | 2146 | else if (equal_Command(cmd, "headingfont.set")) { |
2067 | if (!isFrozen) { | 2147 | if (!isFrozen) { |
2068 | setFreezeDraw_Window(get_Window(), iTrue); | 2148 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); |
2069 | } | 2149 | } |
2070 | d->prefs.headingFont = arg_Command(cmd); | 2150 | d->prefs.headingFont = arg_Command(cmd); |
2071 | setHeadingFont_Text(d->prefs.headingFont); | 2151 | setHeadingFont_Text(text_Window(d->window), d->prefs.headingFont); |
2072 | if (!isFrozen) { | 2152 | if (!isFrozen) { |
2073 | postCommand_App("font.changed"); | 2153 | postCommand_App("font.changed"); |
2074 | postCommand_App("window.unfreeze"); | 2154 | postCommand_App("window.unfreeze"); |
@@ -2077,10 +2157,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2077 | } | 2157 | } |
2078 | else if (equal_Command(cmd, "zoom.set")) { | 2158 | else if (equal_Command(cmd, "zoom.set")) { |
2079 | if (!isFrozen) { | 2159 | if (!isFrozen) { |
2080 | setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ | 2160 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ |
2081 | } | 2161 | } |
2082 | d->prefs.zoomPercent = arg_Command(cmd); | 2162 | d->prefs.zoomPercent = arg_Command(cmd); |
2083 | setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); | 2163 | setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); |
2084 | if (!isFrozen) { | 2164 | if (!isFrozen) { |
2085 | postCommand_App("font.changed"); | 2165 | postCommand_App("font.changed"); |
2086 | postCommand_App("window.unfreeze"); | 2166 | postCommand_App("window.unfreeze"); |
@@ -2089,14 +2169,14 @@ iBool handleCommand_App(const char *cmd) { | |||
2089 | } | 2169 | } |
2090 | else if (equal_Command(cmd, "zoom.delta")) { | 2170 | else if (equal_Command(cmd, "zoom.delta")) { |
2091 | if (!isFrozen) { | 2171 | if (!isFrozen) { |
2092 | setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ | 2172 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */ |
2093 | } | 2173 | } |
2094 | int delta = arg_Command(cmd); | 2174 | int delta = arg_Command(cmd); |
2095 | if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) { | 2175 | if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) { |
2096 | delta /= 2; | 2176 | delta /= 2; |
2097 | } | 2177 | } |
2098 | d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); | 2178 | d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); |
2099 | setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); | 2179 | setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f); |
2100 | if (!isFrozen) { | 2180 | if (!isFrozen) { |
2101 | postCommand_App("font.changed"); | 2181 | postCommand_App("font.changed"); |
2102 | postCommand_App("window.unfreeze"); | 2182 | postCommand_App("window.unfreeze"); |
@@ -2173,6 +2253,10 @@ iBool handleCommand_App(const char *cmd) { | |||
2173 | } | 2253 | } |
2174 | return iTrue; | 2254 | return iTrue; |
2175 | } | 2255 | } |
2256 | else if (equal_Command(cmd, "imagestyle.set")) { | ||
2257 | d->prefs.imageStyle = arg_Command(cmd); | ||
2258 | return iTrue; | ||
2259 | } | ||
2176 | else if (equal_Command(cmd, "linewidth.set")) { | 2260 | else if (equal_Command(cmd, "linewidth.set")) { |
2177 | d->prefs.lineWidth = iMax(20, arg_Command(cmd)); | 2261 | d->prefs.lineWidth = iMax(20, arg_Command(cmd)); |
2178 | postCommand_App("document.layout.changed"); | 2262 | postCommand_App("document.layout.changed"); |
@@ -2192,7 +2276,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2192 | equal_Command(cmd, "prefs.mono.gopher.changed")) { | 2276 | equal_Command(cmd, "prefs.mono.gopher.changed")) { |
2193 | const iBool isSet = (arg_Command(cmd) != 0); | 2277 | const iBool isSet = (arg_Command(cmd) != 0); |
2194 | if (!isFrozen) { | 2278 | if (!isFrozen) { |
2195 | setFreezeDraw_Window(d->window, iTrue); | 2279 | setFreezeDraw_MainWindow(get_MainWindow(), iTrue); |
2196 | } | 2280 | } |
2197 | if (startsWith_CStr(cmd, "prefs.mono.gemini")) { | 2281 | if (startsWith_CStr(cmd, "prefs.mono.gemini")) { |
2198 | d->prefs.monospaceGemini = isSet; | 2282 | d->prefs.monospaceGemini = isSet; |
@@ -2313,10 +2397,12 @@ iBool handleCommand_App(const char *cmd) { | |||
2313 | setCStr_String(&d->prefs.httpProxy, suffixPtr_Command(cmd, "address")); | 2397 | setCStr_String(&d->prefs.httpProxy, suffixPtr_Command(cmd, "address")); |
2314 | return iTrue; | 2398 | return iTrue; |
2315 | } | 2399 | } |
2400 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | ||
2316 | else if (equal_Command(cmd, "downloads")) { | 2401 | else if (equal_Command(cmd, "downloads")) { |
2317 | setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path")); | 2402 | setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path")); |
2318 | return iTrue; | 2403 | return iTrue; |
2319 | } | 2404 | } |
2405 | #endif | ||
2320 | else if (equal_Command(cmd, "downloads.open")) { | 2406 | else if (equal_Command(cmd, "downloads.open")) { |
2321 | postCommandf_App("open url:%s", cstrCollect_String(makeFileUrl_String(downloadDir_App()))); | 2407 | postCommandf_App("open url:%s", cstrCollect_String(makeFileUrl_String(downloadDir_App()))); |
2322 | return iTrue; | 2408 | return iTrue; |
@@ -2360,7 +2446,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2360 | setUrl_UploadWidget(upload, url); | 2446 | setUrl_UploadWidget(upload, url); |
2361 | setResponseViewer_UploadWidget(upload, document_App()); | 2447 | setResponseViewer_UploadWidget(upload, document_App()); |
2362 | addChild_Widget(get_Root()->widget, iClob(upload)); | 2448 | addChild_Widget(get_Root()->widget, iClob(upload)); |
2363 | finalizeSheet_Mobile(as_Widget(upload)); | 2449 | // finalizeSheet_Mobile(as_Widget(upload)); |
2450 | setupSheetTransition_Mobile(as_Widget(upload), iTrue); | ||
2364 | postRefresh_App(); | 2451 | postRefresh_App(); |
2365 | return iTrue; | 2452 | return iTrue; |
2366 | } | 2453 | } |
@@ -2384,8 +2471,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2384 | iRoot *root = get_Root(); | 2471 | iRoot *root = get_Root(); |
2385 | iRoot *oldRoot = root; | 2472 | iRoot *oldRoot = root; |
2386 | if (newTab & otherRoot_OpenTabFlag) { | 2473 | if (newTab & otherRoot_OpenTabFlag) { |
2387 | root = otherRoot_Window(d->window, root); | 2474 | root = otherRoot_Window(as_Window(d->window), root); |
2388 | setKeyRoot_Window(d->window, root); | 2475 | setKeyRoot_Window(as_Window(d->window), root); |
2389 | setCurrent_Root(root); /* need to change for widget creation */ | 2476 | setCurrent_Root(root); /* need to change for widget creation */ |
2390 | } | 2477 | } |
2391 | iDocumentWidget *doc = document_Command(cmd); | 2478 | iDocumentWidget *doc = document_Command(cmd); |
@@ -2484,7 +2571,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2484 | } | 2571 | } |
2485 | else if (equal_Command(cmd, "tabs.close")) { | 2572 | else if (equal_Command(cmd, "tabs.close")) { |
2486 | iWidget *tabs = findWidget_App("doctabs"); | 2573 | iWidget *tabs = findWidget_App("doctabs"); |
2487 | #if defined (iPlatformAppleMobile) | 2574 | #if defined (iPlatformMobile) |
2488 | /* Can't close the last on mobile. */ | 2575 | /* Can't close the last on mobile. */ |
2489 | if (tabCount_Widget(tabs) == 1 && numRoots_Window(get_Window()) == 1) { | 2576 | if (tabCount_Widget(tabs) == 1 && numRoots_Window(get_Window()) == 1) { |
2490 | postCommand_App("navigate.home"); | 2577 | postCommand_App("navigate.home"); |
@@ -2539,7 +2626,8 @@ iBool handleCommand_App(const char *cmd) { | |||
2539 | return iTrue; | 2626 | return iTrue; |
2540 | } | 2627 | } |
2541 | else if (equal_Command(cmd, "keyroot.next")) { | 2628 | else if (equal_Command(cmd, "keyroot.next")) { |
2542 | if (setKeyRoot_Window(d->window, otherRoot_Window(d->window, d->window->keyRoot))) { | 2629 | if (setKeyRoot_Window(as_Window(d->window), |
2630 | otherRoot_Window(as_Window(d->window), d->window->base.keyRoot))) { | ||
2543 | setFocus_Widget(NULL); | 2631 | setFocus_Widget(NULL); |
2544 | } | 2632 | } |
2545 | return iTrue; | 2633 | return iTrue; |
@@ -2565,12 +2653,13 @@ iBool handleCommand_App(const char *cmd) { | |||
2565 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); | 2653 | updatePrefsPinSplitButtons_(dlg, d->prefs.pinSplit); |
2566 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); | 2654 | updateScrollSpeedButtons_(dlg, mouse_ScrollType, d->prefs.smoothScrollSpeed[mouse_ScrollType]); |
2567 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); | 2655 | updateScrollSpeedButtons_(dlg, keyboard_ScrollType, d->prefs.smoothScrollSpeed[keyboard_ScrollType]); |
2568 | updateDropdownSelection_(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); | 2656 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "prefs.uilang"), cstr_String(&d->prefs.uiLanguage)); |
2569 | updateDropdownSelection_(findChild_Widget(dlg, "prefs.returnkey"), | 2657 | updateDropdownSelection_LabelWidget( |
2570 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); | 2658 | findChild_Widget(dlg, "prefs.returnkey"), |
2659 | format_CStr("returnkey.set arg:%d", d->prefs.returnKey)); | ||
2571 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); | 2660 | setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize); |
2572 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), | 2661 | setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), |
2573 | collectNewFormat_String("%g", uiScale_Window(d->window))); | 2662 | collectNewFormat_String("%g", uiScale_Window(as_Window(d->window)))); |
2574 | setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), | 2663 | setFlags_Widget(findChild_Widget(dlg, format_CStr("prefs.font.%d", d->prefs.font)), |
2575 | selected_WidgetFlag, | 2664 | selected_WidgetFlag, |
2576 | iTrue); | 2665 | iTrue); |
@@ -2607,6 +2696,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2607 | setToggle_Widget(findChild_Widget(dlg, "prefs.collapsepreonload"), d->prefs.collapsePreOnLoad); | 2696 | setToggle_Widget(findChild_Widget(dlg, "prefs.collapsepreonload"), d->prefs.collapsePreOnLoad); |
2608 | updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.dark"), d->prefs.docThemeDark); | 2697 | updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.dark"), d->prefs.docThemeDark); |
2609 | updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.light"), d->prefs.docThemeLight); | 2698 | updateColorThemeButton_(findChild_Widget(dlg, "prefs.doctheme.light"), d->prefs.docThemeLight); |
2699 | updateImageStyleButton_(findChild_Widget(dlg, "prefs.imagestyle"), d->prefs.imageStyle); | ||
2610 | updateFontButton_(findChild_Widget(dlg, "prefs.font"), d->prefs.font); | 2700 | updateFontButton_(findChild_Widget(dlg, "prefs.font"), d->prefs.font); |
2611 | updateFontButton_(findChild_Widget(dlg, "prefs.headingfont"), d->prefs.headingFont); | 2701 | updateFontButton_(findChild_Widget(dlg, "prefs.headingfont"), d->prefs.headingFont); |
2612 | setFlags_Widget( | 2702 | setFlags_Widget( |
@@ -2687,6 +2777,31 @@ iBool handleCommand_App(const char *cmd) { | |||
2687 | makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url)); | 2777 | makeFeedSettings_Widget(findUrl_Bookmarks(d->bookmarks, url)); |
2688 | return iTrue; | 2778 | return iTrue; |
2689 | } | 2779 | } |
2780 | else if (equal_Command(cmd, "bookmarks.addfolder")) { | ||
2781 | const int parentId = argLabel_Command(cmd, "parent"); | ||
2782 | if (suffixPtr_Command(cmd, "value")) { | ||
2783 | uint32_t id = add_Bookmarks(d->bookmarks, NULL, | ||
2784 | collect_String(suffix_Command(cmd, "value")), NULL, 0); | ||
2785 | if (parentId) { | ||
2786 | get_Bookmarks(d->bookmarks, id)->parentId = parentId; | ||
2787 | } | ||
2788 | postCommand_App("bookmarks.changed"); | ||
2789 | } | ||
2790 | else { | ||
2791 | iWidget *dlg = makeValueInput_Widget( | ||
2792 | get_Root()->widget, collectNewCStr_String(cstr_Lang("dlg.addfolder.defaulttitle")), | ||
2793 | uiHeading_ColorEscape "${heading.addfolder}", "${dlg.addfolder.prompt}", | ||
2794 | uiTextAction_ColorEscape "${dlg.addfolder}", | ||
2795 | format_CStr("bookmarks.addfolder parent:%d", parentId)); | ||
2796 | setSelectAllOnFocus_InputWidget(findChild_Widget(dlg, "input"), iTrue); | ||
2797 | } | ||
2798 | return iTrue; | ||
2799 | } | ||
2800 | else if (equal_Command(cmd, "bookmarks.sort")) { | ||
2801 | sort_Bookmarks(d->bookmarks, arg_Command(cmd), cmpTitleAscending_Bookmark); | ||
2802 | postCommand_App("bookmarks.changed"); | ||
2803 | return iTrue; | ||
2804 | } | ||
2690 | else if (equal_Command(cmd, "bookmarks.reload.remote")) { | 2805 | else if (equal_Command(cmd, "bookmarks.reload.remote")) { |
2691 | fetchRemote_Bookmarks(bookmarks_App()); | 2806 | fetchRemote_Bookmarks(bookmarks_App()); |
2692 | return iTrue; | 2807 | return iTrue; |
@@ -2715,7 +2830,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2715 | else if (equal_Command(cmd, "feeds.update.finished")) { | 2830 | else if (equal_Command(cmd, "feeds.update.finished")) { |
2716 | showCollapsed_Widget(findWidget_Root("feeds.progress"), iFalse); | 2831 | showCollapsed_Widget(findWidget_Root("feeds.progress"), iFalse); |
2717 | refreshFinished_Feeds(); | 2832 | refreshFinished_Feeds(); |
2718 | postRefresh_App(); | 2833 | refresh_Widget(findWidget_App("url")); |
2719 | return iFalse; | 2834 | return iFalse; |
2720 | } | 2835 | } |
2721 | else if (equal_Command(cmd, "visited.changed")) { | 2836 | else if (equal_Command(cmd, "visited.changed")) { |
@@ -2725,6 +2840,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2725 | else if (equal_Command(cmd, "document.changed")) { | 2840 | else if (equal_Command(cmd, "document.changed")) { |
2726 | /* Set of open tabs has changed. */ | 2841 | /* Set of open tabs has changed. */ |
2727 | postCommand_App("document.openurls.changed"); | 2842 | postCommand_App("document.openurls.changed"); |
2843 | if (deviceType_App() == phone_AppDeviceType) { | ||
2844 | showToolbar_Root(d->window->base.roots[0], iTrue); | ||
2845 | } | ||
2728 | return iFalse; | 2846 | return iFalse; |
2729 | } | 2847 | } |
2730 | else if (equal_Command(cmd, "ident.new")) { | 2848 | else if (equal_Command(cmd, "ident.new")) { |
@@ -2737,7 +2855,9 @@ iBool handleCommand_App(const char *cmd) { | |||
2737 | iCertImportWidget *imp = new_CertImportWidget(); | 2855 | iCertImportWidget *imp = new_CertImportWidget(); |
2738 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); | 2856 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); |
2739 | addChild_Widget(get_Root()->widget, iClob(imp)); | 2857 | addChild_Widget(get_Root()->widget, iClob(imp)); |
2740 | finalizeSheet_Mobile(as_Widget(imp)); | 2858 | // finalizeSheet_Mobile(as_Widget(imp)); |
2859 | arrange_Widget(as_Widget(imp)); | ||
2860 | setupSheetTransition_Mobile(as_Widget(imp), iTrue); | ||
2741 | postRefresh_App(); | 2861 | postRefresh_App(); |
2742 | return iTrue; | 2862 | return iTrue; |
2743 | } | 2863 | } |
@@ -2793,12 +2913,12 @@ iBool handleCommand_App(const char *cmd) { | |||
2793 | write_Ipc(argLabel_Command(cmd, "pid"), | 2913 | write_Ipc(argLabel_Command(cmd, "pid"), |
2794 | collectNewFormat_String("%s\n", cstr_String(url_DocumentWidget(document_App()))), | 2914 | collectNewFormat_String("%s\n", cstr_String(url_DocumentWidget(document_App()))), |
2795 | response_IpcWrite); | 2915 | response_IpcWrite); |
2796 | return iTrue; | 2916 | return iTrue; |
2797 | } | 2917 | } |
2798 | else if (equal_Command(cmd, "ipc.signal")) { | 2918 | else if (equal_Command(cmd, "ipc.signal")) { |
2799 | if (argLabel_Command(cmd, "raise")) { | 2919 | if (argLabel_Command(cmd, "raise")) { |
2800 | if (d->window && d->window->win) { | 2920 | if (d->window && d->window->base.win) { |
2801 | SDL_RaiseWindow(d->window->win); | 2921 | SDL_RaiseWindow(d->window->base.win); |
2802 | } | 2922 | } |
2803 | } | 2923 | } |
2804 | signal_Ipc(arg_Command(cmd)); | 2924 | signal_Ipc(arg_Command(cmd)); |
@@ -2906,3 +3026,7 @@ iStringSet *listOpenURLs_App(void) { | |||
2906 | iRelease(docs); | 3026 | iRelease(docs); |
2907 | return set; | 3027 | return set; |
2908 | } | 3028 | } |
3029 | |||
3030 | iMainWindow *mainWindow_App(void) { | ||
3031 | return app_.window; | ||
3032 | } | ||
@@ -22,8 +22,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | /* Application core: event loop, base event processing, audio synth. */ | ||
26 | |||
27 | #include <the_Foundation/objectlist.h> | 25 | #include <the_Foundation/objectlist.h> |
28 | #include <the_Foundation/string.h> | 26 | #include <the_Foundation/string.h> |
29 | #include <the_Foundation/stringset.h> | 27 | #include <the_Foundation/stringset.h> |
@@ -35,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
35 | iDeclareType(Bookmarks) | 33 | iDeclareType(Bookmarks) |
36 | iDeclareType(DocumentWidget) | 34 | iDeclareType(DocumentWidget) |
37 | iDeclareType(GmCerts) | 35 | iDeclareType(GmCerts) |
36 | iDeclareType(MainWindow) | ||
38 | iDeclareType(MimeHooks) | 37 | iDeclareType(MimeHooks) |
39 | iDeclareType(Periodic) | 38 | iDeclareType(Periodic) |
40 | iDeclareType(Root) | 39 | iDeclareType(Root) |
@@ -44,6 +43,8 @@ iDeclareType(Window) | |||
44 | /* Command line options strings. */ | 43 | /* Command line options strings. */ |
45 | #define listTabUrls_CommandLineOption "list-tab-urls;L" | 44 | #define listTabUrls_CommandLineOption "list-tab-urls;L" |
46 | #define openUrlOrSearch_CommandLineOption "url-or-search;u" | 45 | #define openUrlOrSearch_CommandLineOption "url-or-search;u" |
46 | #define windowWidth_CommandLineOption "width;w" | ||
47 | #define windowHeight_CommandLineOption "height;h" | ||
47 | 48 | ||
48 | enum iAppDeviceType { | 49 | enum iAppDeviceType { |
49 | desktop_AppDeviceType, | 50 | desktop_AppDeviceType, |
@@ -59,14 +60,12 @@ enum iAppEventMode { | |||
59 | enum iUserEventCode { | 60 | enum iUserEventCode { |
60 | command_UserEventCode = 1, | 61 | command_UserEventCode = 1, |
61 | refresh_UserEventCode, | 62 | refresh_UserEventCode, |
62 | arrange_UserEventCode, | ||
63 | asleep_UserEventCode, | 63 | asleep_UserEventCode, |
64 | /* The start of a potential touch tap event is notified via a custom event because | 64 | /* The start of a potential touch tap event is notified via a custom event because |
65 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will | 65 | sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will |
66 | take, it could turn into a tap-and-hold for example. */ | 66 | take, it could turn into a tap-and-hold for example. */ |
67 | widgetTapBegins_UserEventCode, | 67 | widgetTapBegins_UserEventCode, |
68 | widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ | 68 | widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ |
69 | immediateRefresh_UserEventCode, /* refresh even though more events are pending */ | ||
70 | }; | 69 | }; |
71 | 70 | ||
72 | const iString *execPath_App (void); | 71 | const iString *execPath_App (void); |
@@ -117,8 +116,9 @@ iAny * findWidget_App (const char *id); | |||
117 | void addTicker_App (iTickerFunc ticker, iAny *context); | 116 | void addTicker_App (iTickerFunc ticker, iAny *context); |
118 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); | 117 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); |
119 | void removeTicker_App (iTickerFunc ticker, iAny *context); | 118 | void removeTicker_App (iTickerFunc ticker, iAny *context); |
119 | void addPopup_App (iWindow *popup); | ||
120 | void removePopup_App (iWindow *popup); | ||
120 | void postRefresh_App (void); | 121 | void postRefresh_App (void); |
121 | void postImmediateRefresh_App(void); | ||
122 | void postCommand_Root (iRoot *, const char *command); | 122 | void postCommand_Root (iRoot *, const char *command); |
123 | void postCommandf_Root (iRoot *, const char *command, ...); | 123 | void postCommandf_Root (iRoot *, const char *command, ...); |
124 | void postCommandf_App (const char *command, ...); | 124 | void postCommandf_App (const char *command, ...); |
@@ -129,10 +129,12 @@ iLocalDef void postCommandString_Root(iRoot *d, const iString *command) { | |||
129 | } | 129 | } |
130 | } | 130 | } |
131 | iLocalDef void postCommand_App(const char *command) { | 131 | iLocalDef void postCommand_App(const char *command) { |
132 | postCommandf_App(command); | 132 | postCommand_Root(NULL, command); |
133 | } | 133 | } |
134 | 134 | ||
135 | iDocumentWidget * document_Command (const char *cmd); | 135 | iDocumentWidget * document_Command (const char *cmd); |
136 | 136 | ||
137 | void openInDefaultBrowser_App (const iString *url); | 137 | void openInDefaultBrowser_App (const iString *url); |
138 | void revealPath_App (const iString *path); | 138 | void revealPath_App (const iString *path); |
139 | |||
140 | iMainWindow *mainWindow_App(void); | ||
diff --git a/src/bookmarks.c b/src/bookmarks.c index c27efbfe..fe2ca47a 100644 --- a/src/bookmarks.c +++ b/src/bookmarks.c | |||
@@ -31,13 +31,15 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include <the_Foundation/path.h> | 31 | #include <the_Foundation/path.h> |
32 | #include <the_Foundation/regexp.h> | 32 | #include <the_Foundation/regexp.h> |
33 | #include <the_Foundation/stringset.h> | 33 | #include <the_Foundation/stringset.h> |
34 | #include <the_Foundation/toml.h> | ||
34 | 35 | ||
35 | void init_Bookmark(iBookmark *d) { | 36 | void init_Bookmark(iBookmark *d) { |
36 | init_String(&d->url); | 37 | init_String(&d->url); |
37 | init_String(&d->title); | 38 | init_String(&d->title); |
38 | init_String(&d->tags); | 39 | init_String(&d->tags); |
39 | iZap(d->when); | 40 | iZap(d->when); |
40 | d->sourceId = 0; | 41 | d->parentId = 0; |
42 | d->order = 0; | ||
41 | } | 43 | } |
42 | 44 | ||
43 | void deinit_Bookmark(iBookmark *d) { | 45 | void deinit_Bookmark(iBookmark *d) { |
@@ -77,13 +79,18 @@ static int cmpTimeDescending_Bookmark_(const iBookmark **a, const iBookmark **b) | |||
77 | return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); | 79 | return iCmp(seconds_Time(&(*b)->when), seconds_Time(&(*a)->when)); |
78 | } | 80 | } |
79 | 81 | ||
80 | static int cmpTitleAscending_Bookmark_(const iBookmark **a, const iBookmark **b) { | 82 | int cmpTitleAscending_Bookmark(const iBookmark **a, const iBookmark **b) { |
81 | return cmpStringCase_String(&(*a)->title, &(*b)->title); | 83 | return cmpStringCase_String(&(*a)->title, &(*b)->title); |
82 | } | 84 | } |
83 | 85 | ||
86 | iBool filterInsideFolder_Bookmark(void *context, const iBookmark *bm) { | ||
87 | return hasParent_Bookmark(bm, id_Bookmark(context)); | ||
88 | } | ||
89 | |||
84 | /*----------------------------------------------------------------------------------------------*/ | 90 | /*----------------------------------------------------------------------------------------------*/ |
85 | 91 | ||
86 | static const char *fileName_Bookmarks_ = "bookmarks.txt"; | 92 | static const char *oldFileName_Bookmarks_ = "bookmarks.txt"; |
93 | static const char *fileName_Bookmarks_ = "bookmarks.ini"; /* since v1.7 (TOML subset) */ | ||
87 | 94 | ||
88 | struct Impl_Bookmarks { | 95 | struct Impl_Bookmarks { |
89 | iMutex * mtx; | 96 | iMutex * mtx; |
@@ -123,16 +130,19 @@ void clear_Bookmarks(iBookmarks *d) { | |||
123 | unlock_Mutex(d->mtx); | 130 | unlock_Mutex(d->mtx); |
124 | } | 131 | } |
125 | 132 | ||
133 | static void insertId_Bookmarks_(iBookmarks *d, iBookmark *bookmark, int id) { | ||
134 | bookmark->node.key = id; | ||
135 | insert_Hash(&d->bookmarks, &bookmark->node); | ||
136 | } | ||
137 | |||
126 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { | 138 | static void insert_Bookmarks_(iBookmarks *d, iBookmark *bookmark) { |
127 | lock_Mutex(d->mtx); | 139 | lock_Mutex(d->mtx); |
128 | bookmark->node.key = ++d->idEnum; | 140 | insertId_Bookmarks_(d, bookmark, ++d->idEnum); |
129 | insert_Hash(&d->bookmarks, &bookmark->node); | ||
130 | unlock_Mutex(d->mtx); | 141 | unlock_Mutex(d->mtx); |
131 | } | 142 | } |
132 | 143 | ||
133 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { | 144 | static void loadOldFormat_Bookmarks(iBookmarks *d, const char *dirPath) { |
134 | clear_Bookmarks(d); | 145 | iFile *f = newCStr_File(concatPath_CStr(dirPath, oldFileName_Bookmarks_)); |
135 | iFile *f = newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_)); | ||
136 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | 146 | if (open_File(f, readOnly_FileMode | text_FileMode)) { |
137 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); | 147 | const iRangecc src = range_Block(collect_Block(readAll_File(f))); |
138 | iRangecc line = iNullRange; | 148 | iRangecc line = iNullRange; |
@@ -170,6 +180,111 @@ void load_Bookmarks(iBookmarks *d, const char *dirPath) { | |||
170 | iRelease(f); | 180 | iRelease(f); |
171 | } | 181 | } |
172 | 182 | ||
183 | /*----------------------------------------------------------------------------------------------*/ | ||
184 | |||
185 | iDeclareType(BookmarkLoader) | ||
186 | |||
187 | struct Impl_BookmarkLoader { | ||
188 | iTomlParser *toml; | ||
189 | iBookmarks * bookmarks; | ||
190 | iBookmark * bm; | ||
191 | }; | ||
192 | |||
193 | static void handleTable_BookmarkLoader_(void *context, const iString *table, iBool isStart) { | ||
194 | iBookmarkLoader *d = context; | ||
195 | if (isStart) { | ||
196 | iAssert(!d->bm); | ||
197 | d->bm = new_Bookmark(); | ||
198 | const int id = toInt_String(table); | ||
199 | d->bookmarks->idEnum = iMax(d->bookmarks->idEnum, id); | ||
200 | insertId_Bookmarks_(d->bookmarks, d->bm, id); | ||
201 | } | ||
202 | else { | ||
203 | d->bm = NULL; | ||
204 | } | ||
205 | } | ||
206 | |||
207 | static void handleKeyValue_BookmarkLoader_(void *context, const iString *table, const iString *key, | ||
208 | const iTomlValue *tv) { | ||
209 | iBookmarkLoader *d = context; | ||
210 | iBookmark *bm = d->bm; | ||
211 | if (bm) { | ||
212 | iUnused(table); /* it's the current one */ | ||
213 | if (!cmp_String(key, "url") && tv->type == string_TomlType) { | ||
214 | set_String(&bm->url, tv->value.string); | ||
215 | } | ||
216 | else if (!cmp_String(key, "title") && tv->type == string_TomlType) { | ||
217 | set_String(&bm->title, tv->value.string); | ||
218 | } | ||
219 | else if (!cmp_String(key, "tags") && tv->type == string_TomlType) { | ||
220 | set_String(&bm->tags, tv->value.string); | ||
221 | } | ||
222 | else if (!cmp_String(key, "icon") && tv->type == int64_TomlType) { | ||
223 | bm->icon = (iChar) tv->value.int64; | ||
224 | } | ||
225 | else if (!cmp_String(key, "created") && tv->type == int64_TomlType) { | ||
226 | initSeconds_Time(&bm->when, tv->value.int64); | ||
227 | } | ||
228 | else if (!cmp_String(key, "parent") && tv->type == int64_TomlType) { | ||
229 | bm->parentId = tv->value.int64; | ||
230 | } | ||
231 | else if (!cmp_String(key, "order") && tv->type == int64_TomlType) { | ||
232 | bm->order = tv->value.int64; | ||
233 | } | ||
234 | } | ||
235 | } | ||
236 | |||
237 | static void init_BookmarkLoader(iBookmarkLoader *d, iBookmarks *bookmarks) { | ||
238 | d->toml = new_TomlParser(); | ||
239 | setHandlers_TomlParser(d->toml, handleTable_BookmarkLoader_, handleKeyValue_BookmarkLoader_, d); | ||
240 | d->bookmarks = bookmarks; | ||
241 | d->bm = NULL; | ||
242 | } | ||
243 | |||
244 | static void deinit_BookmarkLoader(iBookmarkLoader *d) { | ||
245 | delete_TomlParser(d->toml); | ||
246 | } | ||
247 | |||
248 | static void load_BookmarkLoader(iBookmarkLoader *d, iFile *file) { | ||
249 | if (!parse_TomlParser(d->toml, collect_String(readString_File(file)))) { | ||
250 | fprintf(stderr, "[Bookmarks] syntax error(s) in %s\n", cstr_String(path_File(file))); | ||
251 | } | ||
252 | } | ||
253 | |||
254 | iDefineTypeConstructionArgs(BookmarkLoader, (iBookmarks *b), b) | ||
255 | |||
256 | /*----------------------------------------------------------------------------------------------*/ | ||
257 | |||
258 | static iBool isMatchingParent_Bookmark_(void *context, const iBookmark *bm) { | ||
259 | return bm->parentId == *(const uint32_t *) context; | ||
260 | } | ||
261 | |||
262 | void sort_Bookmarks(iBookmarks *d, uint32_t parentId, iBookmarksCompareFunc cmp) { | ||
263 | lock_Mutex(d->mtx); | ||
264 | iConstForEach(PtrArray, i, list_Bookmarks(d, cmp, isMatchingParent_Bookmark_, &parentId)) { | ||
265 | iBookmark *bm = i.ptr; | ||
266 | bm->order = index_PtrArrayConstIterator(&i) + 1; | ||
267 | } | ||
268 | unlock_Mutex(d->mtx); | ||
269 | } | ||
270 | |||
271 | void load_Bookmarks(iBookmarks *d, const char *dirPath) { | ||
272 | clear_Bookmarks(d); | ||
273 | /* Load new .ini bookmarks, if present. */ | ||
274 | iFile *f = iClob(newCStr_File(concatPath_CStr(dirPath, fileName_Bookmarks_))); | ||
275 | if (!open_File(f, readOnly_FileMode | text_FileMode)) { | ||
276 | /* As a fallback, try loading the v1.6 bookmarks file. */ | ||
277 | loadOldFormat_Bookmarks(d, dirPath); | ||
278 | /* Old format has an implicit alphabetic sort order. */ | ||
279 | sort_Bookmarks(d, 0, cmpTitleAscending_Bookmark); | ||
280 | return; | ||
281 | } | ||
282 | iBookmarkLoader loader; | ||
283 | init_BookmarkLoader(&loader, d); | ||
284 | load_BookmarkLoader(&loader, f); | ||
285 | deinit_BookmarkLoader(&loader); | ||
286 | } | ||
287 | |||
173 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | 288 | void save_Bookmarks(const iBookmarks *d, const char *dirPath) { |
174 | lock_Mutex(d->mtx); | 289 | lock_Mutex(d->mtx); |
175 | iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption)); | 290 | iRegExp *remotePattern = iClob(new_RegExp("\\bremote\\b", caseSensitive_RegExpOption)); |
@@ -185,12 +300,26 @@ void save_Bookmarks(const iBookmarks *d, const char *dirPath) { | |||
185 | continue; | 300 | continue; |
186 | } | 301 | } |
187 | format_String(str, | 302 | format_String(str, |
188 | "%08x %.0lf %s\n%s\n%s\n", | 303 | "[%d]\n" |
304 | "url = \"%s\"\n" | ||
305 | "title = \"%s\"\n" | ||
306 | "tags = \"%s\"\n" | ||
307 | "icon = 0x%x\n" | ||
308 | "created = %.0f # %s\n", | ||
309 | id_Bookmark(bm), | ||
310 | cstrCollect_String(quote_String(&bm->url, iFalse)), | ||
311 | cstrCollect_String(quote_String(&bm->title, iFalse)), | ||
312 | cstrCollect_String(quote_String(&bm->tags, iFalse)), | ||
189 | bm->icon, | 313 | bm->icon, |
190 | seconds_Time(&bm->when), | 314 | seconds_Time(&bm->when), |
191 | cstr_String(&bm->url), | 315 | cstrCollect_String(format_Time(&bm->when, "%Y-%m-%d"))); |
192 | cstr_String(&bm->title), | 316 | if (bm->parentId) { |
193 | cstr_String(&bm->tags)); | 317 | appendFormat_String(str, "parent = %d\n", bm->parentId); |
318 | } | ||
319 | if (bm->order) { | ||
320 | appendFormat_String(str, "order = %d\n", bm->order); | ||
321 | } | ||
322 | appendCStr_String(str, "\n"); | ||
194 | writeData_File(f, cstr_String(str), size_String(str)); | 323 | writeData_File(f, cstr_String(str), size_String(str)); |
195 | } | 324 | } |
196 | } | 325 | } |
@@ -202,7 +331,9 @@ uint32_t add_Bookmarks(iBookmarks *d, const iString *url, const iString *title, | |||
202 | iChar icon) { | 331 | iChar icon) { |
203 | lock_Mutex(d->mtx); | 332 | lock_Mutex(d->mtx); |
204 | iBookmark *bm = new_Bookmark(); | 333 | iBookmark *bm = new_Bookmark(); |
205 | set_String(&bm->url, canonicalUrl_String(url)); | 334 | if (url) { |
335 | set_String(&bm->url, canonicalUrl_String(url)); | ||
336 | } | ||
206 | set_String(&bm->title, title); | 337 | set_String(&bm->title, title); |
207 | if (tags) { | 338 | if (tags) { |
208 | set_String(&bm->tags, tags); | 339 | set_String(&bm->tags, tags); |
@@ -218,16 +349,9 @@ iBool remove_Bookmarks(iBookmarks *d, uint32_t id) { | |||
218 | lock_Mutex(d->mtx); | 349 | lock_Mutex(d->mtx); |
219 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); | 350 | iBookmark *bm = (iBookmark *) remove_Hash(&d->bookmarks, id); |
220 | if (bm) { | 351 | if (bm) { |
221 | /* If this is a remote source, make sure all the remote bookmarks are | 352 | /* Remove all the contained bookmarks as well. */ |
222 | removed as well. */ | 353 | iConstForEach(PtrArray, i, list_Bookmarks(d, NULL, filterInsideFolder_Bookmark, bm)) { |
223 | if (hasTag_Bookmark(bm, remoteSource_BookmarkTag)) { | 354 | delete_Bookmark((iBookmark *) remove_Hash(&d->bookmarks, id_Bookmark(i.ptr))); |
224 | iForEach(Hash, i, &d->bookmarks) { | ||
225 | iBookmark *j = (iBookmark *) i.value; | ||
226 | if (j->sourceId == id_Bookmark(bm)) { | ||
227 | remove_HashIterator(&i); | ||
228 | delete_Bookmark(j); | ||
229 | } | ||
230 | } | ||
231 | } | 355 | } |
232 | delete_Bookmark(bm); | 356 | delete_Bookmark(bm); |
233 | } | 357 | } |
@@ -287,6 +411,20 @@ iBookmark *get_Bookmarks(iBookmarks *d, uint32_t id) { | |||
287 | return (iBookmark *) value_Hash(&d->bookmarks, id); | 411 | return (iBookmark *) value_Hash(&d->bookmarks, id); |
288 | } | 412 | } |
289 | 413 | ||
414 | void reorder_Bookmarks(iBookmarks *d, uint32_t id, int newOrder) { | ||
415 | lock_Mutex(d->mtx); | ||
416 | iForEach(Hash, i, &d->bookmarks) { | ||
417 | iBookmark *bm = (iBookmark *) i.value; | ||
418 | if (id_Bookmark(bm) == id) { | ||
419 | bm->order = newOrder; | ||
420 | } | ||
421 | else if (bm->order >= newOrder) { | ||
422 | bm->order++; | ||
423 | } | ||
424 | } | ||
425 | unlock_Mutex(d->mtx); | ||
426 | } | ||
427 | |||
290 | iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { | 428 | iBool filterTagsRegExp_Bookmarks(void *regExp, const iBookmark *bm) { |
291 | iRegExpMatch m; | 429 | iRegExpMatch m; |
292 | init_RegExpMatch(&m); | 430 | init_RegExpMatch(&m); |
@@ -321,6 +459,16 @@ const iPtrArray *list_Bookmarks(const iBookmarks *d, iBookmarksCompareFunc cmp, | |||
321 | return list; | 459 | return list; |
322 | } | 460 | } |
323 | 461 | ||
462 | size_t count_Bookmarks(const iBookmarks *d) { | ||
463 | size_t n = 0; | ||
464 | iConstForEach(Hash, i, &d->bookmarks) { | ||
465 | if (!isFolder_Bookmark((const iBookmark *) i.value)) { | ||
466 | n++; | ||
467 | } | ||
468 | } | ||
469 | return n; | ||
470 | } | ||
471 | |||
324 | const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkListType listType) { | 472 | const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkListType listType) { |
325 | iString *str = collectNew_String(); | 473 | iString *str = collectNew_String(); |
326 | lock_Mutex(d->mtx); | 474 | lock_Mutex(d->mtx); |
@@ -333,21 +481,37 @@ const iString *bookmarkListPage_Bookmarks(const iBookmarks *d, enum iBookmarkLis | |||
333 | appendFormat_String(str, | 481 | appendFormat_String(str, |
334 | "%s\n\n" | 482 | "%s\n\n" |
335 | "${bookmark.export.saving}\n\n", | 483 | "${bookmark.export.saving}\n\n", |
336 | formatCStrs_Lang("bookmark.export.count.n", size_Hash(&d->bookmarks))); | 484 | formatCStrs_Lang("bookmark.export.count.n", count_Bookmarks(d))); |
337 | } | 485 | } |
338 | else if (listType == listByTag_BookmarkListType) { | 486 | else if (listType == listByTag_BookmarkListType) { |
339 | appendFormat_String(str, "${bookmark.export.taginfo}\n\n"); | 487 | appendFormat_String(str, "${bookmark.export.taginfo}\n\n"); |
340 | } | 488 | } |
341 | iStringSet *tags = new_StringSet(); | 489 | iStringSet *tags = new_StringSet(); |
342 | const iPtrArray *bmList = list_Bookmarks(d, | 490 | const iPtrArray *bmList = |
343 | listType == listByCreationTime_BookmarkListType | 491 | list_Bookmarks(d, |
344 | ? cmpTimeDescending_Bookmark_ | 492 | listType == listByCreationTime_BookmarkListType ? cmpTimeDescending_Bookmark_ |
345 | : cmpTitleAscending_Bookmark_, | 493 | : listType == listByTag_BookmarkListType ? cmpTitleAscending_Bookmark |
346 | NULL, | 494 | : cmpTree_Bookmark, |
347 | NULL); | 495 | NULL, NULL); |
496 | if (listType == listByFolder_BookmarkListType) { | ||
497 | iConstForEach(PtrArray, i, bmList) { | ||
498 | const iBookmark *bm = i.ptr; | ||
499 | if (!isFolder_Bookmark(bm) && !bm->parentId) { | ||
500 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); | ||
501 | } | ||
502 | } | ||
503 | } | ||
348 | iConstForEach(PtrArray, i, bmList) { | 504 | iConstForEach(PtrArray, i, bmList) { |
349 | const iBookmark *bm = i.ptr; | 505 | const iBookmark *bm = i.ptr; |
350 | if (listType == listByFolder_BookmarkListType) { | 506 | if (isFolder_Bookmark(bm)) { |
507 | if (listType == listByFolder_BookmarkListType) { | ||
508 | const int depth = depth_Bookmark(bm); | ||
509 | appendFormat_String(str, "\n%s %s\n", | ||
510 | depth == 0 ? "##" : "###", cstr_String(&bm->title)); | ||
511 | } | ||
512 | continue; | ||
513 | } | ||
514 | if (listType == listByFolder_BookmarkListType && bm->parentId) { | ||
351 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); | 515 | appendFormat_String(str, "=> %s %s\n", cstr_String(&bm->url), cstr_String(&bm->title)); |
352 | } | 516 | } |
353 | else if (listType == listByCreationTime_BookmarkListType) { | 517 | else if (listType == listByCreationTime_BookmarkListType) { |
@@ -452,7 +616,7 @@ void requestFinished_Bookmarks(iBookmarks *d, iGmRequest *req) { | |||
452 | } | 616 | } |
453 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913); | 617 | const uint32_t bmId = add_Bookmarks(d, absUrl, titleStr, remoteTag, 0x2913); |
454 | iBookmark *bm = get_Bookmarks(d, bmId); | 618 | iBookmark *bm = get_Bookmarks(d, bmId); |
455 | bm->sourceId = *(uint32_t *) userData_Object(req); | 619 | bm->parentId = *(uint32_t *) userData_Object(req); |
456 | delete_String(titleStr); | 620 | delete_String(titleStr); |
457 | } | 621 | } |
458 | delete_String(urlStr); | 622 | delete_String(urlStr); |
diff --git a/src/bookmarks.h b/src/bookmarks.h index 353b4197..13501ded 100644 --- a/src/bookmarks.h +++ b/src/bookmarks.h | |||
@@ -32,6 +32,8 @@ iDeclareType(GmRequest) | |||
32 | iDeclareType(Bookmark) | 32 | iDeclareType(Bookmark) |
33 | iDeclareTypeConstruction(Bookmark) | 33 | iDeclareTypeConstruction(Bookmark) |
34 | 34 | ||
35 | /* TODO: Make the special internal tags a bitfield, separate from user's tags. */ | ||
36 | |||
35 | #define headings_BookmarkTag "headings" | 37 | #define headings_BookmarkTag "headings" |
36 | #define homepage_BookmarkTag "homepage" | 38 | #define homepage_BookmarkTag "homepage" |
37 | #define linkSplit_BookmarkTag "linksplit" | 39 | #define linkSplit_BookmarkTag "linksplit" |
@@ -47,11 +49,15 @@ struct Impl_Bookmark { | |||
47 | iString tags; | 49 | iString tags; |
48 | iChar icon; | 50 | iChar icon; |
49 | iTime when; | 51 | iTime when; |
50 | uint32_t sourceId; /* remote */ | 52 | uint32_t parentId; /* remote source or folder */ |
53 | int order; /* sort order */ | ||
51 | }; | 54 | }; |
52 | 55 | ||
53 | iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } | 56 | iLocalDef uint32_t id_Bookmark (const iBookmark *d) { return d->node.key; } |
57 | iLocalDef iBool isFolder_Bookmark (const iBookmark *d) { return isEmpty_String(&d->url); } | ||
54 | 58 | ||
59 | iBool hasParent_Bookmark (const iBookmark *, uint32_t parentId); | ||
60 | int depth_Bookmark (const iBookmark *); | ||
55 | iBool hasTag_Bookmark (const iBookmark *, const char *tag); | 61 | iBool hasTag_Bookmark (const iBookmark *, const char *tag); |
56 | void addTag_Bookmark (iBookmark *, const char *tag); | 62 | void addTag_Bookmark (iBookmark *, const char *tag); |
57 | void removeTag_Bookmark (iBookmark *, const char *tag); | 63 | void removeTag_Bookmark (iBookmark *, const char *tag); |
@@ -70,28 +76,36 @@ iLocalDef void addOrRemoveTag_Bookmark(iBookmark *d, const char *tag, iBool add) | |||
70 | } | 76 | } |
71 | } | 77 | } |
72 | 78 | ||
79 | int cmpTitleAscending_Bookmark (const iBookmark **, const iBookmark **); | ||
80 | int cmpTree_Bookmark (const iBookmark **, const iBookmark **); | ||
81 | |||
82 | iBool filterInsideFolder_Bookmark (void *parentFolder, const iBookmark *); | ||
83 | |||
73 | /*----------------------------------------------------------------------------------------------*/ | 84 | /*----------------------------------------------------------------------------------------------*/ |
74 | 85 | ||
75 | iDeclareType(Bookmarks) | 86 | iDeclareType(Bookmarks) |
76 | iDeclareTypeConstruction(Bookmarks) | 87 | iDeclareTypeConstruction(Bookmarks) |
77 | 88 | ||
89 | typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); | ||
90 | typedef int (*iBookmarksCompareFunc) (const iBookmark **, const iBookmark **); | ||
91 | |||
78 | void clear_Bookmarks (iBookmarks *); | 92 | void clear_Bookmarks (iBookmarks *); |
79 | void load_Bookmarks (iBookmarks *, const char *dirPath); | 93 | void load_Bookmarks (iBookmarks *, const char *dirPath); |
94 | void save_Bookmarks (const iBookmarks *, const char *dirPath); | ||
95 | |||
80 | uint32_t add_Bookmarks (iBookmarks *, const iString *url, const iString *title, | 96 | uint32_t add_Bookmarks (iBookmarks *, const iString *url, const iString *title, |
81 | const iString *tags, iChar icon); | 97 | const iString *tags, iChar icon); |
82 | iBool remove_Bookmarks (iBookmarks *, uint32_t id); | 98 | iBool remove_Bookmarks (iBookmarks *, uint32_t id); |
83 | iBookmark * get_Bookmarks (iBookmarks *, uint32_t id); | 99 | iBookmark * get_Bookmarks (iBookmarks *, uint32_t id); |
100 | void reorder_Bookmarks (iBookmarks *, uint32_t id, int newOrder); | ||
101 | iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); | ||
102 | void sort_Bookmarks (iBookmarks *, uint32_t parentId, iBookmarksCompareFunc cmp); | ||
84 | void fetchRemote_Bookmarks (iBookmarks *); | 103 | void fetchRemote_Bookmarks (iBookmarks *); |
85 | void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req); | 104 | void requestFinished_Bookmarks (iBookmarks *, iGmRequest *req); |
86 | iBool updateBookmarkIcon_Bookmarks(iBookmarks *, const iString *url, iChar icon); | ||
87 | iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); | ||
88 | 105 | ||
89 | void save_Bookmarks (const iBookmarks *, const char *dirPath); | 106 | iChar siteIcon_Bookmarks (const iBookmarks *, const iString *url); |
90 | uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ | 107 | uint32_t findUrl_Bookmarks (const iBookmarks *, const iString *url); /* O(n) */ |
91 | 108 | ||
92 | typedef iBool (*iBookmarksFilterFunc) (void *context, const iBookmark *); | ||
93 | typedef int (*iBookmarksCompareFunc)(const iBookmark **, const iBookmark **); | ||
94 | |||
95 | iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); | 109 | iBool filterTagsRegExp_Bookmarks (void *regExp, const iBookmark *); |
96 | 110 | ||
97 | /** | 111 | /** |
@@ -36,9 +36,18 @@ enum iFileVersion { | |||
36 | multipleRoots_FileVersion = 2, | 36 | multipleRoots_FileVersion = 2, |
37 | serializedSidebarState_FileVersion = 3, | 37 | serializedSidebarState_FileVersion = 3, |
38 | addedRecentUrlFlags_FileVersion = 4, | 38 | addedRecentUrlFlags_FileVersion = 4, |
39 | bookmarkFolderState_FileVersion = 5, | ||
39 | /* meta */ | 40 | /* meta */ |
40 | idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ | 41 | idents_FileVersion = 1, /* version used by GmCerts/idents.lgr */ |
41 | latest_FileVersion = 4, | 42 | latest_FileVersion = 5, |
43 | }; | ||
44 | |||
45 | enum iImageStyle { | ||
46 | original_ImageStyle = 0, | ||
47 | grayscale_ImageStyle = 1, | ||
48 | bgFg_ImageStyle = 2, | ||
49 | textColorized_ImageStyle = 3, | ||
50 | preformatColorized_ImageStyle = 4, | ||
42 | }; | 51 | }; |
43 | 52 | ||
44 | enum iScrollType { | 53 | enum iScrollType { |
@@ -99,11 +108,13 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
99 | #define rightArrow_Icon "\u279e" | 108 | #define rightArrow_Icon "\u279e" |
100 | #define barLeftArrow_Icon "\u21a4" | 109 | #define barLeftArrow_Icon "\u21a4" |
101 | #define barRightArrow_Icon "\u21a6" | 110 | #define barRightArrow_Icon "\u21a6" |
111 | #define upDownArrow_Icon "\u21c5" | ||
102 | #define clock_Icon "\U0001f553" | 112 | #define clock_Icon "\U0001f553" |
103 | #define pin_Icon "\U0001f588" | 113 | #define pin_Icon "\U0001f588" |
104 | #define star_Icon "\u2605" | 114 | #define star_Icon "\u2605" |
105 | #define whiteStar_Icon "\u2606" | 115 | #define whiteStar_Icon "\u2606" |
106 | #define person_Icon "\U0001f464" | 116 | #define person_Icon "\U0001f464" |
117 | #define key_Icon "\U0001f511" | ||
107 | #define download_Icon "\u2ba7" | 118 | #define download_Icon "\u2ba7" |
108 | #define upload_Icon "\u2ba5" | 119 | #define upload_Icon "\u2ba5" |
109 | #define export_Icon "\U0001f4e4" | 120 | #define export_Icon "\U0001f4e4" |
@@ -140,9 +151,14 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
140 | #define clipboard_Icon "\U0001f4cb" | 151 | #define clipboard_Icon "\U0001f4cb" |
141 | #define unhappy_Icon "\U0001f641" | 152 | #define unhappy_Icon "\U0001f641" |
142 | #define globe_Icon "\U0001f310" | 153 | #define globe_Icon "\U0001f310" |
154 | #define envelope_Icon "\U0001f4e7" | ||
143 | #define magnifyingGlass_Icon "\U0001f50d" | 155 | #define magnifyingGlass_Icon "\U0001f50d" |
144 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" | 156 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" |
145 | #define return_Icon "\u23ce" | 157 | #define return_Icon "\u23ce" |
158 | #define undo_Icon "\u23ea" | ||
159 | #define select_Icon "\u2b1a" | ||
160 | #define downAngle_Icon "\ufe40" | ||
161 | #define photo_Icon "\U0001f5bb" | ||
146 | 162 | ||
147 | #if defined (iPlatformApple) | 163 | #if defined (iPlatformApple) |
148 | # define shift_Icon "\u21e7" | 164 | # define shift_Icon "\u21e7" |
@@ -153,7 +169,10 @@ iLocalDef int acceptKeyMod_ReturnKeyBehavior(int behavior) { | |||
153 | #endif | 169 | #endif |
154 | 170 | ||
155 | #if defined (iPlatformAppleDesktop) | 171 | #if defined (iPlatformAppleDesktop) |
156 | # define iHaveNativeMenus | 172 | # define iHaveNativeMenus /* main menu */ |
173 | # if defined (LAGRANGE_ENABLE_MAC_MENUS) | ||
174 | # define iHaveNativeContextMenus | ||
175 | # endif | ||
157 | #endif | 176 | #endif |
158 | 177 | ||
159 | /* UI labels that depend on the platform */ | 178 | /* UI labels that depend on the platform */ |
diff --git a/src/feeds.c b/src/feeds.c index 91ef8c2c..9770ca0a 100644 --- a/src/feeds.c +++ b/src/feeds.c | |||
@@ -50,6 +50,7 @@ void init_FeedEntry(iFeedEntry *d) { | |||
50 | init_String(&d->url); | 50 | init_String(&d->url); |
51 | init_String(&d->title); | 51 | init_String(&d->title); |
52 | d->bookmarkId = 0; | 52 | d->bookmarkId = 0; |
53 | d->isHeading = iFalse; | ||
53 | } | 54 | } |
54 | 55 | ||
55 | void deinit_FeedEntry(iFeedEntry *d) { | 56 | void deinit_FeedEntry(iFeedEntry *d) { |
@@ -234,6 +235,7 @@ static void parseResult_FeedJob_(iFeedJob *d) { | |||
234 | } | 235 | } |
235 | trimStart_Rangecc(&line); | 236 | trimStart_Rangecc(&line); |
236 | iFeedEntry *entry = new_FeedEntry(); | 237 | iFeedEntry *entry = new_FeedEntry(); |
238 | entry->isHeading = iTrue; | ||
237 | entry->posted = now; | 239 | entry->posted = now; |
238 | if (!d->isFirstUpdate) { | 240 | if (!d->isFirstUpdate) { |
239 | entry->discovered = now; | 241 | entry->discovered = now; |
@@ -275,7 +277,8 @@ static void save_Feeds_(iFeeds *d) { | |||
275 | initCurrent_Time(&now); | 277 | initCurrent_Time(&now); |
276 | iConstForEach(Array, i, &d->entries.values) { | 278 | iConstForEach(Array, i, &d->entries.values) { |
277 | const iFeedEntry *entry = *(const iFeedEntry **) i.value; | 279 | const iFeedEntry *entry = *(const iFeedEntry **) i.value; |
278 | if (isValid_Time(&entry->discovered) && | 280 | /* Heading entries are kept as long as they are present in the source. */ |
281 | if (!entry->isHeading && isValid_Time(&entry->discovered) && | ||
279 | secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { | 282 | secondsSince_Time(&now, &entry->discovered) > maxAge_Visited) { |
280 | continue; /* Forget entries discovered long ago. */ | 283 | continue; /* Forget entries discovered long ago. */ |
281 | } | 284 | } |
@@ -298,25 +301,78 @@ static iBool isHeadingEntry_FeedEntry_(const iFeedEntry *d) { | |||
298 | return contains_String(&d->url, '#'); | 301 | return contains_String(&d->url, '#'); |
299 | } | 302 | } |
300 | 303 | ||
301 | static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { | 304 | static iStringSet *listHeadingEntriesFrom_Feeds_(const iFeeds *d, uint32_t sourceId) { |
305 | iStringSet *set = new_StringSet(); | ||
306 | iConstForEach(Array, i, &d->entries.values) { | ||
307 | const iFeedEntry *entry = *(const iFeedEntry **) i.value; | ||
308 | if (entry->bookmarkId == sourceId) { | ||
309 | insert_StringSet(set, &entry->url); | ||
310 | } | ||
311 | } | ||
312 | return set; | ||
313 | } | ||
314 | |||
315 | static iBool updateEntries_Feeds_(iFeeds *d, iBool isHeadings, uint32_t sourceId, | ||
316 | iPtrArray *incoming) { | ||
317 | /* Entries are removed from `incoming` if they are added to the Feeds entries array. | ||
318 | Anything remaining in `incoming` will be deleted afterwards. */ | ||
302 | iBool gotNew = iFalse; | 319 | iBool gotNew = iFalse; |
303 | lock_Mutex(d->mtx); | 320 | lock_Mutex(d->mtx); |
304 | iTime now; | 321 | iTime now; |
305 | initCurrent_Time(&now); | 322 | initCurrent_Time(&now); |
306 | iForEach(PtrArray, i, incoming) { | 323 | if (isHeadings) { |
307 | iFeedEntry *entry = i.ptr; | 324 | // printf("Updating sourceID %d...\n", sourceId); |
308 | /* Disregard old entries. */ | 325 | iStringSet *known = listHeadingEntriesFrom_Feeds_(d, sourceId); |
309 | if (secondsSince_Time(&now, &entry->posted) >= maxAge_Visited) { | 326 | // puts(" Known URLs:"); |
310 | /* We don't remember this far back, so the unread status of the entry would | 327 | // iConstForEach(StringSet, ss, known) { |
311 | be incorrect. */ | 328 | // printf(" {%s}\n", cstr_String(ss.value)); |
312 | continue; | 329 | // } |
330 | iStringSet *presentInSource = new_StringSet(); | ||
331 | /* Look for unknown entries. */ | ||
332 | iForEach(PtrArray, i, incoming) { | ||
333 | iFeedEntry *entry = i.ptr; | ||
334 | insert_StringSet(presentInSource, &entry->url); | ||
335 | if (!contains_StringSet(known, &entry->url)) { | ||
336 | // printf(" {%s} is new\n", cstr_String(&entry->url)); | ||
337 | insert_SortedArray(&d->entries, &entry); | ||
338 | gotNew = iTrue; | ||
339 | remove_PtrArrayIterator(&i); | ||
340 | } | ||
341 | } | ||
342 | // puts(" URLs present in source:"); | ||
343 | // iConstForEach(StringSet, ps, presentInSource) { | ||
344 | // printf(" {%s}\n", cstr_String(ps.value)); | ||
345 | // } | ||
346 | // puts(" URLs to purge:"); | ||
347 | /* All known entries that are no longer present in source must be deleted. */ | ||
348 | iForEach(Array, e, &d->entries.values) { | ||
349 | iFeedEntry *entry = *(iFeedEntry **) e.value; | ||
350 | if (entry->bookmarkId == sourceId && | ||
351 | !contains_StringSet(presentInSource, &entry->url)) { | ||
352 | // printf(" {%s}\n", cstr_String(&entry->url)); | ||
353 | delete_FeedEntry(entry); | ||
354 | remove_ArrayIterator(&e); | ||
355 | } | ||
313 | } | 356 | } |
314 | size_t pos; | 357 | // puts("Done."); |
315 | if (locate_SortedArray(&d->entries, &entry, &pos)) { | 358 | iRelease(presentInSource); |
316 | iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos); | 359 | iRelease(known); |
317 | iAssert(isHeadingEntry_FeedEntry_(existing) == isHeadingEntry_FeedEntry_(entry)); | 360 | } |
318 | /* Already known, but update it, maybe the time and label have changed. */ | 361 | else { |
319 | if (!isHeadingEntry_FeedEntry_(existing)) { | 362 | iForEach(PtrArray, i, incoming) { |
363 | iFeedEntry *entry = i.ptr; | ||
364 | /* Disregard old incoming entries. */ | ||
365 | if (secondsSince_Time(&now, &entry->posted) >= maxAge_Visited) { | ||
366 | /* We don't remember this far back, so the unread status of the entry would | ||
367 | be incorrect. */ | ||
368 | continue; | ||
369 | } | ||
370 | size_t pos; | ||
371 | if (locate_SortedArray(&d->entries, &entry, &pos)) { | ||
372 | iFeedEntry *existing = *(iFeedEntry **) at_SortedArray(&d->entries, pos); | ||
373 | iAssert(!isHeadingEntry_FeedEntry_(existing)); | ||
374 | iAssert(!isHeadingEntry_FeedEntry_(entry)); | ||
375 | /* Already known, but update it, maybe the time and label have changed. */ | ||
320 | iBool changed = iFalse; | 376 | iBool changed = iFalse; |
321 | iDate newDate; | 377 | iDate newDate; |
322 | iDate oldDate; | 378 | iDate oldDate; |
@@ -336,12 +392,12 @@ static iBool updateEntries_Feeds_(iFeeds *d, iPtrArray *incoming) { | |||
336 | gotNew = iTrue; | 392 | gotNew = iTrue; |
337 | } | 393 | } |
338 | } | 394 | } |
395 | else { | ||
396 | insert_SortedArray(&d->entries, &entry); | ||
397 | gotNew = iTrue; | ||
398 | } | ||
399 | remove_PtrArrayIterator(&i); | ||
339 | } | 400 | } |
340 | else { | ||
341 | insert_SortedArray(&d->entries, &entry); | ||
342 | gotNew = iTrue; | ||
343 | } | ||
344 | remove_PtrArrayIterator(&i); | ||
345 | } | 401 | } |
346 | unlock_Mutex(d->mtx); | 402 | unlock_Mutex(d->mtx); |
347 | return gotNew; | 403 | return gotNew; |
@@ -369,7 +425,8 @@ static iThreadResult fetch_Feeds_(iThread *thread) { | |||
369 | if (isFinished_GmRequest(work[i]->request)) { | 425 | if (isFinished_GmRequest(work[i]->request)) { |
370 | /* TODO: Handle redirects. Need to resubmit the job with new URL. */ | 426 | /* TODO: Handle redirects. Need to resubmit the job with new URL. */ |
371 | parseResult_FeedJob_(work[i]); | 427 | parseResult_FeedJob_(work[i]); |
372 | gotNew |= updateEntries_Feeds_(d, &work[i]->results); | 428 | gotNew |= updateEntries_Feeds_( |
429 | d, work[i]->checkHeadings, work[i]->bookmarkId, &work[i]->results); | ||
373 | delete_FeedJob(work[i]); | 430 | delete_FeedJob(work[i]); |
374 | work[i] = NULL; | 431 | work[i] = NULL; |
375 | } | 432 | } |
@@ -407,7 +464,7 @@ static iBool startWorker_Feeds_(iFeeds *d) { | |||
407 | if (!contains_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm))) { | 464 | if (!contains_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm))) { |
408 | job->isFirstUpdate = iTrue; | 465 | job->isFirstUpdate = iTrue; |
409 | // printf("first check of %x: %s\n", id_Bookmark(bm), cstr_String(&bm->title)); | 466 | // printf("first check of %x: %s\n", id_Bookmark(bm), cstr_String(&bm->title)); |
410 | fflush(stdout); | 467 | // fflush(stdout); |
411 | insert_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm)); | 468 | insert_IntSet(&d->previouslyCheckedFeeds, id_Bookmark(bm)); |
412 | } | 469 | } |
413 | pushBack_PtrArray(&d->jobs, job); | 470 | pushBack_PtrArray(&d->jobs, job); |
@@ -539,6 +596,11 @@ static void load_Feeds_(iFeeds *d) { | |||
539 | stripDefaultUrlPort_String(&entry->url); | 596 | stripDefaultUrlPort_String(&entry->url); |
540 | set_String(&entry->url, canonicalUrl_String(&entry->url)); | 597 | set_String(&entry->url, canonicalUrl_String(&entry->url)); |
541 | set_String(&entry->title, title); | 598 | set_String(&entry->title, title); |
599 | entry->isHeading = isHeadingEntry_FeedEntry_(entry); | ||
600 | if (entry->isHeading) { | ||
601 | printf("[Feeds] src:%d url:{%s}\n", entry->bookmarkId, | ||
602 | cstr_String(&entry->url)); | ||
603 | } | ||
542 | insert_SortedArray(&d->entries, &entry); | 604 | insert_SortedArray(&d->entries, &entry); |
543 | } | 605 | } |
544 | delete_String(title); | 606 | delete_String(title); |
@@ -642,7 +704,8 @@ size_t numUnread_Feeds(void) { | |||
642 | size_t max = 100; /* match the number of items shown in the sidebar */ | 704 | size_t max = 100; /* match the number of items shown in the sidebar */ |
643 | iConstForEach(PtrArray, i, listEntries_Feeds()) { | 705 | iConstForEach(PtrArray, i, listEntries_Feeds()) { |
644 | if (!max--) break; | 706 | if (!max--) break; |
645 | if (isUnread_FeedEntry(i.ptr)) { | 707 | const iFeedEntry *entry = i.ptr; |
708 | if (isValid_Time(&entry->discovered) && isUnread_FeedEntry(i.ptr)) { | ||
646 | count++; | 709 | count++; |
647 | } | 710 | } |
648 | } | 711 | } |
diff --git a/src/feeds.h b/src/feeds.h index 5ff2adfb..8863d24f 100644 --- a/src/feeds.h +++ b/src/feeds.h | |||
@@ -34,6 +34,7 @@ struct Impl_FeedEntry { | |||
34 | iTime discovered; | 34 | iTime discovered; |
35 | iString url; | 35 | iString url; |
36 | iString title; | 36 | iString title; |
37 | iBool isHeading; /* URL fragment points to a heading */ | ||
37 | uint32_t bookmarkId; /* note: runtime only, not a persistent ID */ | 38 | uint32_t bookmarkId; /* note: runtime only, not a persistent ID */ |
38 | }; | 39 | }; |
39 | 40 | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index b9832f38..22f409a6 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -82,6 +82,7 @@ struct Impl_GmDocument { | |||
82 | iString url; /* for resolving relative links */ | 82 | iString url; /* for resolving relative links */ |
83 | iString localHost; | 83 | iString localHost; |
84 | iInt2 size; | 84 | iInt2 size; |
85 | int outsideMargin; | ||
85 | iArray layout; /* contents of source, laid out in document space */ | 86 | iArray layout; /* contents of source, laid out in document space */ |
86 | iPtrArray links; | 87 | iPtrArray links; |
87 | enum iGmDocumentBanner bannerType; | 88 | enum iGmDocumentBanner bannerType; |
@@ -239,7 +240,10 @@ static iRangecc addLink_GmDocument_(iGmDocument *d, iRangecc line, iGmLinkId *li | |||
239 | iString *path = newRange_String(parts.path); | 240 | iString *path = newRange_String(parts.path); |
240 | if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || | 241 | if (endsWithCase_String(path, ".gif") || endsWithCase_String(path, ".jpg") || |
241 | endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || | 242 | endsWithCase_String(path, ".jpeg") || endsWithCase_String(path, ".png") || |
242 | endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || | 243 | endsWithCase_String(path, ".tga") || endsWithCase_String(path, ".psd") || |
244 | #if defined (LAGRANGE_ENABLE_WEBP) | ||
245 | endsWithCase_String(path, ".webp") || | ||
246 | #endif | ||
243 | endsWithCase_String(path, ".hdr") || endsWithCase_String(path, ".pic")) { | 247 | endsWithCase_String(path, ".hdr") || endsWithCase_String(path, ".pic")) { |
244 | link->flags |= imageFileExtension_GmLinkFlag; | 248 | link->flags |= imageFileExtension_GmLinkFlag; |
245 | } | 249 | } |
@@ -460,6 +464,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
460 | const iBool isNarrow = d->size.x < 90 * gap_Text; | 464 | const iBool isNarrow = d->size.x < 90 * gap_Text; |
461 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; | 465 | const iBool isVeryNarrow = d->size.x <= 70 * gap_Text; |
462 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; | 466 | const iBool isExtremelyNarrow = d->size.x <= 60 * gap_Text; |
467 | const iBool isFullWidthImages = (d->outsideMargin < 5 * gap_UI); | ||
463 | const iBool isDarkBg = isDark_GmDocumentTheme( | 468 | const iBool isDarkBg = isDark_GmDocumentTheme( |
464 | isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); | 469 | isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight); |
465 | /* TODO: Collect these parameters into a GmTheme. */ | 470 | /* TODO: Collect these parameters into a GmTheme. */ |
@@ -495,7 +500,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
495 | 0.0f, 0.25f, 1.0f, 0.5f, 1.5f, 0.5f, 0.5f, 0.25f | 500 | 0.0f, 0.25f, 1.0f, 0.5f, 1.5f, 0.5f, 0.5f, 0.25f |
496 | }; | 501 | }; |
497 | static const char *arrow = rightArrowhead_Icon; | 502 | static const char *arrow = rightArrowhead_Icon; |
498 | static const char *envelope = "\U0001f4e7"; | 503 | static const char *envelope = envelope_Icon; |
499 | static const char *bullet = "\u2022"; | 504 | static const char *bullet = "\u2022"; |
500 | static const char *folder = "\U0001f4c1"; | 505 | static const char *folder = "\U0001f4c1"; |
501 | static const char *globe = globe_Icon; | 506 | static const char *globe = globe_Icon; |
@@ -503,6 +508,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
503 | static const char *magnifyingGlass = "\U0001f50d"; | 508 | static const char *magnifyingGlass = "\U0001f50d"; |
504 | static const char *pointingFinger = "\U0001f449"; | 509 | static const char *pointingFinger = "\U0001f449"; |
505 | static const char *uploadArrow = upload_Icon; | 510 | static const char *uploadArrow = upload_Icon; |
511 | static const char *image = photo_Icon; | ||
506 | clear_Array(&d->layout); | 512 | clear_Array(&d->layout); |
507 | clearLinks_GmDocument_(d); | 513 | clearLinks_GmDocument_(d); |
508 | clear_Array(&d->headings); | 514 | clear_Array(&d->headings); |
@@ -761,6 +767,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
761 | : scheme == finger_GmLinkScheme ? pointingFinger | 767 | : scheme == finger_GmLinkScheme ? pointingFinger |
762 | : scheme == mailto_GmLinkScheme ? envelope | 768 | : scheme == mailto_GmLinkScheme ? envelope |
763 | : link->flags & remote_GmLinkFlag ? globe | 769 | : link->flags & remote_GmLinkFlag ? globe |
770 | : link->flags & imageFileExtension_GmLinkFlag ? image | ||
764 | : arrow); | 771 | : arrow); |
765 | /* Custom link icon is shown on local Gemini links only. */ | 772 | /* Custom link icon is shown on local Gemini links only. */ |
766 | if (!isEmpty_Range(&link->labelIcon)) { | 773 | if (!isEmpty_Range(&link->labelIcon)) { |
@@ -813,7 +820,7 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
813 | rts.layoutWidth = d->size.x; | 820 | rts.layoutWidth = d->size.x; |
814 | rts.indent = indent * gap_Text; | 821 | rts.indent = indent * gap_Text; |
815 | /* The right margin is used for balancing lines horizontally. */ | 822 | /* The right margin is used for balancing lines horizontally. */ |
816 | if (isVeryNarrow) { | 823 | if (isVeryNarrow || isFullWidthImages) { |
817 | rts.rightMargin = 0; | 824 | rts.rightMargin = 0; |
818 | } | 825 | } |
819 | else { | 826 | else { |
@@ -898,6 +905,13 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
898 | run.bounds.size.x = d->size.x; | 905 | run.bounds.size.x = d->size.x; |
899 | const float aspect = (float) imgSize.y / (float) imgSize.x; | 906 | const float aspect = (float) imgSize.y / (float) imgSize.x; |
900 | run.bounds.size.y = d->size.x * aspect; | 907 | run.bounds.size.y = d->size.x * aspect; |
908 | /* Extend the image to full width, including outside margin, if the viewport | ||
909 | is narrow enough. */ | ||
910 | if (isFullWidthImages) { | ||
911 | run.bounds.size.x += d->outsideMargin * 2; | ||
912 | run.bounds.size.y += d->outsideMargin * 2 * aspect; | ||
913 | run.bounds.pos.x -= d->outsideMargin; | ||
914 | } | ||
901 | run.visBounds = run.bounds; | 915 | run.visBounds = run.bounds; |
902 | const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); | 916 | const iInt2 maxSize = mulf_I2(imgSize, get_Window()->pixelRatio); |
903 | if (width_Rect(run.visBounds) > maxSize.x) { | 917 | if (width_Rect(run.visBounds) > maxSize.x) { |
@@ -990,6 +1004,7 @@ void init_GmDocument(iGmDocument *d) { | |||
990 | init_String(&d->url); | 1004 | init_String(&d->url); |
991 | init_String(&d->localHost); | 1005 | init_String(&d->localHost); |
992 | d->bannerType = siteDomain_GmDocumentBanner; | 1006 | d->bannerType = siteDomain_GmDocumentBanner; |
1007 | d->outsideMargin = 0; | ||
993 | d->size = zero_I2(); | 1008 | d->size = zero_I2(); |
994 | init_Array(&d->layout, sizeof(iGmRun)); | 1009 | init_Array(&d->layout, sizeof(iGmRun)); |
995 | init_PtrArray(&d->links); | 1010 | init_PtrArray(&d->links); |
@@ -1543,8 +1558,9 @@ void setBanner_GmDocument(iGmDocument *d, enum iGmDocumentBanner type) { | |||
1543 | d->bannerType = type; | 1558 | d->bannerType = type; |
1544 | } | 1559 | } |
1545 | 1560 | ||
1546 | void setWidth_GmDocument(iGmDocument *d, int width) { | 1561 | void setWidth_GmDocument(iGmDocument *d, int width, int outsideMargin) { |
1547 | d->size.x = width; | 1562 | d->size.x = width; |
1563 | d->outsideMargin = outsideMargin; /* distance to edge of the viewport */ | ||
1548 | doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ | 1564 | doLayout_GmDocument_(d); /* TODO: just flag need-layout and do it later */ |
1549 | } | 1565 | } |
1550 | 1566 | ||
@@ -1698,7 +1714,7 @@ void setUrl_GmDocument(iGmDocument *d, const iString *url) { | |||
1698 | updateIconBasedOnUrl_GmDocument_(d); | 1714 | updateIconBasedOnUrl_GmDocument_(d); |
1699 | } | 1715 | } |
1700 | 1716 | ||
1701 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width, | 1717 | void setSource_GmDocument(iGmDocument *d, const iString *source, int width, int outsideMargin, |
1702 | enum iGmDocumentUpdate updateType) { | 1718 | enum iGmDocumentUpdate updateType) { |
1703 | // printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n", | 1719 | // printf("[GmDocument] source update (%zu bytes), width:%d, final:%d\n", |
1704 | // size_String(source), width, updateType == final_GmDocumentUpdate); | 1720 | // size_String(source), width, updateType == final_GmDocumentUpdate); |
@@ -1713,7 +1729,7 @@ void setSource_GmDocument(iGmDocument *d, const iString *source, int width, | |||
1713 | if (isNormalized_GmDocument_(d)) { | 1729 | if (isNormalized_GmDocument_(d)) { |
1714 | normalize_GmDocument(d); | 1730 | normalize_GmDocument(d); |
1715 | } | 1731 | } |
1716 | setWidth_GmDocument(d, width); /* re-do layout */ | 1732 | setWidth_GmDocument(d, width, outsideMargin); /* re-do layout */ |
1717 | } | 1733 | } |
1718 | 1734 | ||
1719 | void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { | 1735 | void foldPre_GmDocument(iGmDocument *d, uint16_t preId) { |
diff --git a/src/gmdocument.h b/src/gmdocument.h index 9a7a70df..332c3e00 100644 --- a/src/gmdocument.h +++ b/src/gmdocument.h | |||
@@ -59,6 +59,7 @@ enum iGmDocumentTheme { | |||
59 | white_GmDocumentTheme, | 59 | white_GmDocumentTheme, |
60 | sepia_GmDocumentTheme, | 60 | sepia_GmDocumentTheme, |
61 | highContrast_GmDocumentTheme, | 61 | highContrast_GmDocumentTheme, |
62 | max_GmDocumentTheme | ||
62 | }; | 63 | }; |
63 | 64 | ||
64 | iBool isDark_GmDocumentTheme(enum iGmDocumentTheme); | 65 | iBool isDark_GmDocumentTheme(enum iGmDocumentTheme); |
@@ -174,11 +175,11 @@ enum iGmDocumentUpdate { | |||
174 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); | 175 | void setThemeSeed_GmDocument (iGmDocument *, const iBlock *seed); |
175 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); | 176 | void setFormat_GmDocument (iGmDocument *, enum iSourceFormat format); |
176 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); | 177 | void setBanner_GmDocument (iGmDocument *, enum iGmDocumentBanner type); |
177 | void setWidth_GmDocument (iGmDocument *, int width); | 178 | void setWidth_GmDocument (iGmDocument *, int width, int outsideMargin); |
178 | void redoLayout_GmDocument (iGmDocument *); | 179 | void redoLayout_GmDocument (iGmDocument *); |
179 | iBool updateOpenURLs_GmDocument(iGmDocument *); | 180 | iBool updateOpenURLs_GmDocument(iGmDocument *); |
180 | void setUrl_GmDocument (iGmDocument *, const iString *url); | 181 | void setUrl_GmDocument (iGmDocument *, const iString *url); |
181 | void setSource_GmDocument (iGmDocument *, const iString *source, int width, | 182 | void setSource_GmDocument (iGmDocument *, const iString *source, int width, int outsideMargin, |
182 | enum iGmDocumentUpdate updateType); | 183 | enum iGmDocumentUpdate updateType); |
183 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); | 184 | void foldPre_GmDocument (iGmDocument *, uint16_t preId); |
184 | 185 | ||
diff --git a/src/gmrequest.c b/src/gmrequest.c index 00a02983..1a9e83a9 100644 --- a/src/gmrequest.c +++ b/src/gmrequest.c | |||
@@ -158,6 +158,7 @@ struct Impl_GmRequest { | |||
158 | uint32_t id; | 158 | uint32_t id; |
159 | iMutex * mtx; | 159 | iMutex * mtx; |
160 | iGmCerts * certs; /* not owned */ | 160 | iGmCerts * certs; /* not owned */ |
161 | const iGmIdentity * identity; | ||
161 | enum iGmRequestState state; | 162 | enum iGmRequestState state; |
162 | iString url; | 163 | iString url; |
163 | iTitanData * titan; | 164 | iTitanData * titan; |
@@ -527,6 +528,7 @@ static void beginGopherConnection_GmRequest_(iGmRequest *d, const iString *host, | |||
527 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { | 528 | void init_GmRequest(iGmRequest *d, iGmCerts *certs) { |
528 | d->mtx = new_Mutex(); | 529 | d->mtx = new_Mutex(); |
529 | d->id = add_Atomic(&idGen_, 1) + 1; | 530 | d->id = add_Atomic(&idGen_, 1) + 1; |
531 | d->identity = NULL; | ||
530 | d->resp = new_GmResponse(); | 532 | d->resp = new_GmResponse(); |
531 | d->isFilterEnabled = iTrue; | 533 | d->isFilterEnabled = iTrue; |
532 | d->isRespLocked = iFalse; | 534 | d->isRespLocked = iFalse; |
@@ -582,6 +584,11 @@ void setUrl_GmRequest(iGmRequest *d, const iString *url) { | |||
582 | the web. */ | 584 | the web. */ |
583 | urlEncodePath_String(&d->url); | 585 | urlEncodePath_String(&d->url); |
584 | urlEncodeSpaces_String(&d->url); | 586 | urlEncodeSpaces_String(&d->url); |
587 | d->identity = identityForUrl_GmCerts(d->certs, &d->url); | ||
588 | } | ||
589 | |||
590 | void setIdentity_GmRequest(iGmRequest *d, const iGmIdentity *id) { | ||
591 | d->identity = id; | ||
585 | } | 592 | } |
586 | 593 | ||
587 | static iBool isTitan_GmRequest_(const iGmRequest *d) { | 594 | static iBool isTitan_GmRequest_(const iGmRequest *d) { |
@@ -902,9 +909,8 @@ void submit_GmRequest(iGmRequest *d) { | |||
902 | } | 909 | } |
903 | d->state = receivingHeader_GmRequestState; | 910 | d->state = receivingHeader_GmRequestState; |
904 | d->req = new_TlsRequest(); | 911 | d->req = new_TlsRequest(); |
905 | const iGmIdentity *identity = identityForUrl_GmCerts(d->certs, &d->url); | 912 | if (d->identity) { |
906 | if (identity) { | 913 | setCertificate_TlsRequest(d->req, d->identity->cert); |
907 | setCertificate_TlsRequest(d->req, identity->cert); | ||
908 | } | 914 | } |
909 | iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); | 915 | iConnect(TlsRequest, d->req, readyRead, d, readIncoming_GmRequest_); |
910 | iConnect(TlsRequest, d->req, sent, d, bytesSent_GmRequest_); | 916 | iConnect(TlsRequest, d->req, sent, d, bytesSent_GmRequest_); |
diff --git a/src/gmrequest.h b/src/gmrequest.h index 97b23f3c..a377ac91 100644 --- a/src/gmrequest.h +++ b/src/gmrequest.h | |||
@@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | #include "gmutil.h" | 28 | #include "gmutil.h" |
29 | 29 | ||
30 | iDeclareType(GmCerts) | 30 | iDeclareType(GmCerts) |
31 | iDeclareType(GmIdentity) | ||
31 | iDeclareType(GmResponse) | 32 | iDeclareType(GmResponse) |
32 | 33 | ||
33 | enum iGmCertFlag { | 34 | enum iGmCertFlag { |
@@ -69,6 +70,7 @@ typedef void (*iGmRequestProgressFunc)(iGmRequest *, size_t current, size_t tota | |||
69 | 70 | ||
70 | void enableFilters_GmRequest (iGmRequest *, iBool enable); | 71 | void enableFilters_GmRequest (iGmRequest *, iBool enable); |
71 | void setUrl_GmRequest (iGmRequest *, const iString *url); | 72 | void setUrl_GmRequest (iGmRequest *, const iString *url); |
73 | void setIdentity_GmRequest (iGmRequest *, const iGmIdentity *id); | ||
72 | void setTitanData_GmRequest (iGmRequest *, const iString *mime, | 74 | void setTitanData_GmRequest (iGmRequest *, const iString *mime, |
73 | const iBlock *payload, const iString *token); | 75 | const iBlock *payload, const iString *token); |
74 | void setSendProgressFunc_GmRequest(iGmRequest *, iGmRequestProgressFunc func); | 76 | void setSendProgressFunc_GmRequest(iGmRequest *, iGmRequestProgressFunc func); |
diff --git a/src/gmutil.c b/src/gmutil.c index 9552c2a1..d87de8f6 100644 --- a/src/gmutil.c +++ b/src/gmutil.c | |||
@@ -511,54 +511,64 @@ const iString *findContainerArchive_Path(const iString *path) { | |||
511 | return NULL; | 511 | return NULL; |
512 | } | 512 | } |
513 | 513 | ||
514 | const char *mediaType_Path(const iString *path) { | 514 | const char *mediaTypeFromFileExtension_String(const iString *d) { |
515 | if (endsWithCase_String(path, ".gmi") || endsWithCase_String(path, ".gemini")) { | 515 | if (endsWithCase_String(d, ".gmi") || endsWithCase_String(d, ".gemini")) { |
516 | return "text/gemini; charset=utf-8"; | 516 | return "text/gemini; charset=utf-8"; |
517 | } | 517 | } |
518 | else if (endsWithCase_String(path, ".pem")) { | 518 | else if (endsWithCase_String(d, ".pem")) { |
519 | return "application/x-pem-file"; | 519 | return "application/x-pem-file"; |
520 | } | 520 | } |
521 | else if (endsWithCase_String(path, ".zip")) { | 521 | else if (endsWithCase_String(d, ".zip")) { |
522 | return "application/zip"; | 522 | return "application/zip"; |
523 | } | 523 | } |
524 | else if (endsWithCase_String(path, ".gpub")) { | 524 | else if (endsWithCase_String(d, ".gpub")) { |
525 | return "application/gpub+zip"; | 525 | return "application/gpub+zip"; |
526 | } | 526 | } |
527 | else if (endsWithCase_String(path, ".xml")) { | 527 | else if (endsWithCase_String(d, ".xml")) { |
528 | return "text/xml"; | 528 | return "text/xml"; |
529 | } | 529 | } |
530 | else if (endsWithCase_String(path, ".png")) { | 530 | else if (endsWithCase_String(d, ".png")) { |
531 | return "image/png"; | 531 | return "image/png"; |
532 | } | 532 | } |
533 | else if (endsWithCase_String(path, ".jpg") || endsWithCase_String(path, ".jpeg")) { | 533 | else if (endsWithCase_String(d, ".webp")) { |
534 | return "image/webp"; | ||
535 | } | ||
536 | else if (endsWithCase_String(d, ".jpg") || endsWithCase_String(d, ".jpeg")) { | ||
534 | return "image/jpeg"; | 537 | return "image/jpeg"; |
535 | } | 538 | } |
536 | else if (endsWithCase_String(path, ".gif")) { | 539 | else if (endsWithCase_String(d, ".gif")) { |
537 | return "image/gif"; | 540 | return "image/gif"; |
538 | } | 541 | } |
539 | else if (endsWithCase_String(path, ".wav")) { | 542 | else if (endsWithCase_String(d, ".wav")) { |
540 | return "audio/wave"; | 543 | return "audio/wave"; |
541 | } | 544 | } |
542 | else if (endsWithCase_String(path, ".ogg")) { | 545 | else if (endsWithCase_String(d, ".ogg")) { |
543 | return "audio/ogg"; | 546 | return "audio/ogg"; |
544 | } | 547 | } |
545 | else if (endsWithCase_String(path, ".mp3")) { | 548 | else if (endsWithCase_String(d, ".mp3")) { |
546 | return "audio/mpeg"; | 549 | return "audio/mpeg"; |
547 | } | 550 | } |
548 | else if (endsWithCase_String(path, ".mid")) { | 551 | else if (endsWithCase_String(d, ".mid")) { |
549 | return "audio/midi"; | 552 | return "audio/midi"; |
550 | } | 553 | } |
551 | else if (endsWithCase_String(path, ".txt") || | 554 | else if (endsWithCase_String(d, ".txt") || |
552 | endsWithCase_String(path, ".md") || | 555 | endsWithCase_String(d, ".md") || |
553 | endsWithCase_String(path, ".c") || | 556 | endsWithCase_String(d, ".c") || |
554 | endsWithCase_String(path, ".h") || | 557 | endsWithCase_String(d, ".h") || |
555 | endsWithCase_String(path, ".cc") || | 558 | endsWithCase_String(d, ".cc") || |
556 | endsWithCase_String(path, ".hh") || | 559 | endsWithCase_String(d, ".hh") || |
557 | endsWithCase_String(path, ".cpp") || | 560 | endsWithCase_String(d, ".cpp") || |
558 | endsWithCase_String(path, ".hpp")) { | 561 | endsWithCase_String(d, ".hpp")) { |
559 | return "text/plain"; | 562 | return "text/plain"; |
560 | } | 563 | } |
561 | const char *mtype = "application/octet-stream"; | 564 | return "application/octet-stream"; |
565 | } | ||
566 | |||
567 | const char *mediaType_Path(const iString *path) { | ||
568 | const char *mtype = mediaTypeFromFileExtension_String(path); | ||
569 | if (iCmpStr(mtype, "application/octet-stream")) { | ||
570 | return mtype; /* extension recognized */ | ||
571 | } | ||
562 | /* If the file is reasonably small and looks like UTF-8, we'll display it as text/plain. */ | 572 | /* If the file is reasonably small and looks like UTF-8, we'll display it as text/plain. */ |
563 | if (fileExists_FileInfo(path) && fileSize_FileInfo(path) <= 5000000) { | 573 | if (fileExists_FileInfo(path) && fileSize_FileInfo(path) <= 5000000) { |
564 | iFile *f = new_File(path); | 574 | iFile *f = new_File(path); |
diff --git a/src/gmutil.h b/src/gmutil.h index f8491781..3c10d45b 100644 --- a/src/gmutil.h +++ b/src/gmutil.h | |||
@@ -133,6 +133,7 @@ const iString * withSpacesEncoded_String(const iString *); | |||
133 | const iString * canonicalUrl_String (const iString *); | 133 | const iString * canonicalUrl_String (const iString *); |
134 | 134 | ||
135 | const char * mediaType_Path (const iString *path); | 135 | const char * mediaType_Path (const iString *path); |
136 | const char * mediaTypeFromFileExtension_String (const iString *); | ||
136 | iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); | 137 | iRangecc mediaTypeWithoutParameters_Rangecc (iRangecc mime); |
137 | 138 | ||
138 | const iString * findContainerArchive_Path (const iString *path); | 139 | const iString * findContainerArchive_Path (const iString *path); |
@@ -37,6 +37,7 @@ iBool processEvent_iOS (const SDL_Event *); | |||
37 | void playHapticEffect_iOS (enum iHapticEffect effect); | 37 | void playHapticEffect_iOS (enum iHapticEffect effect); |
38 | void exportDownloadedFile_iOS(const iString *path); | 38 | void exportDownloadedFile_iOS(const iString *path); |
39 | void pickFileForOpening_iOS (void); | 39 | void pickFileForOpening_iOS (void); |
40 | void pickFile_iOS (const char *command); /* ` path:%s` will be appended */ | ||
40 | 41 | ||
41 | iBool isPhone_iOS (void); | 42 | iBool isPhone_iOS (void); |
42 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); | 43 | void safeAreaInsets_iOS (float *left, float *top, float *right, float *bottom); |
@@ -161,6 +161,7 @@ API_AVAILABLE(ios(13.0)) | |||
161 | 161 | ||
162 | @interface AppState : NSObject<UIDocumentPickerDelegate> { | 162 | @interface AppState : NSObject<UIDocumentPickerDelegate> { |
163 | iString *fileBeingSaved; | 163 | iString *fileBeingSaved; |
164 | iString *pickFileCommand; | ||
164 | } | 165 | } |
165 | @property (nonatomic, assign) BOOL isHapticsAvailable; | 166 | @property (nonatomic, assign) BOOL isHapticsAvailable; |
166 | @property (nonatomic, strong) NSObject *haptic; | 167 | @property (nonatomic, strong) NSObject *haptic; |
@@ -173,9 +174,17 @@ static AppState *appState_; | |||
173 | -(instancetype)init { | 174 | -(instancetype)init { |
174 | self = [super init]; | 175 | self = [super init]; |
175 | fileBeingSaved = NULL; | 176 | fileBeingSaved = NULL; |
177 | pickFileCommand = NULL; | ||
176 | return self; | 178 | return self; |
177 | } | 179 | } |
178 | 180 | ||
181 | -(void)setPickFileCommand:(const char *)command { | ||
182 | if (!pickFileCommand) { | ||
183 | pickFileCommand = new_String(); | ||
184 | } | ||
185 | setCStr_String(pickFileCommand, command); | ||
186 | } | ||
187 | |||
179 | -(void)setFileBeingSaved:(const iString *)path { | 188 | -(void)setFileBeingSaved:(const iString *)path { |
180 | fileBeingSaved = copy_String(path); | 189 | fileBeingSaved = copy_String(path); |
181 | } | 190 | } |
@@ -213,7 +222,11 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
213 | NSURL *url = [urls firstObject]; | 222 | NSURL *url = [urls firstObject]; |
214 | iString *path = localFilePathFromUrl_String(collectNewCStr_String([[url absoluteString] | 223 | iString *path = localFilePathFromUrl_String(collectNewCStr_String([[url absoluteString] |
215 | UTF8String])); | 224 | UTF8String])); |
216 | postCommandf_App("file.open temp:1 path:%s", cstrCollect_String(path)); | 225 | postCommandf_App("%s temp:1 path:%s", |
226 | cstr_String(pickFileCommand), | ||
227 | cstrCollect_String(path)); | ||
228 | delete_String(pickFileCommand); | ||
229 | pickFileCommand = NULL; | ||
217 | } | 230 | } |
218 | } | 231 | } |
219 | 232 | ||
@@ -221,6 +234,10 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
221 | if (fileBeingSaved) { | 234 | if (fileBeingSaved) { |
222 | [self removeSavedFile]; | 235 | [self removeSavedFile]; |
223 | } | 236 | } |
237 | if (pickFileCommand) { | ||
238 | delete_String(pickFileCommand); | ||
239 | pickFileCommand = NULL; | ||
240 | } | ||
224 | } | 241 | } |
225 | 242 | ||
226 | -(void)keyboardOnScreen:(NSNotification *)notification { | 243 | -(void)keyboardOnScreen:(NSNotification *)notification { |
@@ -230,14 +247,14 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
230 | UIView *view = [viewController_(get_Window()) view]; | 247 | UIView *view = [viewController_(get_Window()) view]; |
231 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; | 248 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; |
232 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); | 249 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); |
233 | iWindow *window = get_Window(); | 250 | iMainWindow *window = get_MainWindow(); |
234 | const iInt2 rootSize = size_Root(window->roots[0]); | 251 | const iInt2 rootSize = size_Root(window->base.roots[0]); |
235 | const int keyTop = keyboardFrame.origin.y * window->pixelRatio; | 252 | const int keyTop = keyboardFrame.origin.y * window->base.pixelRatio; |
236 | setKeyboardHeight_Window(window, rootSize.y - keyTop); | 253 | setKeyboardHeight_MainWindow(window, rootSize.y - keyTop); |
237 | } | 254 | } |
238 | 255 | ||
239 | -(void)keyboardOffScreen:(NSNotification *)notification { | 256 | -(void)keyboardOffScreen:(NSNotification *)notification { |
240 | setKeyboardHeight_Window(get_Window(), 0); | 257 | setKeyboardHeight_MainWindow(get_MainWindow(), 0); |
241 | } | 258 | } |
242 | @end | 259 | @end |
243 | 260 | ||
@@ -264,7 +281,6 @@ void setupApplication_iOS(void) { | |||
264 | selector:@selector(keyboardOffScreen:) | 281 | selector:@selector(keyboardOffScreen:) |
265 | name:UIKeyboardWillHideNotification | 282 | name:UIKeyboardWillHideNotification |
266 | object:nil]; | 283 | object:nil]; |
267 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; | ||
268 | /* Media player remote controls. */ | 284 | /* Media player remote controls. */ |
269 | MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; | 285 | MPRemoteCommandCenter *commandCenter = [MPRemoteCommandCenter sharedCommandCenter]; |
270 | [[commandCenter pauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { | 286 | [[commandCenter pauseCommand] addTargetWithHandler:^MPRemoteCommandHandlerStatus(MPRemoteCommandEvent * _Nonnull event) { |
@@ -455,11 +471,13 @@ void exportDownloadedFile_iOS(const iString *path) { | |||
455 | } | 471 | } |
456 | 472 | ||
457 | void pickFileForOpening_iOS(void) { | 473 | void pickFileForOpening_iOS(void) { |
474 | pickFile_iOS("file.open"); | ||
475 | } | ||
476 | |||
477 | void pickFile_iOS(const char *command) { | ||
478 | [appState_ setPickFileCommand:command]; | ||
458 | UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] | 479 | UIDocumentPickerViewController *picker = [[UIDocumentPickerViewController alloc] |
459 | initWithDocumentTypes:@[@"fi.skyjake.lagrange.gemini", | 480 | initWithDocumentTypes:@[@"public.data"] |
460 | @"public.text", | ||
461 | @"public.image", | ||
462 | @"public.audio"] | ||
463 | inMode:UIDocumentPickerModeImport]; | 481 | inMode:UIDocumentPickerModeImport]; |
464 | picker.delegate = appState_; | 482 | picker.delegate = appState_; |
465 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; | 483 | [viewController_(get_Window()) presentViewController:picker animated:YES completion:nil]; |
@@ -487,6 +505,8 @@ void init_AVFAudioPlayer(iAVFAudioPlayer *d) { | |||
487 | d->player = NULL; | 505 | d->player = NULL; |
488 | d->volume = 1.0f; | 506 | d->volume = 1.0f; |
489 | d->state = initialized_AVFAudioPlayerState; | 507 | d->state = initialized_AVFAudioPlayerState; |
508 | /* Playback is imminent. */ | ||
509 | [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil]; | ||
490 | } | 510 | } |
491 | 511 | ||
492 | void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { | 512 | void deinit_AVFAudioPlayer(iAVFAudioPlayer *d) { |
diff --git a/src/macos.h b/src/macos.h index 0d3f097a..22a6dfff 100644 --- a/src/macos.h +++ b/src/macos.h | |||
@@ -24,6 +24,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | 24 | ||
25 | #include "ui/util.h" | 25 | #include "ui/util.h" |
26 | 26 | ||
27 | iDeclareType(MenuItem) | ||
28 | iDeclareType(Window) | ||
29 | iDeclareType(Widget) | ||
30 | |||
27 | /* Platform-specific functionality for macOS */ | 31 | /* Platform-specific functionality for macOS */ |
28 | 32 | ||
29 | iBool shouldDefaultToMetalRenderer_MacOS (void); | 33 | iBool shouldDefaultToMetalRenderer_MacOS (void); |
@@ -31,9 +35,12 @@ iBool shouldDefaultToMetalRenderer_MacOS (void); | |||
31 | void enableMomentumScroll_MacOS (void); | 35 | void enableMomentumScroll_MacOS (void); |
32 | void registerURLHandler_MacOS (void); | 36 | void registerURLHandler_MacOS (void); |
33 | void setupApplication_MacOS (void); | 37 | void setupApplication_MacOS (void); |
38 | void hideTitleBar_MacOS (iWindow *window); | ||
34 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); | 39 | void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); |
35 | void removeMenu_MacOS (int atIndex); | 40 | void removeMenu_MacOS (int atIndex); |
36 | void enableMenu_MacOS (const char *menuLabel, iBool enable); | 41 | void enableMenu_MacOS (const char *menuLabel, iBool enable); |
37 | void enableMenuItem_MacOS (const char *menuItemCommand, iBool enable); | 42 | void enableMenuItem_MacOS (const char *menuItemCommand, iBool enable); |
38 | void enableMenuItemsByKey_MacOS (int key, int kmods, iBool enable); | 43 | void enableMenuItemsByKey_MacOS (int key, int kmods, iBool enable); |
39 | void handleCommand_MacOS (const char *cmd); | 44 | void handleCommand_MacOS (const char *cmd); |
45 | |||
46 | void showPopupMenu_MacOS (iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n); | ||
diff --git a/src/macos.m b/src/macos.m index d588fa4a..53a6da00 100644 --- a/src/macos.m +++ b/src/macos.m | |||
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "ui/window.h" | 30 | #include "ui/window.h" |
31 | 31 | ||
32 | #include <SDL_timer.h> | 32 | #include <SDL_timer.h> |
33 | #include <SDL_syswm.h> | ||
33 | 34 | ||
34 | #import <AppKit/AppKit.h> | 35 | #import <AppKit/AppKit.h> |
35 | 36 | ||
@@ -51,6 +52,16 @@ static iInt2 macVer_(void) { | |||
51 | return init_I2(10, 10); | 52 | return init_I2(10, 10); |
52 | } | 53 | } |
53 | 54 | ||
55 | static NSWindow *nsWindow_(SDL_Window *window) { | ||
56 | SDL_SysWMinfo wm; | ||
57 | SDL_VERSION(&wm.version); | ||
58 | if (SDL_GetWindowWMInfo(window, &wm)) { | ||
59 | return wm.info.cocoa.window; | ||
60 | } | ||
61 | iAssert(false); | ||
62 | return nil; | ||
63 | } | ||
64 | |||
54 | static NSString *currentSystemAppearance_(void) { | 65 | static NSString *currentSystemAppearance_(void) { |
55 | /* This API does not exist on 10.13. */ | 66 | /* This API does not exist on 10.13. */ |
56 | if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) { | 67 | if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) { |
@@ -66,6 +77,14 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) { | |||
66 | return ver.x > 10 || ver.y > 13;*/ | 77 | return ver.x > 10 || ver.y > 13;*/ |
67 | } | 78 | } |
68 | 79 | ||
80 | static void ignoreImmediateKeyDownEvents_(void) { | ||
81 | /* SDL ignores menu key equivalents so the keydown events will be posted regardless. | ||
82 | However, we shouldn't double-activate menu items when a shortcut key is used in our | ||
83 | widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to | ||
84 | ignore the immediately following key down events. */ | ||
85 | get_Window()->focusGainedAt = SDL_GetTicks(); | ||
86 | } | ||
87 | |||
69 | /*----------------------------------------------------------------------------------------------*/ | 88 | /*----------------------------------------------------------------------------------------------*/ |
70 | 89 | ||
71 | @interface CommandButton : NSCustomTouchBarItem { | 90 | @interface CommandButton : NSCustomTouchBarItem { |
@@ -135,11 +154,60 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) { | |||
135 | 154 | ||
136 | /*----------------------------------------------------------------------------------------------*/ | 155 | /*----------------------------------------------------------------------------------------------*/ |
137 | 156 | ||
157 | @interface MenuCommands : NSObject { | ||
158 | NSMutableDictionary<NSString *, NSString *> *commands; | ||
159 | iWidget *source; | ||
160 | } | ||
161 | @end | ||
162 | |||
163 | @implementation MenuCommands | ||
164 | |||
165 | - (id)init { | ||
166 | commands = [[NSMutableDictionary<NSString *, NSString *> alloc] init]; | ||
167 | source = NULL; | ||
168 | return self; | ||
169 | } | ||
170 | |||
171 | - (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem { | ||
172 | [commands setObject:command forKey:[menuItem title]]; | ||
173 | } | ||
174 | |||
175 | - (void)setSource:(iWidget *)widget { | ||
176 | source = widget; | ||
177 | } | ||
178 | |||
179 | - (void)clear { | ||
180 | [commands removeAllObjects]; | ||
181 | } | ||
182 | |||
183 | - (NSString *)commandForMenuItem:(NSMenuItem *)menuItem { | ||
184 | return [commands objectForKey:[menuItem title]]; | ||
185 | } | ||
186 | |||
187 | - (void)postMenuItemCommand:(id)sender { | ||
188 | NSString *command = [commands objectForKey:[(NSMenuItem *)sender title]]; | ||
189 | if (command) { | ||
190 | const char *cstr = [command cStringUsingEncoding:NSUTF8StringEncoding]; | ||
191 | if (source) { | ||
192 | postCommand_Widget(source, "%s", cstr); | ||
193 | } | ||
194 | else { | ||
195 | postCommand_Root(NULL, cstr); | ||
196 | } | ||
197 | ignoreImmediateKeyDownEvents_(); | ||
198 | } | ||
199 | } | ||
200 | |||
201 | @end | ||
202 | |||
203 | /*----------------------------------------------------------------------------------------------*/ | ||
204 | |||
138 | @interface MyDelegate : NSResponder<NSApplicationDelegate, NSTouchBarDelegate> { | 205 | @interface MyDelegate : NSResponder<NSApplicationDelegate, NSTouchBarDelegate> { |
139 | enum iTouchBarVariant touchBarVariant; | 206 | enum iTouchBarVariant touchBarVariant; |
140 | NSString *currentAppearanceName; | 207 | NSString *currentAppearanceName; |
141 | NSObject<NSApplicationDelegate> *sdlDelegate; | 208 | NSObject<NSApplicationDelegate> *sdlDelegate; |
142 | NSMutableDictionary<NSString *, NSString*> *menuCommands; | 209 | //NSMutableDictionary<NSString *, NSString*> *menuCommands; |
210 | MenuCommands *menuCommands; | ||
143 | } | 211 | } |
144 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl; | 212 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl; |
145 | - (NSTouchBar *)makeTouchBar; | 213 | - (NSTouchBar *)makeTouchBar; |
@@ -154,7 +222,7 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) { | |||
154 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl { | 222 | - (id)initWithSDLDelegate:(NSObject<NSApplicationDelegate> *)sdl { |
155 | [super init]; | 223 | [super init]; |
156 | currentAppearanceName = nil; | 224 | currentAppearanceName = nil; |
157 | menuCommands = [[NSMutableDictionary<NSString *, NSString *> alloc] init]; | 225 | menuCommands = [[MenuCommands alloc] init]; |
158 | touchBarVariant = default_TouchBarVariant; | 226 | touchBarVariant = default_TouchBarVariant; |
159 | sdlDelegate = sdl; | 227 | sdlDelegate = sdl; |
160 | return self; | 228 | return self; |
@@ -171,6 +239,14 @@ iBool shouldDefaultToMetalRenderer_MacOS(void) { | |||
171 | self.touchBar = nil; | 239 | self.touchBar = nil; |
172 | } | 240 | } |
173 | 241 | ||
242 | - (MenuCommands *)menuCommands { | ||
243 | return menuCommands; | ||
244 | } | ||
245 | |||
246 | - (void)postMenuItemCommand:(id)sender { | ||
247 | [menuCommands postMenuItemCommand:sender]; | ||
248 | } | ||
249 | |||
174 | static void appearanceChanged_MacOS_(NSString *name) { | 250 | static void appearanceChanged_MacOS_(NSString *name) { |
175 | const iBool isDark = [name containsString:@"Dark"]; | 251 | const iBool isDark = [name containsString:@"Dark"]; |
176 | const iBool isHighContrast = [name containsString:@"HighContrast"]; | 252 | const iBool isHighContrast = [name containsString:@"HighContrast"]; |
@@ -187,10 +263,6 @@ static void appearanceChanged_MacOS_(NSString *name) { | |||
187 | } | 263 | } |
188 | } | 264 | } |
189 | 265 | ||
190 | - (void)setCommand:(NSString *)command forMenuItem:(NSMenuItem *)menuItem { | ||
191 | [menuCommands setObject:command forKey:[menuItem title]]; | ||
192 | } | ||
193 | |||
194 | - (BOOL)application:(NSApplication *)app openFile:(NSString *)filename { | 266 | - (BOOL)application:(NSApplication *)app openFile:(NSString *)filename { |
195 | return [sdlDelegate application:app openFile:filename]; | 267 | return [sdlDelegate application:app openFile:filename]; |
196 | } | 268 | } |
@@ -247,31 +319,11 @@ static void appearanceChanged_MacOS_(NSString *name) { | |||
247 | ignoreImmediateKeyDownEvents_(); | 319 | ignoreImmediateKeyDownEvents_(); |
248 | } | 320 | } |
249 | 321 | ||
250 | static void ignoreImmediateKeyDownEvents_(void) { | ||
251 | /* SDL ignores menu key equivalents so the keydown events will be posted regardless. | ||
252 | However, we shouldn't double-activate menu items when a shortcut key is used in our | ||
253 | widgets. Quite a kludge: take advantage of Window's focus-acquisition threshold to | ||
254 | ignore the immediately following key down events. */ | ||
255 | get_Window()->focusGainedAt = SDL_GetTicks(); | ||
256 | } | ||
257 | |||
258 | - (void)closeTab { | 322 | - (void)closeTab { |
259 | postCommand_App("tabs.close"); | 323 | postCommand_App("tabs.close"); |
260 | ignoreImmediateKeyDownEvents_(); | 324 | ignoreImmediateKeyDownEvents_(); |
261 | } | 325 | } |
262 | 326 | ||
263 | - (NSString *)commandForItem:(NSMenuItem *)menuItem { | ||
264 | return [menuCommands objectForKey:[menuItem title]]; | ||
265 | } | ||
266 | |||
267 | - (void)postMenuItemCommand:(id)sender { | ||
268 | NSString *command = [menuCommands objectForKey:[(NSMenuItem *)sender title]]; | ||
269 | if (command) { | ||
270 | postCommand_App([command cStringUsingEncoding:NSUTF8StringEncoding]); | ||
271 | ignoreImmediateKeyDownEvents_(); | ||
272 | } | ||
273 | } | ||
274 | |||
275 | - (void)sidebarModePressed:(id)sender { | 327 | - (void)sidebarModePressed:(id)sender { |
276 | NSSegmentedControl *seg = sender; | 328 | NSSegmentedControl *seg = sender; |
277 | postCommandf_App("sidebar.mode arg:%d toggle:1", (int) [seg selectedSegment]); | 329 | postCommandf_App("sidebar.mode arg:%d toggle:1", (int) [seg selectedSegment]); |
@@ -370,6 +422,11 @@ void setupApplication_MacOS(void) { | |||
370 | windowCloseItem.action = @selector(closeTab); | 422 | windowCloseItem.action = @selector(closeTab); |
371 | } | 423 | } |
372 | 424 | ||
425 | void hideTitleBar_MacOS(iWindow *window) { | ||
426 | NSWindow *w = nsWindow_(window->win); | ||
427 | w.styleMask = 0; /* borderless */ | ||
428 | } | ||
429 | |||
373 | void enableMenu_MacOS(const char *menuLabel, iBool enable) { | 430 | void enableMenu_MacOS(const char *menuLabel, iBool enable) { |
374 | menuLabel = translateCStr_Lang(menuLabel); | 431 | menuLabel = translateCStr_Lang(menuLabel); |
375 | NSApplication *app = [NSApplication sharedApplication]; | 432 | NSApplication *app = [NSApplication sharedApplication]; |
@@ -377,7 +434,6 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) { | |||
377 | NSString *label = [NSString stringWithUTF8String:menuLabel]; | 434 | NSString *label = [NSString stringWithUTF8String:menuLabel]; |
378 | NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]]; | 435 | NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]]; |
379 | [menuItem setEnabled:enable]; | 436 | [menuItem setEnabled:enable]; |
380 | [label release]; | ||
381 | } | 437 | } |
382 | 438 | ||
383 | void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { | 439 | void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { |
@@ -388,7 +444,7 @@ void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { | |||
388 | NSMenu *menu = mainMenuItem.submenu; | 444 | NSMenu *menu = mainMenuItem.submenu; |
389 | if (menu) { | 445 | if (menu) { |
390 | for (NSMenuItem *menuItem in menu.itemArray) { | 446 | for (NSMenuItem *menuItem in menu.itemArray) { |
391 | NSString *command = [myDel commandForItem:menuItem]; | 447 | NSString *command = [[myDel menuCommands] commandForMenuItem:menuItem]; |
392 | if (command) { | 448 | if (command) { |
393 | if (!iCmpStr([command cStringUsingEncoding:NSUTF8StringEncoding], | 449 | if (!iCmpStr([command cStringUsingEncoding:NSUTF8StringEncoding], |
394 | menuItemCommand)) { | 450 | menuItemCommand)) { |
@@ -468,35 +524,58 @@ void removeMenu_MacOS(int atIndex) { | |||
468 | [appMenu removeItemAtIndex:atIndex]; | 524 | [appMenu removeItemAtIndex:atIndex]; |
469 | } | 525 | } |
470 | 526 | ||
471 | void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *items, size_t count) { | 527 | enum iColorId removeColorEscapes_String(iString *d) { |
472 | NSApplication *app = [NSApplication sharedApplication]; | 528 | enum iColorId color = none_ColorId; |
473 | MyDelegate *myDel = (MyDelegate *) app.delegate; | 529 | for (;;) { |
474 | NSMenu *appMenu = [app mainMenu]; | 530 | const char *esc = strchr(cstr_String(d), '\v'); |
475 | menuLabel = translateCStr_Lang(menuLabel); | 531 | if (esc) { |
476 | NSMenuItem *mainItem = [appMenu insertItemWithTitle:[NSString stringWithUTF8String:menuLabel] | 532 | const char *endp; |
477 | action:nil | 533 | color = parseEscape_Color(esc, &endp); |
478 | keyEquivalent:@"" | 534 | remove_Block(&d->chars, esc - cstr_String(d), endp - esc); |
479 | atIndex:atIndex]; | ||
480 | NSMenu *menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menuLabel]]; | ||
481 | [menu setAutoenablesItems:NO]; | ||
482 | for (size_t i = 0; i < count; ++i) { | ||
483 | const char *label = translateCStr_Lang(items[i].label); | ||
484 | if (label[0] == '\v') { | ||
485 | /* Skip the formatting escape. */ | ||
486 | label += 2; | ||
487 | } | 535 | } |
536 | else break; | ||
537 | } | ||
538 | return color; | ||
539 | } | ||
540 | |||
541 | static void makeMenuItems_(NSMenu *menu, MenuCommands *commands, const iMenuItem *items, size_t n) { | ||
542 | for (size_t i = 0; i < n && items[i].label; ++i) { | ||
543 | const char *label = translateCStr_Lang(items[i].label); | ||
488 | if (equal_CStr(label, "---")) { | 544 | if (equal_CStr(label, "---")) { |
489 | [menu addItem:[NSMenuItem separatorItem]]; | 545 | [menu addItem:[NSMenuItem separatorItem]]; |
490 | } | 546 | } |
491 | else { | 547 | else { |
492 | const iBool hasCommand = (items[i].command && items[i].command[0]); | 548 | const iBool hasCommand = (items[i].command && items[i].command[0]); |
493 | NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:label] | 549 | iBool isChecked = iFalse; |
550 | iBool isDisabled = iFalse; | ||
551 | if (startsWith_CStr(label, "###")) { | ||
552 | isChecked = iTrue; | ||
553 | label += 3; | ||
554 | } | ||
555 | else if (startsWith_CStr(label, "///")) { | ||
556 | isDisabled = iTrue; | ||
557 | label += 3; | ||
558 | } | ||
559 | iString itemTitle; | ||
560 | initCStr_String(&itemTitle, label); | ||
561 | removeIconPrefix_String(&itemTitle); | ||
562 | if (removeColorEscapes_String(&itemTitle) == uiTextCaution_ColorId) { | ||
563 | // prependCStr_String(&itemTitle, "\u26a0\ufe0f "); | ||
564 | } | ||
565 | NSMenuItem *item = [menu addItemWithTitle:[NSString stringWithUTF8String:cstr_String(&itemTitle)] | ||
494 | action:(hasCommand ? @selector(postMenuItemCommand:) : nil) | 566 | action:(hasCommand ? @selector(postMenuItemCommand:) : nil) |
495 | keyEquivalent:@""]; | 567 | keyEquivalent:@""]; |
568 | deinit_String(&itemTitle); | ||
569 | [item setTarget:commands]; | ||
570 | if (isChecked) { | ||
571 | [item setState:NSControlStateValueOn]; | ||
572 | } | ||
573 | [item setEnabled:!isDisabled]; | ||
496 | int key = items[i].key; | 574 | int key = items[i].key; |
497 | int kmods = items[i].kmods; | 575 | int kmods = items[i].kmods; |
498 | if (hasCommand) { | 576 | if (hasCommand) { |
499 | [myDel setCommand:[NSString stringWithUTF8String:items[i].command] forMenuItem:item]; | 577 | [commands setCommand:[NSString stringWithUTF8String:items[i].command] |
578 | forMenuItem:item]; | ||
500 | /* Bindings may have a different key. */ | 579 | /* Bindings may have a different key. */ |
501 | const iBinding *bind = findCommand_Keys(items[i].command); | 580 | const iBinding *bind = findCommand_Keys(items[i].command); |
502 | if (bind && bind->id < builtIn_BindingId) { | 581 | if (bind && bind->id < builtIn_BindingId) { |
@@ -507,6 +586,20 @@ void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem * | |||
507 | setShortcut_NSMenuItem_(item, key, kmods); | 586 | setShortcut_NSMenuItem_(item, key, kmods); |
508 | } | 587 | } |
509 | } | 588 | } |
589 | } | ||
590 | |||
591 | void insertMenuItems_MacOS(const char *menuLabel, int atIndex, const iMenuItem *items, size_t count) { | ||
592 | NSApplication *app = [NSApplication sharedApplication]; | ||
593 | MyDelegate *myDel = (MyDelegate *) app.delegate; | ||
594 | NSMenu *appMenu = [app mainMenu]; | ||
595 | menuLabel = translateCStr_Lang(menuLabel); | ||
596 | NSMenuItem *mainItem = [appMenu insertItemWithTitle:[NSString stringWithUTF8String:menuLabel] | ||
597 | action:nil | ||
598 | keyEquivalent:@"" | ||
599 | atIndex:atIndex]; | ||
600 | NSMenu *menu = [[NSMenu alloc] initWithTitle:[NSString stringWithUTF8String:menuLabel]]; | ||
601 | [menu setAutoenablesItems:NO]; | ||
602 | makeMenuItems_(menu, [myDel menuCommands], items, count); | ||
510 | [mainItem setSubmenu:menu]; | 603 | [mainItem setSubmenu:menu]; |
511 | [menu release]; | 604 | [menu release]; |
512 | } | 605 | } |
@@ -527,7 +620,7 @@ void handleCommand_MacOS(const char *cmd) { | |||
527 | if (menu) { | 620 | if (menu) { |
528 | int itemIndex = 0; | 621 | int itemIndex = 0; |
529 | for (NSMenuItem *menuItem in menu.itemArray) { | 622 | for (NSMenuItem *menuItem in menu.itemArray) { |
530 | NSString *command = [myDel commandForItem:menuItem]; | 623 | NSString *command = [[myDel menuCommands] commandForMenuItem:menuItem]; |
531 | if (!command && mainIndex == 6 && itemIndex == 0) { | 624 | if (!command && mainIndex == 6 && itemIndex == 0) { |
532 | /* Window > Close */ | 625 | /* Window > Close */ |
533 | command = @"tabs.close"; | 626 | command = @"tabs.close"; |
@@ -553,3 +646,40 @@ void handleCommand_MacOS(const char *cmd) { | |||
553 | void log_MacOS(const char *msg) { | 646 | void log_MacOS(const char *msg) { |
554 | NSLog(@"%s", msg); | 647 | NSLog(@"%s", msg); |
555 | } | 648 | } |
649 | |||
650 | void showPopupMenu_MacOS(iWidget *source, iInt2 windowCoord, const iMenuItem *items, size_t n) { | ||
651 | NSMenu * menu = [[NSMenu alloc] init]; | ||
652 | MenuCommands *menuCommands = [[MenuCommands alloc] init]; | ||
653 | iWindow * window = as_Window(mainWindow_App()); | ||
654 | NSWindow * nsWindow = nsWindow_(window->win); | ||
655 | /* View coordinates are flipped. */ | ||
656 | iBool isCentered = iFalse; | ||
657 | if (isEqual_I2(windowCoord, zero_I2())) { | ||
658 | windowCoord = divi_I2(window->size, 2); | ||
659 | isCentered = iTrue; | ||
660 | } | ||
661 | windowCoord.y = window->size.y - windowCoord.y; | ||
662 | windowCoord = divf_I2(windowCoord, window->pixelRatio); | ||
663 | NSPoint screenPoint = [nsWindow convertPointToScreen:(CGPoint){ windowCoord.x, windowCoord.y }]; | ||
664 | makeMenuItems_(menu, menuCommands, items, n); | ||
665 | [menuCommands setSource:source]; | ||
666 | if (isCentered) { | ||
667 | NSSize menuSize = [menu size]; | ||
668 | screenPoint.x -= menuSize.width / 2; | ||
669 | screenPoint.y += menuSize.height / 2; | ||
670 | } | ||
671 | [menu setAutoenablesItems:NO]; | ||
672 | [menu popUpMenuPositioningItem:nil atLocation:screenPoint inView:nil]; | ||
673 | [menu release]; | ||
674 | [menuCommands release]; | ||
675 | /* The right mouse button has now been released so let SDL know about it. The button up event | ||
676 | was consumed by the popup menu so it got never passed to SDL. */ | ||
677 | SEL sel = NSSelectorFromString(@"syncMouseButtonState"); /* custom method */ | ||
678 | if ([[nsWindow delegate] respondsToSelector:sel]) { | ||
679 | NSInvocation *call = [NSInvocation invocationWithMethodSignature: | ||
680 | [NSMethodSignature signatureWithObjCTypes:"v@:"]]; | ||
681 | [call setSelector:sel]; | ||
682 | [call invokeWithTarget:[nsWindow delegate]]; | ||
683 | } | ||
684 | } | ||
685 | |||
diff --git a/src/media.c b/src/media.c index eb4a8311..636cd91f 100644 --- a/src/media.c +++ b/src/media.c | |||
@@ -30,6 +30,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "stb_image.h" | 30 | #include "stb_image.h" |
31 | #include "stb_image_resize.h" | 31 | #include "stb_image_resize.h" |
32 | 32 | ||
33 | #if defined (LAGRANGE_ENABLE_WEBP) | ||
34 | # include <webp/decode.h> | ||
35 | #endif | ||
36 | |||
33 | #include <the_Foundation/file.h> | 37 | #include <the_Foundation/file.h> |
34 | #include <the_Foundation/ptrarray.h> | 38 | #include <the_Foundation/ptrarray.h> |
35 | #include <SDL_hints.h> | 39 | #include <SDL_hints.h> |
@@ -83,16 +87,70 @@ void deinit_GmImage(iGmImage *d) { | |||
83 | deinit_GmMediaProps_(&d->props); | 87 | deinit_GmMediaProps_(&d->props); |
84 | } | 88 | } |
85 | 89 | ||
90 | static void applyImageStyle_(enum iImageStyle style, iInt2 size, uint8_t *imgData) { | ||
91 | if (style == original_ImageStyle) { | ||
92 | return; | ||
93 | } | ||
94 | uint8_t *pos = imgData; | ||
95 | size_t numPixels = size.x * size.y; | ||
96 | float brighten = 0.0f; | ||
97 | if (style == bgFg_ImageStyle) { | ||
98 | iColor dark = get_Color(tmBackground_ColorId); | ||
99 | iColor light = get_Color(tmParagraph_ColorId); | ||
100 | if (hsl_Color(dark).lum > hsl_Color(light).lum) { | ||
101 | iSwap(iColor, dark, light); | ||
102 | } | ||
103 | while (numPixels-- > 0) { | ||
104 | iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); | ||
105 | const float s = 1.0f - hsl.lum; | ||
106 | const float t = hsl.lum; | ||
107 | pos[0] = dark.r * s + light.r * t; | ||
108 | pos[1] = dark.g * s + light.g * t; | ||
109 | pos[2] = dark.b * s + light.b * t; | ||
110 | pos += 4; | ||
111 | } | ||
112 | return; | ||
113 | } | ||
114 | iColor colorize = (iColor){ 255, 255, 255, 255 }; | ||
115 | if (style != grayscale_ImageStyle) { | ||
116 | colorize = get_Color(style == textColorized_ImageStyle ? tmParagraph_ColorId | ||
117 | : tmPreformatted_ColorId); | ||
118 | /* Compensate for change in mid-tones. */ | ||
119 | const int colMax = iMax(iMax(colorize.r, colorize.g), colorize.b); | ||
120 | brighten = iClamp(1.0f - (colorize.r + colorize.g + colorize.b) / (colMax * 3), 0.0f, 0.5f); | ||
121 | } | ||
122 | iHSLColor hslColorize = hsl_Color(colorize); | ||
123 | while (numPixels-- > 0) { | ||
124 | iHSLColor hsl = hsl_Color((iColor){ pos[0], pos[1], pos[2], 255 }); | ||
125 | iHSLColor out = { hslColorize.hue, hslColorize.sat, hsl.lum, 1.0f }; | ||
126 | out.lum = powf(out.lum, 1.0f + brighten * 2); | ||
127 | iColor outRgb = rgb_HSLColor(out); | ||
128 | pos[0] = powf(outRgb.r / 255.0f, 1.0f - brighten * 0.75f) * 255; | ||
129 | pos[1] = powf(outRgb.g / 255.0f, 1.0f - brighten * 0.75f) * 255; | ||
130 | pos[2] = powf(outRgb.b / 255.0f, 1.0f - brighten * 0.75f) * 255; | ||
131 | pos += 4; | ||
132 | } | ||
133 | } | ||
134 | |||
86 | void makeTexture_GmImage(iGmImage *d) { | 135 | void makeTexture_GmImage(iGmImage *d) { |
87 | iBlock *data = &d->partialData; | 136 | iBlock *data = &d->partialData; |
88 | d->numBytes = size_Block(data); | 137 | d->numBytes = size_Block(data); |
89 | uint8_t *imgData = stbi_load_from_memory( | 138 | uint8_t *imgData = NULL; |
90 | constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); | 139 | if (cmp_String(&d->props.mime, "image/webp") == 0) { |
140 | #if defined (LAGRANGE_ENABLE_WEBP) | ||
141 | imgData = WebPDecodeRGBA(constData_Block(data), size_Block(data), &d->size.x, &d->size.y); | ||
142 | #endif | ||
143 | } | ||
144 | else { | ||
145 | imgData = stbi_load_from_memory( | ||
146 | constData_Block(data), size_Block(data), &d->size.x, &d->size.y, NULL, 4); | ||
147 | } | ||
91 | if (!imgData) { | 148 | if (!imgData) { |
92 | d->size = zero_I2(); | 149 | d->size = zero_I2(); |
93 | d->texture = NULL; | 150 | d->texture = NULL; |
94 | } | 151 | } |
95 | else { | 152 | else { |
153 | applyImageStyle_(prefs_App()->imageStyle, d->size, imgData); | ||
96 | /* TODO: Save some memory by checking if the alpha channel is actually in use. */ | 154 | /* TODO: Save some memory by checking if the alpha channel is actually in use. */ |
97 | iWindow *window = get_Window(); | 155 | iWindow *window = get_Window(); |
98 | iInt2 texSize = d->size; | 156 | iInt2 texSize = d->size; |
diff --git a/src/prefs.c b/src/prefs.c index ef1ce1b0..088cc7bc 100644 --- a/src/prefs.c +++ b/src/prefs.c | |||
@@ -62,6 +62,7 @@ void init_Prefs(iPrefs *d) { | |||
62 | d->quoteIcon = iTrue; | 62 | d->quoteIcon = iTrue; |
63 | d->centerShortDocs = iTrue; | 63 | d->centerShortDocs = iTrue; |
64 | d->plainTextWrap = iTrue; | 64 | d->plainTextWrap = iTrue; |
65 | d->imageStyle = original_ImageStyle; | ||
65 | d->docThemeDark = colorfulDark_GmDocumentTheme; | 66 | d->docThemeDark = colorfulDark_GmDocumentTheme; |
66 | d->docThemeLight = white_GmDocumentTheme; | 67 | d->docThemeLight = white_GmDocumentTheme; |
67 | d->saturation = 1.0f; | 68 | d->saturation = 1.0f; |
diff --git a/src/prefs.h b/src/prefs.h index a947a595..87c9a6e6 100644 --- a/src/prefs.h +++ b/src/prefs.h | |||
@@ -86,6 +86,7 @@ struct Impl_Prefs { | |||
86 | iBool quoteIcon; | 86 | iBool quoteIcon; |
87 | iBool centerShortDocs; | 87 | iBool centerShortDocs; |
88 | iBool plainTextWrap; | 88 | iBool plainTextWrap; |
89 | enum iImageStyle imageStyle; | ||
89 | /* Colors */ | 90 | /* Colors */ |
90 | enum iGmDocumentTheme docThemeDark; | 91 | enum iGmDocumentTheme docThemeDark; |
91 | enum iGmDocumentTheme docThemeLight; | 92 | enum iGmDocumentTheme docThemeLight; |
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index a8346e19..f4dfdefa 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c | |||
@@ -31,6 +31,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
31 | #include "text.h" | 31 | #include "text.h" |
32 | #include "ui/util.h" | 32 | #include "ui/util.h" |
33 | 33 | ||
34 | #if defined (iPlatformAppleMobile) | ||
35 | # include "ios.h" | ||
36 | #endif | ||
37 | |||
34 | #include <the_Foundation/file.h> | 38 | #include <the_Foundation/file.h> |
35 | #include <the_Foundation/tlsrequest.h> | 39 | #include <the_Foundation/tlsrequest.h> |
36 | #include <the_Foundation/path.h> | 40 | #include <the_Foundation/path.h> |
@@ -75,7 +79,7 @@ static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *dat | |||
75 | deinit_String(&pem); | 79 | deinit_String(&pem); |
76 | /* Update the labels. */ { | 80 | /* Update the labels. */ { |
77 | if (d->cert && !isEmpty_TlsCertificate(d->cert)) { | 81 | if (d->cert && !isEmpty_TlsCertificate(d->cert)) { |
78 | setTextCStr_LabelWidget( | 82 | updateTextCStr_LabelWidget( |
79 | d->crtLabel, | 83 | d->crtLabel, |
80 | format_CStr("%s%s", | 84 | format_CStr("%s%s", |
81 | uiTextAction_ColorEscape, | 85 | uiTextAction_ColorEscape, |
@@ -83,19 +87,19 @@ static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *dat | |||
83 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId); | 87 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextAction_ColorId); |
84 | } | 88 | } |
85 | else { | 89 | else { |
86 | setTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "${dlg.certimport.nocert}"); | 90 | updateTextCStr_LabelWidget(d->crtLabel, uiTextCaution_ColorEscape "${dlg.certimport.nocert}"); |
87 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); | 91 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); |
88 | } | 92 | } |
89 | if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) { | 93 | if (d->cert && hasPrivateKey_TlsCertificate(d->cert)) { |
90 | iString *fng = collect_String( | 94 | iString *fng = collect_String( |
91 | hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert)))); | 95 | hexEncode_Block(collect_Block(privateKeyFingerprint_TlsCertificate(d->cert)))); |
92 | insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1); | 96 | insertData_Block(&fng->chars, size_String(fng) / 2, "\n", 1); |
93 | setTextCStr_LabelWidget( | 97 | updateTextCStr_LabelWidget( |
94 | d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng))); | 98 | d->keyLabel, format_CStr("%s%s", uiTextAction_ColorEscape, cstr_String(fng))); |
95 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId); | 99 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextAction_ColorId); |
96 | } | 100 | } |
97 | else { | 101 | else { |
98 | setTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "${dlg.certimport.nokey}"); | 102 | updateTextCStr_LabelWidget(d->keyLabel, uiTextCaution_ColorEscape "${dlg.certimport.nokey}"); |
99 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); | 103 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); |
100 | } | 104 | } |
101 | } | 105 | } |
@@ -104,61 +108,85 @@ static iBool tryImport_CertImportWidget_(iCertImportWidget *d, const iBlock *dat | |||
104 | 108 | ||
105 | void init_CertImportWidget(iCertImportWidget *d) { | 109 | void init_CertImportWidget(iCertImportWidget *d) { |
106 | iWidget *w = as_Widget(d); | 110 | iWidget *w = as_Widget(d); |
111 | const iMenuItem actions[] = { | ||
112 | #if defined (iPlatformAppleMobile) | ||
113 | { "${dlg.certimport.pickfile}", 0, 0, "certimport.pickfile" }, | ||
114 | { "---" }, | ||
115 | #endif | ||
116 | { "${cancel}" }, | ||
117 | { uiTextAction_ColorEscape "${dlg.certimport.import}", | ||
118 | SDLK_RETURN, KMOD_PRIMARY, | ||
119 | "certimport.accept" } | ||
120 | }; | ||
107 | init_Widget(w); | 121 | init_Widget(w); |
108 | setId_Widget(w, "certimport"); | 122 | setId_Widget(w, "certimport"); |
109 | d->cert = NULL; | 123 | d->cert = NULL; |
110 | /* This should behave similar to sheets. */ | 124 | if (isUsingPanelLayout_Mobile()) { |
111 | useSheetStyle_Widget(w); | 125 | initPanels_Mobile(w, NULL, (iMenuItem[]){ |
112 | addChildFlags_Widget( | 126 | { "title id:heading.certimport" }, |
113 | w, | 127 | { format_CStr("label id:certimport.info text:%s", infoText_) }, |
114 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), | 128 | //{ "padding" }, |
115 | frameless_WidgetFlag); | 129 | { "label id:certimport.crt nowrap:1 frame:1" }, |
116 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); | 130 | { "padding arg:0.25" }, |
117 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 131 | { "label id:certimport.key nowrap:1 frame:1" }, |
118 | d->crtLabel = new_LabelWidget("", NULL); { | 132 | { "heading text:${dlg.certimport.notes}" }, |
133 | { "input id:certimport.notes hint:hint.certimport.description noheading:1" }, | ||
134 | { NULL } | ||
135 | }, actions, iElemCount(actions)); | ||
136 | d->info = findChild_Widget(w, "certimport.info"); | ||
137 | d->crtLabel = findChild_Widget(w, "certimport.crt"); | ||
138 | d->keyLabel = findChild_Widget(w, "certimport.key"); | ||
139 | d->notes = findChild_Widget(w, "certimport.notes"); | ||
119 | setFont_LabelWidget(d->crtLabel, uiContent_FontId); | 140 | setFont_LabelWidget(d->crtLabel, uiContent_FontId); |
120 | addChildFlags_Widget(w, iClob(d->crtLabel), 0); | ||
121 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); | ||
122 | } | ||
123 | d->keyLabel = new_LabelWidget("", NULL); { | ||
124 | setFont_LabelWidget(d->keyLabel, uiContent_FontId); | 141 | setFont_LabelWidget(d->keyLabel, uiContent_FontId); |
125 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 142 | setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(-1, gap_UI * 12)); |
126 | addChildFlags_Widget(w, iClob(d->keyLabel), 0); | 143 | setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(-1, gap_UI * 12)); |
127 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); | ||
128 | } | 144 | } |
129 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 145 | else { |
130 | /* TODO: Use makeTwoColumnWidget_() */ | 146 | /* This should behave similar to sheets. */ |
131 | iWidget *page = new_Widget(); { | 147 | useSheetStyle_Widget(w); |
132 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | 148 | addChildFlags_Widget( |
133 | iWidget *headings = addChildFlags_Widget( | 149 | w, |
134 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 150 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.certimport}", NULL)), |
135 | iWidget *values = addChildFlags_Widget( | 151 | frameless_WidgetFlag); |
136 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 152 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget(infoText_, NULL)), frameless_WidgetFlag); |
137 | // addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.certimport.notes}"))); | 153 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); |
138 | // addChild_Widget(values, iClob(d->notes = new_InputWidget(0))); | 154 | d->crtLabel = new_LabelWidget("", NULL); { |
139 | // setHint_InputWidget(d->notes, "${hint.certimport.description}"); | 155 | setFont_LabelWidget(d->crtLabel, uiContent_FontId); |
140 | addTwoColumnDialogInputField_Widget( | 156 | addChildFlags_Widget(w, iClob(d->crtLabel), 0); |
141 | headings, | 157 | } |
142 | values, | 158 | d->keyLabel = new_LabelWidget("", NULL); { |
143 | "${dlg.certimport.notes}", | 159 | setFont_LabelWidget(d->keyLabel, uiContent_FontId); |
144 | "", | 160 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); |
145 | iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); | 161 | addChildFlags_Widget(w, iClob(d->keyLabel), 0); |
146 | as_Widget(d->notes)->rect.size.x = gap_UI * 70; | 162 | } |
163 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
164 | /* TODO: Use makeTwoColumnWidget_() */ | ||
165 | iWidget *page = new_Widget(); { | ||
166 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
167 | iWidget *headings = addChildFlags_Widget( | ||
168 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
169 | iWidget *values = addChildFlags_Widget( | ||
170 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
171 | addTwoColumnDialogInputField_Widget( | ||
172 | headings, | ||
173 | values, | ||
174 | "${dlg.certimport.notes}", | ||
175 | "", | ||
176 | iClob(d->notes = newHint_InputWidget(0, "${hint.certimport.description}"))); | ||
177 | as_Widget(d->notes)->rect.size.x = gap_UI * 70; | ||
178 | } | ||
179 | addChild_Widget(w, iClob(page)); | ||
180 | arrange_Widget(w); | ||
181 | setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
182 | setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
183 | /* Buttons. */ | ||
184 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
185 | iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); | ||
186 | addChild_Widget(w, iClob(buttons)); | ||
147 | } | 187 | } |
148 | addChild_Widget(w, iClob(page)); | 188 | setFrameColor_Widget(as_Widget(d->crtLabel), uiTextCaution_ColorId); |
149 | arrange_Widget(w); | 189 | setFrameColor_Widget(as_Widget(d->keyLabel), uiTextCaution_ColorId); |
150 | setFixedSize_Widget(as_Widget(d->crtLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
151 | setFixedSize_Widget(as_Widget(d->keyLabel), init_I2(width_Widget(w) - 6.5 * gap_UI, gap_UI * 12)); | ||
152 | /* Buttons. */ | ||
153 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
154 | iWidget *buttons = makeDialogButtons_Widget( | ||
155 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
156 | { uiTextAction_ColorEscape "${dlg.certimport.import}", | ||
157 | SDLK_RETURN, | ||
158 | KMOD_PRIMARY, | ||
159 | "certimport.accept" } }, | ||
160 | 2); | ||
161 | addChild_Widget(w, iClob(buttons)); | ||
162 | if (deviceType_App() != desktop_AppDeviceType) { | 190 | if (deviceType_App() != desktop_AppDeviceType) { |
163 | /* Try auto-pasting. */ | 191 | /* Try auto-pasting. */ |
164 | postCommand_App("certimport.paste"); | 192 | postCommand_App("certimport.paste"); |
@@ -191,6 +219,25 @@ static iBool tryImportFromClipboard_CertImportWidget_(iCertImportWidget *d) { | |||
191 | return tryImport_CertImportWidget_(d, collect_Block(newCStr_Block(SDL_GetClipboardText()))); | 219 | return tryImport_CertImportWidget_(d, collect_Block(newCStr_Block(SDL_GetClipboardText()))); |
192 | } | 220 | } |
193 | 221 | ||
222 | static iBool tryImportFromFile_CertImportWidget_(iCertImportWidget *d, const iString *path) { | ||
223 | iBool success = iFalse; | ||
224 | iFile *f = new_File(path); | ||
225 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | ||
226 | if (tryImport_CertImportWidget_(d, collect_Block(readAll_File(f)))) { | ||
227 | success = iTrue; | ||
228 | if (isComplete_CertImportWidget_(d)) { | ||
229 | setFocus_Widget(as_Widget(d->notes)); | ||
230 | } | ||
231 | } | ||
232 | else { | ||
233 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.dropped}", | ||
234 | "${dlg.certimport.notfound}"); | ||
235 | } | ||
236 | } | ||
237 | iRelease(f); | ||
238 | return success; | ||
239 | } | ||
240 | |||
194 | static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) { | 241 | static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Event *ev) { |
195 | iWidget *w = as_Widget(d); | 242 | iWidget *w = as_Widget(d); |
196 | if (ev->type == SDL_KEYDOWN) { | 243 | if (ev->type == SDL_KEYDOWN) { |
@@ -232,21 +279,22 @@ static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Even | |||
232 | } | 279 | } |
233 | return iTrue; | 280 | return iTrue; |
234 | } | 281 | } |
235 | if (ev->type == SDL_DROPFILE) { | 282 | #if defined (iPlatformAppleMobile) |
236 | const iString *name = collectNewCStr_String(ev->drop.file); | 283 | if (isCommand_UserEvent(ev, "certimport.pickfile")) { |
237 | iFile *f = new_File(name); | 284 | const char *cmd = command_UserEvent(ev); |
238 | if (open_File(f, readOnly_FileMode | text_FileMode)) { | 285 | if (hasLabel_Command(cmd, "path")) { |
239 | if (tryImport_CertImportWidget_(d, collect_Block(readAll_File(f)))) { | 286 | const iString *path = collect_String(suffix_Command(cmd, "path")); |
240 | if (isComplete_CertImportWidget_(d)) { | 287 | tryImportFromFile_CertImportWidget_(d, path); |
241 | setFocus_Widget(as_Widget(d->notes)); | 288 | remove(cstr_String(path)); /* it is a temporary copy */ |
242 | } | 289 | } |
243 | } | 290 | else { |
244 | else { | 291 | pickFile_iOS("certimport.pickfile"); |
245 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.dropped}", | ||
246 | "${dlg.certimport.notfound}"); | ||
247 | } | ||
248 | } | 292 | } |
249 | iRelease(f); | 293 | return iTrue; |
294 | } | ||
295 | #endif | ||
296 | if (ev->type == SDL_DROPFILE) { | ||
297 | tryImportFromFile_CertImportWidget_(d, collectNewCStr_String(ev->drop.file)); | ||
250 | return iTrue; | 298 | return iTrue; |
251 | } | 299 | } |
252 | return processEvent_Widget(w, ev); | 300 | return processEvent_Widget(w, ev); |
diff --git a/src/ui/color.c b/src/ui/color.c index 656de6f0..072c4b3f 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -85,6 +85,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
85 | const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId); | 85 | const int accentLo = (prefs->accent == cyan_ColorAccent ? teal_ColorId : brown_ColorId); |
86 | const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId); | 86 | const int altAccentHi = (prefs->accent == cyan_ColorAccent ? orange_ColorId : cyan_ColorId); |
87 | const int altAccentLo = (prefs->accent == cyan_ColorAccent ? brown_ColorId : teal_ColorId); | 87 | const int altAccentLo = (prefs->accent == cyan_ColorAccent ? brown_ColorId : teal_ColorId); |
88 | const iColor accentMid = mix_Color(get_Color(accentHi), get_Color(accentLo), 0.5f); | ||
88 | const iColor altAccentMid = mix_Color(get_Color(altAccentHi), get_Color(altAccentLo), 0.5f); | 89 | const iColor altAccentMid = mix_Color(get_Color(altAccentHi), get_Color(altAccentLo), 0.5f); |
89 | switch (theme) { | 90 | switch (theme) { |
90 | case pureBlack_ColorTheme: { | 91 | case pureBlack_ColorTheme: { |
@@ -124,7 +125,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
124 | copy_(uiInputTextFocused_ColorId, white_ColorId); | 125 | copy_(uiInputTextFocused_ColorId, white_ColorId); |
125 | copy_(uiInputFrame_ColorId, gray25_ColorId); | 126 | copy_(uiInputFrame_ColorId, gray25_ColorId); |
126 | copy_(uiInputFrameHover_ColorId, accentHi); | 127 | copy_(uiInputFrameHover_ColorId, accentHi); |
127 | set_Color(uiInputFrameFocused_ColorId, altAccentMid); | 128 | copy_(uiInputFrameFocused_ColorId, accentLo); |
128 | copy_(uiInputCursor_ColorId, altAccentHi); | 129 | copy_(uiInputCursor_ColorId, altAccentHi); |
129 | copy_(uiInputCursorText_ColorId, black_ColorId); | 130 | copy_(uiInputCursorText_ColorId, black_ColorId); |
130 | copy_(uiHeading_ColorId, accentHi); | 131 | copy_(uiHeading_ColorId, accentHi); |
@@ -132,8 +133,8 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
132 | copy_(uiIcon_ColorId, accentHi); | 133 | copy_(uiIcon_ColorId, accentHi); |
133 | copy_(uiIconHover_ColorId, accentHi); | 134 | copy_(uiIconHover_ColorId, accentHi); |
134 | copy_(uiSeparator_ColorId, gray25_ColorId); | 135 | copy_(uiSeparator_ColorId, gray25_ColorId); |
135 | copy_(uiMarked_ColorId, altAccentLo); | 136 | copy_(uiMarked_ColorId, accentLo); |
136 | copy_(uiMatching_ColorId, accentLo); | 137 | copy_(uiMatching_ColorId, altAccentLo); |
137 | break; | 138 | break; |
138 | } | 139 | } |
139 | default: | 140 | default: |
@@ -177,7 +178,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
177 | get_Color(altAccentHi), 0.15f)); | 178 | get_Color(altAccentHi), 0.15f)); |
178 | copy_(uiInputFrame_ColorId, uiInputBackground_ColorId); | 179 | copy_(uiInputFrame_ColorId, uiInputBackground_ColorId); |
179 | copy_(uiInputFrameHover_ColorId, accentHi); | 180 | copy_(uiInputFrameHover_ColorId, accentHi); |
180 | set_Color(uiInputFrameFocused_ColorId, altAccentMid); | 181 | copy_(uiInputFrameFocused_ColorId, accentLo); |
181 | copy_(uiInputCursor_ColorId, altAccentHi); | 182 | copy_(uiInputCursor_ColorId, altAccentHi); |
182 | copy_(uiInputCursorText_ColorId, black_ColorId); | 183 | copy_(uiInputCursorText_ColorId, black_ColorId); |
183 | copy_(uiHeading_ColorId, accentHi); | 184 | copy_(uiHeading_ColorId, accentHi); |
@@ -185,8 +186,8 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
185 | copy_(uiIcon_ColorId, accentHi); | 186 | copy_(uiIcon_ColorId, accentHi); |
186 | copy_(uiIconHover_ColorId, accentHi); | 187 | copy_(uiIconHover_ColorId, accentHi); |
187 | copy_(uiSeparator_ColorId, black_ColorId); | 188 | copy_(uiSeparator_ColorId, black_ColorId); |
188 | copy_(uiMarked_ColorId, altAccentLo); | 189 | copy_(uiMarked_ColorId, accentLo); |
189 | copy_(uiMatching_ColorId, accentLo); | 190 | copy_(uiMatching_ColorId, altAccentLo); |
190 | break; | 191 | break; |
191 | } | 192 | } |
192 | case light_ColorTheme: | 193 | case light_ColorTheme: |
@@ -227,7 +228,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
227 | set_Color(uiInputFrame_ColorId, | 228 | set_Color(uiInputFrame_ColorId, |
228 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f)); | 229 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f)); |
229 | copy_(uiInputFrameHover_ColorId, accentLo); | 230 | copy_(uiInputFrameHover_ColorId, accentLo); |
230 | copy_(uiInputFrameFocused_ColorId, altAccentLo); | 231 | copy_(uiInputFrameFocused_ColorId, accentLo); |
231 | copy_(uiInputCursor_ColorId, altAccentLo); | 232 | copy_(uiInputCursor_ColorId, altAccentLo); |
232 | copy_(uiInputCursorText_ColorId, white_ColorId); | 233 | copy_(uiInputCursorText_ColorId, white_ColorId); |
233 | copy_(uiHeading_ColorId, accentLo); | 234 | copy_(uiHeading_ColorId, accentLo); |
@@ -236,8 +237,8 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
236 | copy_(uiIconHover_ColorId, accentLo); | 237 | copy_(uiIconHover_ColorId, accentLo); |
237 | set_Color(uiSeparator_ColorId, | 238 | set_Color(uiSeparator_ColorId, |
238 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f)); | 239 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.5f)); |
239 | copy_(uiMarked_ColorId, altAccentHi); | 240 | copy_(uiMarked_ColorId, accentHi); |
240 | copy_(uiMatching_ColorId, accentHi); | 241 | copy_(uiMatching_ColorId, altAccentHi); |
241 | break; | 242 | break; |
242 | case pureWhite_ColorTheme: | 243 | case pureWhite_ColorTheme: |
243 | copy_(uiBackground_ColorId, white_ColorId); | 244 | copy_(uiBackground_ColorId, white_ColorId); |
@@ -278,7 +279,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
278 | copy_(uiInputTextFocused_ColorId, black_ColorId); | 279 | copy_(uiInputTextFocused_ColorId, black_ColorId); |
279 | copy_(uiInputFrame_ColorId, gray50_ColorId); | 280 | copy_(uiInputFrame_ColorId, gray50_ColorId); |
280 | copy_(uiInputFrameHover_ColorId, accentLo); | 281 | copy_(uiInputFrameHover_ColorId, accentLo); |
281 | copy_(uiInputFrameFocused_ColorId, altAccentLo); | 282 | copy_(uiInputFrameFocused_ColorId, accentLo); |
282 | copy_(uiInputCursor_ColorId, altAccentLo); | 283 | copy_(uiInputCursor_ColorId, altAccentLo); |
283 | copy_(uiInputCursorText_ColorId, white_ColorId); | 284 | copy_(uiInputCursorText_ColorId, white_ColorId); |
284 | copy_(uiHeading_ColorId, accentLo); | 285 | copy_(uiHeading_ColorId, accentLo); |
@@ -287,8 +288,8 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
287 | copy_(uiIconHover_ColorId, accentLo); | 288 | copy_(uiIconHover_ColorId, accentLo); |
288 | set_Color(uiSeparator_ColorId, | 289 | set_Color(uiSeparator_ColorId, |
289 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.67f)); | 290 | mix_Color(get_Color(gray50_ColorId), get_Color(gray75_ColorId), 0.67f)); |
290 | copy_(uiMarked_ColorId, altAccentHi); | 291 | copy_(uiMarked_ColorId, accentHi); |
291 | copy_(uiMatching_ColorId, accentHi); | 292 | copy_(uiMatching_ColorId, altAccentHi); |
292 | break; | 293 | break; |
293 | } | 294 | } |
294 | set_Color(uiSubheading_ColorId, | 295 | set_Color(uiSubheading_ColorId, |
@@ -481,6 +482,24 @@ const char *escape_Color(int color) { | |||
481 | return format_CStr("\v%c", color + asciiBase_ColorEscape); | 482 | return format_CStr("\v%c", color + asciiBase_ColorEscape); |
482 | } | 483 | } |
483 | 484 | ||
485 | enum iColorId parseEscape_Color(const char *cstr, const char **endp) { | ||
486 | enum iColorId color = none_ColorId; | ||
487 | if (*cstr == '\v') { | ||
488 | cstr++; | ||
489 | color = 0; | ||
490 | if (*cstr == '\v') { | ||
491 | color += asciiExtended_ColorEscape; | ||
492 | cstr++; | ||
493 | } | ||
494 | color += *cstr - asciiBase_ColorEscape; | ||
495 | cstr++; | ||
496 | } | ||
497 | if (endp) { | ||
498 | *endp = cstr; | ||
499 | } | ||
500 | return color; | ||
501 | } | ||
502 | |||
484 | iHSLColor setSat_HSLColor(iHSLColor d, float sat) { | 503 | iHSLColor setSat_HSLColor(iHSLColor d, float sat) { |
485 | d.sat = iClamp(sat, 0, 1); | 504 | d.sat = iClamp(sat, 0, 1); |
486 | return d; | 505 | return d; |
diff --git a/src/ui/color.h b/src/ui/color.h index 37ec49eb..b6571c86 100644 --- a/src/ui/color.h +++ b/src/ui/color.h | |||
@@ -183,6 +183,7 @@ iLocalDef iBool isRegularText_ColorId(enum iColorId d) { | |||
183 | #define mask_ColorId 0x7f | 183 | #define mask_ColorId 0x7f |
184 | #define permanent_ColorId 0x80 /* cannot be changed via escapes */ | 184 | #define permanent_ColorId 0x80 /* cannot be changed via escapes */ |
185 | #define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ | 185 | #define fillBackground_ColorId 0x100 /* fill background with same color, but alpha 0 */ |
186 | #define opaque_ColorId 0x200 | ||
186 | 187 | ||
187 | #define asciiBase_ColorEscape 33 | 188 | #define asciiBase_ColorEscape 33 |
188 | #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) | 189 | #define asciiExtended_ColorEscape (128 - asciiBase_ColorEscape) |
@@ -249,4 +250,4 @@ void setThemePalette_Color (enum iColorTheme theme); | |||
249 | 250 | ||
250 | iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); | 251 | iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); |
251 | const char * escape_Color (int color); | 252 | const char * escape_Color (int color); |
252 | 253 | enum iColorId parseEscape_Color (const char *cstr, const char **endp); | |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 638906cf..5c1e473f 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -750,7 +750,7 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) { | |||
750 | if (document_App() != d) { | 750 | if (document_App() != d) { |
751 | return 0; | 751 | return 0; |
752 | } | 752 | } |
753 | if (get_Window()->isDrawFrozen) { | 753 | if (as_MainWindow(window_Widget(d))->isDrawFrozen) { |
754 | return 0; | 754 | return 0; |
755 | } | 755 | } |
756 | static const uint32_t invalidInterval_ = ~0u; | 756 | static const uint32_t invalidInterval_ = ~0u; |
@@ -934,7 +934,7 @@ static void updateWindowTitle_DocumentWidget_(const iDocumentWidget *d) { | |||
934 | iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); | 934 | iString *text = collect_String(joinCStr_StringArray(title, " \u2014 ")); |
935 | if (setWindow) { | 935 | if (setWindow) { |
936 | /* Longest version for the window title, and omit the icon. */ | 936 | /* Longest version for the window title, and omit the icon. */ |
937 | setTitle_Window(get_Window(), text); | 937 | setTitle_MainWindow(get_MainWindow(), text); |
938 | setWindow = iFalse; | 938 | setWindow = iFalse; |
939 | } | 939 | } |
940 | const iChar siteIcon = siteIcon_GmDocument(d->doc); | 940 | const iChar siteIcon = siteIcon_GmDocument(d->doc); |
@@ -1010,6 +1010,9 @@ static void documentRunsInvalidated_DocumentWidget_(iDocumentWidget *d) { | |||
1010 | } | 1010 | } |
1011 | 1011 | ||
1012 | iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { | 1012 | iBool isPinned_DocumentWidget_(const iDocumentWidget *d) { |
1013 | if (deviceType_App() == phone_AppDeviceType) { | ||
1014 | return iFalse; | ||
1015 | } | ||
1013 | if (d->flags & otherRootByDefault_DocumentWidgetFlag) { | 1016 | if (d->flags & otherRootByDefault_DocumentWidgetFlag) { |
1014 | return iTrue; | 1017 | return iTrue; |
1015 | } | 1018 | } |
@@ -1054,9 +1057,12 @@ static void documentWasChanged_DocumentWidget_(iDocumentWidget *d) { | |||
1054 | 1057 | ||
1055 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { | 1058 | void setSource_DocumentWidget(iDocumentWidget *d, const iString *source) { |
1056 | setUrl_GmDocument(d->doc, d->mod.url); | 1059 | setUrl_GmDocument(d->doc, d->mod.url); |
1060 | const int docWidth = documentWidth_DocumentWidget_(d); | ||
1061 | const int outsideMargin = (width_Widget(d) - docWidth) / 2; | ||
1057 | setSource_GmDocument(d->doc, | 1062 | setSource_GmDocument(d->doc, |
1058 | source, | 1063 | source, |
1059 | documentWidth_DocumentWidget_(d), | 1064 | docWidth, |
1065 | outsideMargin, | ||
1060 | isFinished_GmRequest(d->request) ? final_GmDocumentUpdate | 1066 | isFinished_GmRequest(d->request) ? final_GmDocumentUpdate |
1061 | : partial_GmDocumentUpdate); | 1067 | : partial_GmDocumentUpdate); |
1062 | documentWasChanged_DocumentWidget_(d); | 1068 | documentWasChanged_DocumentWidget_(d); |
@@ -1165,13 +1171,25 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1165 | iString *key = collectNew_String(); | 1171 | iString *key = collectNew_String(); |
1166 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1172 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
1167 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); | 1173 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); |
1168 | makeFooterButtons_DocumentWidget_( | 1174 | const char *mtype = mediaTypeFromFileExtension_String(d->mod.url); |
1169 | d, | 1175 | iArray items; |
1170 | (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), | 1176 | init_Array(&items, sizeof(iMenuItem)); |
1171 | 0, | 1177 | if (iCmpStr(mtype, "application/octet-stream")) { |
1172 | 0, | 1178 | pushBack_Array( |
1173 | "document.save" } }, | 1179 | &items, |
1174 | 1); | 1180 | &(iMenuItem){ translateCStr_Lang(format_CStr("View as \"%s\"", mtype)), |
1181 | SDLK_RETURN, | ||
1182 | 0, | ||
1183 | format_CStr("document.setmediatype mime:%s", mtype) }); | ||
1184 | } | ||
1185 | pushBack_Array( | ||
1186 | &items, | ||
1187 | &(iMenuItem){ translateCStr_Lang(download_Icon " " saveToDownloads_Label), | ||
1188 | 0, | ||
1189 | 0, | ||
1190 | "document.save" }); | ||
1191 | makeFooterButtons_DocumentWidget_(d, data_Array(&items), size_Array(&items)); | ||
1192 | deinit_Array(&items); | ||
1175 | break; | 1193 | break; |
1176 | } | 1194 | } |
1177 | default: | 1195 | default: |
@@ -2254,7 +2272,7 @@ static iBool updateDocumentWidthRetainingScrollPosition_DocumentWidget_(iDocumen | |||
2254 | /* TODO: First *fully* visible run? */ | 2272 | /* TODO: First *fully* visible run? */ |
2255 | voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); | 2273 | voffset = visibleRange_DocumentWidget_(d).start - top_Rect(run->visBounds); |
2256 | } | 2274 | } |
2257 | setWidth_GmDocument(d->doc, newWidth); | 2275 | setWidth_GmDocument(d->doc, newWidth, (width_Widget(d) - newWidth) / 2); |
2258 | documentRunsInvalidated_DocumentWidget_(d); | 2276 | documentRunsInvalidated_DocumentWidget_(d); |
2259 | if (runLoc && !keepCenter) { | 2277 | if (runLoc && !keepCenter) { |
2260 | run = findRunAtLoc_GmDocument(d->doc, runLoc); | 2278 | run = findRunAtLoc_GmDocument(d->doc, runLoc); |
@@ -2834,7 +2852,8 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2834 | setUrl_UploadWidget(upload, d->mod.url); | 2852 | setUrl_UploadWidget(upload, d->mod.url); |
2835 | setResponseViewer_UploadWidget(upload, d); | 2853 | setResponseViewer_UploadWidget(upload, d); |
2836 | addChild_Widget(get_Root()->widget, iClob(upload)); | 2854 | addChild_Widget(get_Root()->widget, iClob(upload)); |
2837 | finalizeSheet_Mobile(as_Widget(upload)); | 2855 | // finalizeSheet_Mobile(as_Widget(upload)); |
2856 | setupSheetTransition_Mobile(as_Widget(upload), iTrue); | ||
2838 | postRefresh_App(); | 2857 | postRefresh_App(); |
2839 | } | 2858 | } |
2840 | return iTrue; | 2859 | return iTrue; |
@@ -3109,7 +3128,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3109 | makeQuestion_Widget( | 3128 | makeQuestion_Widget( |
3110 | uiHeading_ColorEscape "${heading.import.bookmarks}", | 3129 | uiHeading_ColorEscape "${heading.import.bookmarks}", |
3111 | formatCStrs_Lang("dlg.import.found.n", count), | 3130 | formatCStrs_Lang("dlg.import.found.n", count), |
3112 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | 3131 | (iMenuItem[]){ { "${cancel}" }, |
3113 | { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count), | 3132 | { format_CStr(cstrCount_Lang("dlg.import.add.n", (int) count), |
3114 | uiTextAction_ColorEscape, | 3133 | uiTextAction_ColorEscape, |
3115 | count), | 3134 | count), |
@@ -3173,6 +3192,10 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
3173 | document_App() == d) { | 3192 | document_App() == d) { |
3174 | return handleSwipe_DocumentWidget_(d, cmd); | 3193 | return handleSwipe_DocumentWidget_(d, cmd); |
3175 | } | 3194 | } |
3195 | else if (equal_Command(cmd, "document.setmediatype") && document_App() == d) { | ||
3196 | setUrlAndSource_DocumentWidget(d, d->mod.url, string_Command(cmd, "mime"), &d->sourceContent); | ||
3197 | return iTrue; | ||
3198 | } | ||
3176 | return iFalse; | 3199 | return iFalse; |
3177 | } | 3200 | } |
3178 | 3201 | ||
@@ -3277,7 +3300,7 @@ static iBool processMediaEvents_DocumentWidget_(iDocumentWidget *d, const SDL_Ev | |||
3277 | d->playerMenu = makeMenu_Widget( | 3300 | d->playerMenu = makeMenu_Widget( |
3278 | as_Widget(d), | 3301 | as_Widget(d), |
3279 | (iMenuItem[]){ | 3302 | (iMenuItem[]){ |
3280 | { cstrCollect_String(metadataLabel_Player(plr)), 0, 0, NULL }, | 3303 | { cstrCollect_String(metadataLabel_Player(plr)) }, |
3281 | }, | 3304 | }, |
3282 | 1); | 3305 | 1); |
3283 | openMenu_Widget(d->playerMenu, bottomLeft_Rect(ui.menuRect)); | 3306 | openMenu_Widget(d->playerMenu, bottomLeft_Rect(ui.menuRect)); |
@@ -3582,7 +3605,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3582 | pushBackN_Array( | 3605 | pushBackN_Array( |
3583 | &items, | 3606 | &items, |
3584 | (iMenuItem[]){ | 3607 | (iMenuItem[]){ |
3585 | { "---", 0, 0, NULL }, | 3608 | { "---" }, |
3586 | { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}", | 3609 | { isGemini ? "${link.noproxy}" : openExt_Icon " ${link.browser}", |
3587 | 0, | 3610 | 0, |
3588 | 0, | 3611 | 0, |
@@ -3593,7 +3616,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3593 | linkLabel_GmDocument(d->doc, d->contextLink->linkId)); | 3616 | linkLabel_GmDocument(d->doc, d->contextLink->linkId)); |
3594 | urlEncodeSpaces_String(linkLabel); | 3617 | urlEncodeSpaces_String(linkLabel); |
3595 | pushBackN_Array(&items, | 3618 | pushBackN_Array(&items, |
3596 | (iMenuItem[]){ { "---", 0, 0, NULL }, | 3619 | (iMenuItem[]){ { "---" }, |
3597 | { "${link.copy}", 0, 0, "document.copylink" }, | 3620 | { "${link.copy}", 0, 0, "document.copylink" }, |
3598 | { bookmark_Icon " ${link.bookmark}", | 3621 | { bookmark_Icon " ${link.bookmark}", |
3599 | 0, | 3622 | 0, |
@@ -3605,7 +3628,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3605 | 3); | 3628 | 3); |
3606 | if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { | 3629 | if (isNative && d->contextLink->mediaType != download_GmRunMediaType) { |
3607 | pushBackN_Array(&items, (iMenuItem[]){ | 3630 | pushBackN_Array(&items, (iMenuItem[]){ |
3608 | { "---", 0, 0, NULL }, | 3631 | { "---" }, |
3609 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, | 3632 | { download_Icon " ${link.download}", 0, 0, "document.downloadlink" }, |
3610 | }, 2); | 3633 | }, 2); |
3611 | } | 3634 | } |
@@ -3648,17 +3671,17 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3648 | { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" }, | 3671 | { "${menu.forward}", navigateForward_KeyShortcut, "navigate.forward" }, |
3649 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | 3672 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, |
3650 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | 3673 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, |
3651 | { "---", 0, 0, NULL }, | 3674 | { "---" }, |
3652 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | 3675 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, |
3653 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | 3676 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, |
3654 | { "---", 0, 0, NULL }, | 3677 | { "---" }, |
3655 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 3678 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
3656 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | 3679 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, |
3657 | { "---", 0, 0, NULL }, | 3680 | { "---" }, |
3658 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 3681 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
3659 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | 3682 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, |
3660 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, | 3683 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, |
3661 | { "---", 0, 0, NULL }, | 3684 | { "---" }, |
3662 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, | 3685 | { "${menu.page.copyurl}", 0, 0, "document.copylink" } }, |
3663 | 15); | 3686 | 15); |
3664 | if (isEmpty_Range(&d->selectMark)) { | 3687 | if (isEmpty_Range(&d->selectMark)) { |
@@ -3834,7 +3857,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3834 | } | 3857 | } |
3835 | d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){ | 3858 | d->copyMenu = makeMenu_Widget(w, (iMenuItem[]){ |
3836 | { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, | 3859 | { clipCopy_Icon " ${menu.copy}", 0, 0, "copy" }, |
3837 | { "---", 0, 0, NULL }, | 3860 | { "---" }, |
3838 | { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" }, | 3861 | { close_Icon " ${menu.select.clear}", 0, 0, "document.select arg:0" }, |
3839 | }, 3); | 3862 | }, 3); |
3840 | setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); | 3863 | setFlags_Widget(d->copyMenu, noFadeBackground_WidgetFlag, iTrue); |
@@ -3927,7 +3950,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3927 | uiTextAction_ColorEscape, | 3950 | uiTextAction_ColorEscape, |
3928 | cstr_String(url)), | 3951 | cstr_String(url)), |
3929 | (iMenuItem[]){ | 3952 | (iMenuItem[]){ |
3930 | { "${cancel}", 0, 0, NULL }, | 3953 | { "${cancel}" }, |
3931 | { uiTextCaution_ColorEscape "${dlg.openlink}", | 3954 | { uiTextCaution_ColorEscape "${dlg.openlink}", |
3932 | 0, 0, format_CStr("!open default:1 url:%s", cstr_String(url)) } }, | 3955 | 0, 0, format_CStr("!open default:1 url:%s", cstr_String(url)) } }, |
3933 | 2); | 3956 | 2); |
@@ -4574,23 +4597,6 @@ static void drawMedia_DocumentWidget_(const iDocumentWidget *d, iPaint *p) { | |||
4574 | } | 4597 | } |
4575 | } | 4598 | } |
4576 | 4599 | ||
4577 | static void drawPin_(iPaint *p, iRect rangeRect, int dir) { | ||
4578 | const int pinColor = tmQuote_ColorId; | ||
4579 | const int height = height_Rect(rangeRect); | ||
4580 | iRect pin; | ||
4581 | if (dir == 0) { | ||
4582 | pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), | ||
4583 | init_I2(gap_UI / 2, height + gap_UI) }; | ||
4584 | } | ||
4585 | else { | ||
4586 | pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), | ||
4587 | init_I2(gap_UI / 2, height + gap_UI) }; | ||
4588 | } | ||
4589 | fillRect_Paint(p, pin, pinColor); | ||
4590 | fillRect_Paint(p, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), | ||
4591 | init1_I2(gap_UI * 2)), pinColor); | ||
4592 | } | ||
4593 | |||
4594 | static void extend_GmRunRange_(iGmRunRange *runs) { | 4600 | static void extend_GmRunRange_(iGmRunRange *runs) { |
4595 | if (runs->start) { | 4601 | if (runs->start) { |
4596 | runs->start--; | 4602 | runs->start--; |
@@ -4834,8 +4840,8 @@ static void draw_DocumentWidget_(const iDocumentWidget *d) { | |||
4834 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); | 4840 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); |
4835 | /* Selection range pins. */ | 4841 | /* Selection range pins. */ |
4836 | if (isTouchSelecting) { | 4842 | if (isTouchSelecting) { |
4837 | drawPin_(&ctx.paint, ctx.firstMarkRect, 0); | 4843 | drawPin_Paint(&ctx.paint, ctx.firstMarkRect, 0, tmQuote_ColorId); |
4838 | drawPin_(&ctx.paint, ctx.lastMarkRect, 1); | 4844 | drawPin_Paint(&ctx.paint, ctx.lastMarkRect, 1, tmQuote_ColorId); |
4839 | } | 4845 | } |
4840 | } | 4846 | } |
4841 | drawMedia_DocumentWidget_(d, &ctx.paint); | 4847 | drawMedia_DocumentWidget_(d, &ctx.paint); |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 690107a2..874cf2b5 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -27,6 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
27 | #include "keys.h" | 27 | #include "keys.h" |
28 | #include "prefs.h" | 28 | #include "prefs.h" |
29 | #include "lang.h" | 29 | #include "lang.h" |
30 | #include "touch.h" | ||
30 | #include "app.h" | 31 | #include "app.h" |
31 | 32 | ||
32 | #include <the_Foundation/array.h> | 33 | #include <the_Foundation/array.h> |
@@ -178,19 +179,23 @@ static void deinit_InputUndo_(iInputUndo *d) { | |||
178 | } | 179 | } |
179 | 180 | ||
180 | enum iInputWidgetFlag { | 181 | enum iInputWidgetFlag { |
181 | isSensitive_InputWidgetFlag = iBit(1), | 182 | isSensitive_InputWidgetFlag = iBit(1), |
182 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ | 183 | isUrl_InputWidgetFlag = iBit(2), /* affected by decoding preference */ |
183 | enterPressed_InputWidgetFlag = iBit(3), | 184 | enterPressed_InputWidgetFlag = iBit(3), |
184 | selectAllOnFocus_InputWidgetFlag = iBit(4), | 185 | selectAllOnFocus_InputWidgetFlag = iBit(4), |
185 | notifyEdits_InputWidgetFlag = iBit(5), | 186 | notifyEdits_InputWidgetFlag = iBit(5), |
186 | eatEscape_InputWidgetFlag = iBit(6), | 187 | eatEscape_InputWidgetFlag = iBit(6), |
187 | isMarking_InputWidgetFlag = iBit(7), | 188 | isMarking_InputWidgetFlag = iBit(7), |
188 | markWords_InputWidgetFlag = iBit(8), | 189 | markWords_InputWidgetFlag = iBit(8), |
189 | needUpdateBuffer_InputWidgetFlag = iBit(9), | 190 | needUpdateBuffer_InputWidgetFlag = iBit(9), |
190 | enterKeyEnabled_InputWidgetFlag = iBit(10), | 191 | enterKeyEnabled_InputWidgetFlag = iBit(10), |
191 | lineBreaksEnabled_InputWidgetFlag= iBit(11), | 192 | lineBreaksEnabled_InputWidgetFlag = iBit(11), |
192 | needBackup_InputWidgetFlag = iBit(12), | 193 | needBackup_InputWidgetFlag = iBit(12), |
193 | useReturnKeyBehavior_InputWidgetFlag = iBit(13), | 194 | useReturnKeyBehavior_InputWidgetFlag = iBit(13), |
195 | //touchBehavior_InputWidgetFlag = iBit(14), /* different behavior depending on interaction method */ | ||
196 | dragCursor_InputWidgetFlag = iBit(14), | ||
197 | dragMarkerStart_InputWidgetFlag = iBit(15), | ||
198 | dragMarkerEnd_InputWidgetFlag = iBit(16), | ||
194 | }; | 199 | }; |
195 | 200 | ||
196 | /*----------------------------------------------------------------------------------------------*/ | 201 | /*----------------------------------------------------------------------------------------------*/ |
@@ -216,6 +221,10 @@ struct Impl_InputWidget { | |||
216 | iArray undoStack; | 221 | iArray undoStack; |
217 | int font; | 222 | int font; |
218 | iClick click; | 223 | iClick click; |
224 | uint32_t tapStartTime; | ||
225 | uint32_t lastTapTime; | ||
226 | iInt2 lastTapPos; | ||
227 | int tapCount; | ||
219 | int wheelAccum; | 228 | int wheelAccum; |
220 | int cursorVis; | 229 | int cursorVis; |
221 | uint32_t timer; | 230 | uint32_t timer; |
@@ -459,14 +468,54 @@ static iWrapText wrap_InputWidget_(const iInputWidget *d, int y) { | |||
459 | }; | 468 | }; |
460 | } | 469 | } |
461 | 470 | ||
462 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | 471 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { |
463 | /* Relative to the start of the line on which the cursor is. */ | 472 | iRangei vis = { -1, -1 }; |
464 | iWrapText wt = wrap_InputWidget_(d, d->cursor.y); | 473 | /* Determine which lines are in the potentially visible range. */ |
465 | wt.hitChar = wt.text.start + d->cursor.x; | 474 | for (int i = 0; i < size_Array(&d->lines); i++) { |
475 | const iInputLine *line = constAt_Array(&d->lines, i); | ||
476 | if (vis.start < 0 && line->wrapLines.end > d->visWrapLines.start) { | ||
477 | vis.start = vis.end = i; | ||
478 | } | ||
479 | if (line->wrapLines.start < d->visWrapLines.end) { | ||
480 | vis.end = i + 1; | ||
481 | } | ||
482 | else break; | ||
483 | } | ||
484 | iAssert(isEmpty_Range(&vis) || (vis.start >= 0 && vis.end >= vis.start)); | ||
485 | return vis; | ||
486 | } | ||
487 | |||
488 | static iInt2 relativeCoordOnLine_InputWidget_(const iInputWidget *d, iInt2 pos) { | ||
489 | /* Relative to the start of the line on which the position is. */ | ||
490 | iWrapText wt = wrap_InputWidget_(d, pos.y); | ||
491 | wt.hitChar = wt.text.start + pos.x; | ||
466 | measure_WrapText(&wt, d->font); | 492 | measure_WrapText(&wt, d->font); |
467 | return wt.hitAdvance_out; | 493 | return wt.hitAdvance_out; |
468 | } | 494 | } |
469 | 495 | ||
496 | static iInt2 cursorToWindowCoord_InputWidget_(const iInputWidget *d, iInt2 pos, iBool *isInsideBounds) { | ||
497 | /* Maps a cursor XY position to a window coordinate. */ | ||
498 | const iRect bounds = contentBounds_InputWidget_(d); | ||
499 | iInt2 wc = addY_I2(topLeft_Rect(bounds), visLineOffsetY_InputWidget_(d)); | ||
500 | iRangei visLines = visibleLineRange_InputWidget_(d); | ||
501 | if (!contains_Range(&visLines, pos.y)) { | ||
502 | /* This line is not visible. */ | ||
503 | *isInsideBounds = iFalse; | ||
504 | return zero_I2(); | ||
505 | } | ||
506 | for (int i = visLines.start; i < pos.y; i++) { | ||
507 | wc.y += lineHeight_Text(d->font) * numWrapLines_InputLine_(line_InputWidget_(d, i)); | ||
508 | } | ||
509 | const iInputLine *line = line_InputWidget_(d, pos.y); | ||
510 | addv_I2(&wc, relativeCoordOnLine_InputWidget_(d, pos)); | ||
511 | *isInsideBounds = contains_Rect(bounds, wc); | ||
512 | return wc; | ||
513 | } | ||
514 | |||
515 | static iInt2 relativeCursorCoord_InputWidget_(const iInputWidget *d) { | ||
516 | return relativeCoordOnLine_InputWidget_(d, d->cursor); | ||
517 | } | ||
518 | |||
470 | static void updateVisible_InputWidget_(iInputWidget *d) { | 519 | static void updateVisible_InputWidget_(iInputWidget *d) { |
471 | const int totalWraps = numWrapLines_InputWidget_(d); | 520 | const int totalWraps = numWrapLines_InputWidget_(d); |
472 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); | 521 | const int visWraps = iClamp(totalWraps, d->minWrapLines, d->maxWrapLines); |
@@ -492,6 +541,8 @@ static void updateVisible_InputWidget_(iInputWidget *d) { | |||
492 | d->visWrapLines.start = 0; | 541 | d->visWrapLines.start = 0; |
493 | d->visWrapLines.end = 1; | 542 | d->visWrapLines.end = 1; |
494 | } | 543 | } |
544 | // printf("[InputWidget %p] total:%d viswrp:%d cur:%d vis:%d..%d\n", | ||
545 | // d, totalWraps, visWraps, d->cursor.y, d->visWrapLines.start, d->visWrapLines.end); | ||
495 | } | 546 | } |
496 | 547 | ||
497 | static void showCursor_InputWidget_(iInputWidget *d) { | 548 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -542,8 +593,10 @@ static int contentHeight_InputWidget_(const iInputWidget *d) { | |||
542 | } | 593 | } |
543 | 594 | ||
544 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { | 595 | static void updateTextInputRect_InputWidget_(const iInputWidget *d) { |
596 | #if !defined (iPlatformAppleMobile) | ||
545 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | 597 | const iRect bounds = bounds_Widget(constAs_Widget(d)); |
546 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); | 598 | SDL_SetTextInputRect(&(SDL_Rect){ bounds.pos.x, bounds.pos.y, bounds.size.x, bounds.size.y }); |
599 | #endif | ||
547 | } | 600 | } |
548 | 601 | ||
549 | static void updateMetrics_InputWidget_(iInputWidget *d) { | 602 | static void updateMetrics_InputWidget_(iInputWidget *d) { |
@@ -629,7 +682,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
629 | init_Widget(w); | 682 | init_Widget(w); |
630 | d->validator = NULL; | 683 | d->validator = NULL; |
631 | d->validatorContext = NULL; | 684 | d->validatorContext = NULL; |
632 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag | touchDrag_WidgetFlag, iTrue); | 685 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, iTrue); |
633 | #if defined (iPlatformMobile) | 686 | #if defined (iPlatformMobile) |
634 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); | 687 | setFlags_Widget(w, extraPadding_WidgetFlag, iTrue); |
635 | #endif | 688 | #endif |
@@ -659,6 +712,8 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
659 | splitToLines_(&iStringLiteral(""), &d->lines); | 712 | splitToLines_(&iStringLiteral(""), &d->lines); |
660 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ | 713 | setFlags_Widget(w, fixedHeight_WidgetFlag, iTrue); /* resizes its own height */ |
661 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 714 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
715 | d->lastTapTime = 0; | ||
716 | d->tapCount = 0; | ||
662 | d->wheelAccum = 0; | 717 | d->wheelAccum = 0; |
663 | d->timer = 0; | 718 | d->timer = 0; |
664 | d->cursorVis = 0; | 719 | d->cursorVis = 0; |
@@ -753,6 +808,10 @@ const iString *text_InputWidget(const iInputWidget *d) { | |||
753 | return collectNew_String(); | 808 | return collectNew_String(); |
754 | } | 809 | } |
755 | 810 | ||
811 | int font_InputWidget(const iInputWidget *d) { | ||
812 | return d->font; | ||
813 | } | ||
814 | |||
756 | iInputWidgetContentPadding contentPadding_InputWidget(const iInputWidget *d) { | 815 | iInputWidgetContentPadding contentPadding_InputWidget(const iInputWidget *d) { |
757 | return (iInputWidgetContentPadding){ d->leftPadding, d->rightPadding }; | 816 | return (iInputWidgetContentPadding){ d->leftPadding, d->rightPadding }; |
758 | } | 817 | } |
@@ -764,6 +823,7 @@ void setMaxLen_InputWidget(iInputWidget *d, size_t maxLen) { | |||
764 | } | 823 | } |
765 | 824 | ||
766 | void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) { | 825 | void setLineLimits_InputWidget(iInputWidget *d, int minLines, int maxLines) { |
826 | maxLines = iMax(minLines, maxLines); | ||
767 | if (d->minWrapLines != minLines || d->maxWrapLines != maxLines) { | 827 | if (d->minWrapLines != minLines || d->maxWrapLines != maxLines) { |
768 | d->minWrapLines = minLines; | 828 | d->minWrapLines = minLines; |
769 | d->maxWrapLines = maxLines; | 829 | d->maxWrapLines = maxLines; |
@@ -822,23 +882,6 @@ static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | |||
822 | return !isEmpty_String(&d->hint) && isEmpty_InputWidget_(d); | 882 | return !isEmpty_String(&d->hint) && isEmpty_InputWidget_(d); |
823 | } | 883 | } |
824 | 884 | ||
825 | static iRangei visibleLineRange_InputWidget_(const iInputWidget *d) { | ||
826 | iRangei vis = { -1, -1 }; | ||
827 | /* Determine which lines are in the potentially visible range. */ | ||
828 | for (int i = 0; i < size_Array(&d->lines); i++) { | ||
829 | const iInputLine *line = constAt_Array(&d->lines, i); | ||
830 | if (vis.start < 0 && line->wrapLines.end > d->visWrapLines.start) { | ||
831 | vis.start = vis.end = i; | ||
832 | } | ||
833 | if (line->wrapLines.start < d->visWrapLines.end) { | ||
834 | vis.end = i + 1; | ||
835 | } | ||
836 | else break; | ||
837 | } | ||
838 | iAssert(isEmpty_Range(&vis) || (vis.start >= 0 && vis.end >= vis.start)); | ||
839 | return vis; | ||
840 | } | ||
841 | |||
842 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 885 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
843 | invalidateBuffered_InputWidget_(d); | 886 | invalidateBuffered_InputWidget_(d); |
844 | if (isHintVisible_InputWidget_(d)) { | 887 | if (isHintVisible_InputWidget_(d)) { |
@@ -990,7 +1033,7 @@ void begin_InputWidget(iInputWidget *d) { | |||
990 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; | 1033 | d->mark = (iRanges){ 0, lastLine_InputWidget_(d)->range.end }; |
991 | d->cursor = cursorMax_InputWidget_(d); | 1034 | d->cursor = cursorMax_InputWidget_(d); |
992 | } | 1035 | } |
993 | else { | 1036 | else if (~d->inFlags & isMarking_InputWidgetFlag) { |
994 | iZap(d->mark); | 1037 | iZap(d->mark); |
995 | } | 1038 | } |
996 | enableEditorKeysInMenus_(iFalse); | 1039 | enableEditorKeysInMenus_(iFalse); |
@@ -1010,9 +1053,10 @@ void end_InputWidget(iInputWidget *d, iBool accept) { | |||
1010 | splitToLines_(&d->oldText, &d->lines); | 1053 | splitToLines_(&d->oldText, &d->lines); |
1011 | } | 1054 | } |
1012 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1055 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1056 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1013 | startOrStopCursorTimer_InputWidget_(d, iFalse); | 1057 | startOrStopCursorTimer_InputWidget_(d, iFalse); |
1014 | SDL_StopTextInput(); | 1058 | SDL_StopTextInput(); |
1015 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iFalse); | 1059 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag | touchDrag_WidgetFlag, iFalse); |
1016 | const char *id = cstr_String(id_Widget(as_Widget(d))); | 1060 | const char *id = cstr_String(id_Widget(as_Widget(d))); |
1017 | if (!*id) id = "_"; | 1061 | if (!*id) id = "_"; |
1018 | refresh_Widget(w); | 1062 | refresh_Widget(w); |
@@ -1314,9 +1358,10 @@ static iInt2 coordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | |||
1314 | if (relCoord.y < 0) { | 1358 | if (relCoord.y < 0) { |
1315 | return zero_I2(); | 1359 | return zero_I2(); |
1316 | } | 1360 | } |
1317 | if (relCoord.y >= height_Rect(bounds)) { | 1361 | // if (relCoord.y >= height_Rect(bounds)) { |
1318 | return cursorMax_InputWidget_(d); | 1362 | // printf("relCoord > bounds.h\n"); fflush(stdout); |
1319 | } | 1363 | // return cursorMax_InputWidget_(d); |
1364 | // } | ||
1320 | iWrapText wrapText = { | 1365 | iWrapText wrapText = { |
1321 | .maxWidth = d->maxLen == 0 ? width_Rect(bounds) : unlimitedWidth_InputWidget_, | 1366 | .maxWidth = d->maxLen == 0 ? width_Rect(bounds) : unlimitedWidth_InputWidget_, |
1322 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), | 1367 | .mode = (d->inFlags & isUrl_InputWidgetFlag ? anyCharacter_WrapTextMode : word_WrapTextMode), |
@@ -1442,6 +1487,374 @@ static iBool checkAcceptMods_InputWidget_(const iInputWidget *d, int mods) { | |||
1442 | return mods == 0; | 1487 | return mods == 0; |
1443 | } | 1488 | } |
1444 | 1489 | ||
1490 | enum iEventResult { | ||
1491 | ignored_EventResult = 0, /* event was not processed */ | ||
1492 | false_EventResult = 1, /* event was processed but other widgets can still process it, too*/ | ||
1493 | true_EventResult = 2, /* event was processed and should not be passed on */ | ||
1494 | }; | ||
1495 | |||
1496 | static void markWordAtCursor_InputWidget_(iInputWidget *d) { | ||
1497 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1498 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
1499 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
1500 | d->initialMark = d->mark; | ||
1501 | } | ||
1502 | |||
1503 | static void showClipMenu_(iInt2 coord) { | ||
1504 | iWidget *clipMenu = findWidget_App("clipmenu"); | ||
1505 | if (isVisible_Widget(clipMenu)) { | ||
1506 | closeMenu_Widget(clipMenu); | ||
1507 | } | ||
1508 | else { | ||
1509 | openMenuFlags_Widget(clipMenu, coord, iFalse); | ||
1510 | } | ||
1511 | } | ||
1512 | |||
1513 | static enum iEventResult processPointerEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | ||
1514 | iWidget *w = as_Widget(d); | ||
1515 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { | ||
1516 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1517 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
1518 | setCursor_Window(get_Window(), | ||
1519 | inner.x >= 2 * gap_UI + d->leftPadding && | ||
1520 | inner.x < width_Widget(w) - d->rightPadding | ||
1521 | ? SDL_SYSTEM_CURSOR_IBEAM | ||
1522 | : SDL_SYSTEM_CURSOR_ARROW); | ||
1523 | } | ||
1524 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | ||
1525 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1526 | showClipMenu_(mouseCoord_Window(get_Window(), ev->button.which)); | ||
1527 | return iTrue; | ||
1528 | } | ||
1529 | switch (processEvent_Click(&d->click, ev)) { | ||
1530 | case none_ClickResult: | ||
1531 | break; | ||
1532 | case started_ClickResult: { | ||
1533 | setFocus_Widget(w); | ||
1534 | const iInt2 oldCursor = d->cursor; | ||
1535 | setCursor_InputWidget(d, coordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1536 | if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { | ||
1537 | d->mark = d->initialMark = (iRanges){ | ||
1538 | cursorToIndex_InputWidget_(d, oldCursor), | ||
1539 | cursorToIndex_InputWidget_(d, d->cursor) | ||
1540 | }; | ||
1541 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1542 | } | ||
1543 | else { | ||
1544 | iZap(d->mark); | ||
1545 | iZap(d->initialMark); | ||
1546 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | ||
1547 | if (d->click.count == 2) { | ||
1548 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
1549 | markWordAtCursor_InputWidget_(d); | ||
1550 | refresh_Widget(w); | ||
1551 | } | ||
1552 | if (d->click.count == 3) { | ||
1553 | selectAll_InputWidget(d); | ||
1554 | } | ||
1555 | } | ||
1556 | refresh_Widget(d); | ||
1557 | return true_EventResult; | ||
1558 | } | ||
1559 | case aborted_ClickResult: | ||
1560 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1561 | return true_EventResult; | ||
1562 | case drag_ClickResult: | ||
1563 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1564 | showCursor_InputWidget_(d); | ||
1565 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1566 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1567 | d->mark.start = cursorToIndex_InputWidget_(d, d->cursor); | ||
1568 | } | ||
1569 | d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1570 | if (d->inFlags & markWords_InputWidgetFlag) { | ||
1571 | const iBool isFwd = d->mark.end >= d->mark.start; | ||
1572 | extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); | ||
1573 | d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; | ||
1574 | } | ||
1575 | refresh_Widget(w); | ||
1576 | return true_EventResult; | ||
1577 | case finished_ClickResult: | ||
1578 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1579 | return true_EventResult; | ||
1580 | } | ||
1581 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | ||
1582 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1583 | if (contains_Click(&d->click, coord)) { | ||
1584 | return true_EventResult; | ||
1585 | } | ||
1586 | } | ||
1587 | return ignored_EventResult; | ||
1588 | } | ||
1589 | |||
1590 | static iInt2 touchCoordCursor_InputWidget_(const iInputWidget *d, iInt2 coord) { | ||
1591 | /* Clamp to the bounds so the cursor doesn't wrap at the ends. */ | ||
1592 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); | ||
1593 | bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; | ||
1594 | return coordCursor_InputWidget_(d, min_I2(bottomRight_Rect(bounds), | ||
1595 | max_I2(coord, topLeft_Rect(bounds)))); | ||
1596 | } | ||
1597 | |||
1598 | static iBool isInsideMark_InputWidget_(const iInputWidget *d, size_t pos) { | ||
1599 | const iRanges mark = mark_InputWidget_(d); | ||
1600 | return contains_Range(&mark, pos); | ||
1601 | } | ||
1602 | |||
1603 | static int distanceToPos_InputWidget_(const iInputWidget *d, iInt2 uiCoord, iInt2 textPos) { | ||
1604 | iBool isInside; | ||
1605 | const iInt2 winCoord = cursorToWindowCoord_InputWidget_(d, textPos, &isInside); | ||
1606 | if (!isInside) { | ||
1607 | return INT_MAX; | ||
1608 | } | ||
1609 | return dist_I2(addY_I2(winCoord, lineHeight_Text(d->font) / 2), uiCoord); | ||
1610 | } | ||
1611 | |||
1612 | static enum iEventResult processTouchEvents_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | ||
1613 | iWidget *w = as_Widget(d); | ||
1614 | /* | ||
1615 | + first tap to focus & select all/place cursor | ||
1616 | + focused tap to place cursor | ||
1617 | - drag cursor to move it | ||
1618 | - double-click to select a word | ||
1619 | - drag to move selection handles | ||
1620 | - long-press for context menu: copy, paste, delete, select all, deselect | ||
1621 | - double-click and hold to select words | ||
1622 | - triple-click to select all | ||
1623 | - drag/wheel elsewhere to scroll (contents or overflow), no change in focus | ||
1624 | */ | ||
1625 | // if (ev->type != SDL_MOUSEBUTTONUP && ev->type != SDL_MOUSEBUTTONDOWN && | ||
1626 | // ev->type != SDL_MOUSEWHEEL && ev->type != SDL_MOUSEMOTION && | ||
1627 | // !(ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) && | ||
1628 | // !(ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { | ||
1629 | // return ignored_EventResult; | ||
1630 | // } | ||
1631 | if (isFocused_Widget(w)) { | ||
1632 | if (ev->type == SDL_USEREVENT && ev->user.code == widgetTapBegins_UserEventCode) { | ||
1633 | d->lastTapTime = d->tapStartTime; | ||
1634 | d->tapStartTime = SDL_GetTicks(); | ||
1635 | const int tapDist = dist_I2(latestPosition_Touch(), d->lastTapPos); | ||
1636 | d->lastTapPos = latestPosition_Touch(); | ||
1637 | // printf("[%p] tap start time: %u (%u) %d\n", w, d->tapStartTime, d->tapStartTime - d->lastTapTime, tapDist); | ||
1638 | if (d->tapStartTime - d->lastTapTime < 400 && tapDist < gap_UI * 4) { | ||
1639 | d->tapCount++; | ||
1640 | // printf("[%p] >> tap count: %d\n", w, d->tapCount); | ||
1641 | } | ||
1642 | else { | ||
1643 | d->tapCount = 0; | ||
1644 | } | ||
1645 | if (!isEmpty_Range(&d->mark)) { | ||
1646 | const int dist[2] = { | ||
1647 | distanceToPos_InputWidget_(d, latestPosition_Touch(), | ||
1648 | indexToCursor_InputWidget_(d, d->mark.start)), | ||
1649 | distanceToPos_InputWidget_(d, latestPosition_Touch(), | ||
1650 | indexToCursor_InputWidget_(d, d->mark.end)) | ||
1651 | }; | ||
1652 | if (dist[0] < dist[1]) { | ||
1653 | // printf("[%p] begin marker start drag\n", w); | ||
1654 | d->inFlags |= dragMarkerStart_InputWidgetFlag; | ||
1655 | } | ||
1656 | else { | ||
1657 | // printf("[%p] begin marker end drag\n", w); | ||
1658 | d->inFlags |= dragMarkerEnd_InputWidgetFlag; | ||
1659 | } | ||
1660 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1661 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1662 | } | ||
1663 | else { | ||
1664 | const int dist = distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor); | ||
1665 | // printf("[%p] tap dist: %d\n", w, dist); | ||
1666 | if (dist < gap_UI * 10) { | ||
1667 | // printf("[%p] begin cursor drag\n", w); | ||
1668 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1669 | d->inFlags |= dragCursor_InputWidgetFlag; | ||
1670 | // d->inFlags |= touchBehavior_InputWidgetFlag; | ||
1671 | // setMouseGrab_Widget(w); | ||
1672 | // return iTrue; | ||
1673 | } | ||
1674 | } | ||
1675 | // if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { | ||
1676 | // d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1677 | // showCursor_InputWidget_(d); | ||
1678 | // } | ||
1679 | return true_EventResult; | ||
1680 | } | ||
1681 | } | ||
1682 | #if 0 | ||
1683 | else if (isFocused_Widget(w)) { | ||
1684 | if (ev->type == SDL_MOUSEMOTION) { | ||
1685 | if (~d->inFlags & touchBehavior_InputWidgetFlag) { | ||
1686 | const iInt2 curPos = relativeCursorCoord_InputWidget_(d); | ||
1687 | const iInt2 relClick = sub_I2(pos_Click(&d->click), | ||
1688 | topLeft_Rect(contentBounds_InputWidget_(d))); | ||
1689 | if (dist_I2(curPos, relClick) < gap_UI * 8) { | ||
1690 | // printf("tap on cursor!\n"); | ||
1691 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1692 | d->inFlags |= touchBehavior_InputWidgetFlag; | ||
1693 | // printf("[Input] begin cursor drag\n"); | ||
1694 | setMouseGrab_Widget(w); | ||
1695 | return iTrue; | ||
1696 | } | ||
1697 | } | ||
1698 | else if (ev->motion.x > 0 && ev->motion.y > 0) { | ||
1699 | // printf("[Input] cursor being dragged\n"); | ||
1700 | iRect bounds = shrunk_Rect(contentBounds_InputWidget_(d), one_I2()); | ||
1701 | bounds.size.y = iMini(numWrapLines_InputWidget_(d), d->maxWrapLines) * lineHeight_Text(d->font) - 2; | ||
1702 | iInt2 mpos = init_I2(ev->motion.x, ev->motion.y); | ||
1703 | mpos = min_I2(bottomRight_Rect(bounds), max_I2(mpos, topLeft_Rect(bounds))); | ||
1704 | d->cursor = coordCursor_InputWidget_(d, mpos); | ||
1705 | showCursor_InputWidget_(d); | ||
1706 | refresh_Widget(w); | ||
1707 | return iTrue; | ||
1708 | } | ||
1709 | } | ||
1710 | if (d->inFlags & touchBehavior_InputWidgetFlag) { | ||
1711 | if (ev->type == SDL_MOUSEBUTTONUP || | ||
1712 | (ev->type == SDL_USEREVENT && ev->user.code == widgetTouchEnds_UserEventCode)) { | ||
1713 | d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1714 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1715 | setMouseGrab_Widget(NULL); | ||
1716 | // printf("[Input] touch ends\n"); | ||
1717 | return iFalse; | ||
1718 | } | ||
1719 | } | ||
1720 | } | ||
1721 | #endif | ||
1722 | #if 1 | ||
1723 | if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && | ||
1724 | ev->button.button == SDL_BUTTON_RIGHT && contains_Widget(w, latestPosition_Touch())) { | ||
1725 | if (ev->type == SDL_MOUSEBUTTONDOWN) { | ||
1726 | /*if (isFocused_Widget(w)) { | ||
1727 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1728 | d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); | ||
1729 | markWordAtCursor_InputWidget_(d); | ||
1730 | refresh_Widget(d); | ||
1731 | return true_EventResult; | ||
1732 | }*/ | ||
1733 | setFocus_Widget(w); | ||
1734 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1735 | d->cursor = touchCoordCursor_InputWidget_(d, latestPosition_Touch()); | ||
1736 | markWordAtCursor_InputWidget_(d); | ||
1737 | d->cursor = indexToCursor_InputWidget_(d, d->mark.end); | ||
1738 | refresh_Widget(d); | ||
1739 | } | ||
1740 | return true_EventResult; | ||
1741 | } | ||
1742 | switch (processEvent_Click(&d->click, ev)) { | ||
1743 | case none_ClickResult: | ||
1744 | break; | ||
1745 | case started_ClickResult: { | ||
1746 | // printf("[%p] started\n", w); | ||
1747 | /* | ||
1748 | const iInt2 curPos = relativeCursorCoord_InputWidget_(d); | ||
1749 | const iInt2 relClick = sub_I2(pos_Click(&d->click), | ||
1750 | topLeft_Rect(contentBounds_InputWidget_(d))); | ||
1751 | if (dist_I2(curPos, relClick) < gap_UI * 8) { | ||
1752 | printf("tap on cursor!\n"); | ||
1753 | setFlags_Widget(w, touchDrag_WidgetFlag, iTrue); | ||
1754 | } | ||
1755 | else { | ||
1756 | printf("tap elsewhere\n"); | ||
1757 | }*/ | ||
1758 | return true_EventResult; | ||
1759 | } | ||
1760 | case drag_ClickResult: | ||
1761 | // printf("[%p] drag %d,%d\n", w, pos_Click(&d->click).x, pos_Click(&d->click).y); | ||
1762 | if (d->inFlags & dragCursor_InputWidgetFlag) { | ||
1763 | iZap(d->mark); | ||
1764 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1765 | showCursor_InputWidget_(d); | ||
1766 | refresh_Widget(w); | ||
1767 | } | ||
1768 | else if (d->inFlags & dragMarkerStart_InputWidgetFlag) { | ||
1769 | d->mark.start = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1770 | refresh_Widget(w); | ||
1771 | } | ||
1772 | else if (d->inFlags & dragMarkerEnd_InputWidgetFlag) { | ||
1773 | d->mark.end = cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1774 | refresh_Widget(w); | ||
1775 | } | ||
1776 | return true_EventResult; | ||
1777 | // printf("[%p] aborted\n", w); | ||
1778 | // d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1779 | // setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1780 | // return true_EventResult; | ||
1781 | case finished_ClickResult: | ||
1782 | case aborted_ClickResult: { | ||
1783 | // printf("[%p] ended\n", w); | ||
1784 | uint32_t tapElapsed = SDL_GetTicks() - d->tapStartTime; | ||
1785 | // printf("tapElapsed: %u\n", tapElapsed); | ||
1786 | if (!isFocused_Widget(w)) { | ||
1787 | setFocus_Widget(w); | ||
1788 | d->lastTapPos = latestPosition_Touch(); | ||
1789 | d->tapStartTime = SDL_GetTicks(); | ||
1790 | d->tapCount = 0; | ||
1791 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1792 | showCursor_InputWidget_(d); | ||
1793 | } | ||
1794 | else if (!isEmpty_Range(&d->mark) && !isMoved_Click(&d->click)) { | ||
1795 | if (isInsideMark_InputWidget_(d, cursorToIndex_InputWidget_(d, touchCoordCursor_InputWidget_(d, latestPosition_Touch())))) { | ||
1796 | showClipMenu_(latestPosition_Touch()); | ||
1797 | } | ||
1798 | else { | ||
1799 | iZap(d->mark); | ||
1800 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1801 | } | ||
1802 | } | ||
1803 | else if (SDL_GetTicks() - d->lastTapTime > 1000 && | ||
1804 | d->tapCount == 0 && isEmpty_Range(&d->mark) && !isMoved_Click(&d->click) && | ||
1805 | distanceToPos_InputWidget_(d, latestPosition_Touch(), d->cursor) < gap_UI * 5) { | ||
1806 | showClipMenu_(latestPosition_Touch()); | ||
1807 | } | ||
1808 | else { | ||
1809 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1810 | iZap(d->mark); | ||
1811 | d->cursor = touchCoordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1812 | } | ||
1813 | } | ||
1814 | if (d->inFlags & (dragCursor_InputWidgetFlag | dragMarkerStart_InputWidgetFlag | | ||
1815 | dragMarkerEnd_InputWidgetFlag)) { | ||
1816 | // printf("[%p] finished cursor/marker drag\n", w); | ||
1817 | d->inFlags &= ~(dragCursor_InputWidgetFlag | | ||
1818 | dragMarkerStart_InputWidgetFlag | | ||
1819 | dragMarkerEnd_InputWidgetFlag); | ||
1820 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1821 | } | ||
1822 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1823 | showCursor_InputWidget_(d); | ||
1824 | refresh_Widget(w); | ||
1825 | #if 0 | ||
1826 | d->inFlags &= ~touchBehavior_InputWidgetFlag; | ||
1827 | if (flags_Widget(w) & touchDrag_WidgetFlag) { | ||
1828 | setFlags_Widget(w, touchDrag_WidgetFlag, iFalse); | ||
1829 | return true_EventResult; | ||
1830 | } | ||
1831 | if (!isMoved_Click(&d->click)) { | ||
1832 | if (!isFocused_Widget(w)) { | ||
1833 | setFocus_Widget(w); | ||
1834 | if (~d->inFlags & selectAllOnFocus_InputWidgetFlag) { | ||
1835 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1836 | showCursor_InputWidget_(d); | ||
1837 | } | ||
1838 | } | ||
1839 | else { | ||
1840 | iZap(d->mark); | ||
1841 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1842 | showCursor_InputWidget_(d); | ||
1843 | } | ||
1844 | } | ||
1845 | #endif | ||
1846 | return true_EventResult; | ||
1847 | } | ||
1848 | } | ||
1849 | #endif | ||
1850 | // if ((ev->type == SDL_MOUSEBUTTONDOWN || ev->type == SDL_MOUSEBUTTONUP) && | ||
1851 | // contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1852 | // /* Eat all mouse clicks on the widget. */ | ||
1853 | // return true_EventResult; | ||
1854 | // } | ||
1855 | return ignored_EventResult; | ||
1856 | } | ||
1857 | |||
1445 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | 1858 | static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { |
1446 | iWidget *w = as_Widget(d); | 1859 | iWidget *w = as_Widget(d); |
1447 | /* Resize according to width immediately. */ | 1860 | /* Resize according to width immediately. */ |
@@ -1486,23 +1899,35 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1486 | paste_InputWidget_(d); | 1899 | paste_InputWidget_(d); |
1487 | return iTrue; | 1900 | return iTrue; |
1488 | } | 1901 | } |
1902 | else if (isCommand_UserEvent(ev, "input.undo") && isEditing_InputWidget_(d)) { | ||
1903 | if (popUndo_InputWidget_(d)) { | ||
1904 | refresh_Widget(w); | ||
1905 | contentsWereChanged_InputWidget_(d); | ||
1906 | } | ||
1907 | return iTrue; | ||
1908 | } | ||
1909 | else if (isCommand_UserEvent(ev, "input.selectall") && isEditing_InputWidget_(d)) { | ||
1910 | selectAll_InputWidget(d); | ||
1911 | return iTrue; | ||
1912 | } | ||
1489 | else if (isCommand_UserEvent(ev, "theme.changed")) { | 1913 | else if (isCommand_UserEvent(ev, "theme.changed")) { |
1490 | if (d->buffered) { | 1914 | if (d->buffered) { |
1491 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1915 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1492 | } | 1916 | } |
1493 | return iFalse; | 1917 | return iFalse; |
1494 | } | 1918 | } |
1495 | else if (isCommand_UserEvent(ev, "keyboard.changed")) { | 1919 | /* TODO: Scroll to keep widget visible when keyboard appears. */ |
1496 | if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { | 1920 | // else if (isCommand_UserEvent(ev, "keyboard.changed")) { |
1497 | iRect rect = bounds_Widget(w); | 1921 | // if (isFocused_Widget(d) && arg_Command(command_UserEvent(ev))) { |
1498 | rect.pos.y -= value_Anim(&get_Window()->rootOffset); | 1922 | // iRect rect = bounds_Widget(w); |
1499 | const iInt2 visRoot = visibleSize_Root(w->root); | 1923 | // rect.pos.y -= value_Anim(&get_Window()->rootOffset); |
1500 | if (bottom_Rect(rect) > visRoot.y) { | 1924 | // const iInt2 visRoot = visibleSize_Root(w->root); |
1501 | setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); | 1925 | // if (bottom_Rect(rect) > visRoot.y) { |
1502 | } | 1926 | // setValue_Anim(&get_Window()->rootOffset, -(bottom_Rect(rect) - visRoot.y), 250); |
1503 | } | 1927 | // } |
1504 | return iFalse; | 1928 | // } |
1505 | } | 1929 | // return iFalse; |
1930 | // } | ||
1506 | else if (isCommand_UserEvent(ev, "text.insert")) { | 1931 | else if (isCommand_UserEvent(ev, "text.insert")) { |
1507 | pushUndo_InputWidget_(d); | 1932 | pushUndo_InputWidget_(d); |
1508 | deleteMarked_InputWidget_(d); | 1933 | deleteMarked_InputWidget_(d); |
@@ -1524,16 +1949,10 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1524 | copy_InputWidget_(d, iFalse); | 1949 | copy_InputWidget_(d, iFalse); |
1525 | return iTrue; | 1950 | return iTrue; |
1526 | } | 1951 | } |
1527 | if (ev->type == SDL_MOUSEMOTION && (isHover_Widget(d) || flags_Widget(w) & keepOnTop_WidgetFlag)) { | ||
1528 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | ||
1529 | const iInt2 inner = windowToInner_Widget(w, coord); | ||
1530 | setCursor_Window(get_Window(), | ||
1531 | inner.x >= 2 * gap_UI + d->leftPadding && | ||
1532 | inner.x < width_Widget(w) - d->rightPadding | ||
1533 | ? SDL_SYSTEM_CURSOR_IBEAM | ||
1534 | : SDL_SYSTEM_CURSOR_ARROW); | ||
1535 | } | ||
1536 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { | 1952 | if (ev->type == SDL_MOUSEWHEEL && contains_Widget(w, coord_MouseWheelEvent(&ev->wheel))) { |
1953 | if (numWrapLines_InputWidget_(d) <= size_Range(&d->visWrapLines)) { | ||
1954 | return ignored_EventResult; | ||
1955 | } | ||
1537 | const int lineHeight = lineHeight_Text(d->font); | 1956 | const int lineHeight = lineHeight_Text(d->font); |
1538 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { | 1957 | if (isPerPixel_MouseWheelEvent(&ev->wheel)) { |
1539 | d->wheelAccum -= ev->wheel.y; | 1958 | d->wheelAccum -= ev->wheel.y; |
@@ -1551,87 +1970,24 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1551 | lastLine_InputWidget_(d)->wrapLines.end - d->visWrapLines.end); | 1970 | lastLine_InputWidget_(d)->wrapLines.end - d->visWrapLines.end); |
1552 | if (!lineDelta) d->wheelAccum = 0; | 1971 | if (!lineDelta) d->wheelAccum = 0; |
1553 | } | 1972 | } |
1554 | d->wheelAccum -= lineDelta * lineHeight; | 1973 | if (lineDelta) { |
1555 | d->visWrapLines.start += lineDelta; | 1974 | d->wheelAccum -= lineDelta * lineHeight; |
1556 | d->visWrapLines.end += lineDelta; | 1975 | d->visWrapLines.start += lineDelta; |
1557 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | 1976 | d->visWrapLines.end += lineDelta; |
1558 | refresh_Widget(d); | 1977 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; |
1559 | return iTrue; | ||
1560 | } | ||
1561 | switch (processEvent_Click(&d->click, ev)) { | ||
1562 | case none_ClickResult: | ||
1563 | break; | ||
1564 | case started_ClickResult: { | ||
1565 | setFocus_Widget(w); | ||
1566 | const iInt2 oldCursor = d->cursor; | ||
1567 | setCursor_InputWidget(d, coordCursor_InputWidget_(d, pos_Click(&d->click))); | ||
1568 | if (keyMods_Sym(modState_Keys()) == KMOD_SHIFT) { | ||
1569 | d->mark = d->initialMark = (iRanges){ | ||
1570 | cursorToIndex_InputWidget_(d, oldCursor), | ||
1571 | cursorToIndex_InputWidget_(d, d->cursor) | ||
1572 | }; | ||
1573 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1574 | } | ||
1575 | else { | ||
1576 | iZap(d->mark); | ||
1577 | iZap(d->initialMark); | ||
1578 | d->inFlags &= ~(isMarking_InputWidgetFlag | markWords_InputWidgetFlag); | ||
1579 | if (d->click.count == 2) { | ||
1580 | d->inFlags |= isMarking_InputWidgetFlag | markWords_InputWidgetFlag; | ||
1581 | d->mark.start = d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1582 | extendRange_InputWidget_(d, &d->mark.start, -1); | ||
1583 | extendRange_InputWidget_(d, &d->mark.end, +1); | ||
1584 | d->initialMark = d->mark; | ||
1585 | refresh_Widget(w); | ||
1586 | } | ||
1587 | if (d->click.count == 3) { | ||
1588 | selectAll_InputWidget(d); | ||
1589 | } | ||
1590 | } | ||
1591 | refresh_Widget(d); | 1978 | refresh_Widget(d); |
1592 | return iTrue; | 1979 | return true_EventResult; |
1593 | } | 1980 | } |
1594 | case aborted_ClickResult: | 1981 | return false_EventResult; |
1595 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1596 | return iTrue; | ||
1597 | case drag_ClickResult: | ||
1598 | d->cursor = coordCursor_InputWidget_(d, pos_Click(&d->click)); | ||
1599 | showCursor_InputWidget_(d); | ||
1600 | if (~d->inFlags & isMarking_InputWidgetFlag) { | ||
1601 | d->inFlags |= isMarking_InputWidgetFlag; | ||
1602 | d->mark.start = cursorToIndex_InputWidget_(d, d->cursor); | ||
1603 | } | ||
1604 | d->mark.end = cursorToIndex_InputWidget_(d, d->cursor); | ||
1605 | if (d->inFlags & markWords_InputWidgetFlag) { | ||
1606 | const iBool isFwd = d->mark.end >= d->mark.start; | ||
1607 | extendRange_InputWidget_(d, &d->mark.end, isFwd ? +1 : -1); | ||
1608 | d->mark.start = isFwd ? d->initialMark.start : d->initialMark.end; | ||
1609 | } | ||
1610 | refresh_Widget(w); | ||
1611 | return iTrue; | ||
1612 | case finished_ClickResult: | ||
1613 | d->inFlags &= ~isMarking_InputWidgetFlag; | ||
1614 | return iTrue; | ||
1615 | } | 1982 | } |
1616 | if (ev->type == SDL_MOUSEMOTION && flags_Widget(w) & keepOnTop_WidgetFlag) { | 1983 | /* Click behavior depends on device type. */ { |
1617 | const iInt2 coord = init_I2(ev->motion.x, ev->motion.y); | 1984 | const int mbResult = (deviceType_App() == desktop_AppDeviceType |
1618 | if (contains_Click(&d->click, coord)) { | 1985 | ? processPointerEvents_InputWidget_(d, ev) |
1619 | return iTrue; | 1986 | : processTouchEvents_InputWidget_(d, ev)); |
1987 | if (mbResult) { | ||
1988 | return mbResult >> 1; | ||
1620 | } | 1989 | } |
1621 | } | 1990 | } |
1622 | if (ev->type == SDL_MOUSEBUTTONDOWN && ev->button.button == SDL_BUTTON_RIGHT && | ||
1623 | contains_Widget(w, init_I2(ev->button.x, ev->button.y))) { | ||
1624 | iWidget *clipMenu = findWidget_App("clipmenu"); | ||
1625 | if (isVisible_Widget(clipMenu)) { | ||
1626 | closeMenu_Widget(clipMenu); | ||
1627 | } | ||
1628 | else { | ||
1629 | openMenuFlags_Widget(clipMenu, | ||
1630 | mouseCoord_Window(get_Window(), ev->button.which), | ||
1631 | iFalse); | ||
1632 | } | ||
1633 | return iTrue; | ||
1634 | } | ||
1635 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { | 1991 | if (ev->type == SDL_KEYUP && isFocused_Widget(w)) { |
1636 | return iTrue; | 1992 | return iTrue; |
1637 | } | 1993 | } |
@@ -1833,6 +2189,13 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1833 | return iTrue; | 2189 | return iTrue; |
1834 | } | 2190 | } |
1835 | case SDLK_TAB: | 2191 | case SDLK_TAB: |
2192 | if (mods == (KMOD_ALT | KMOD_SHIFT)) { | ||
2193 | pushUndo_InputWidget_(d); | ||
2194 | deleteMarked_InputWidget_(d); | ||
2195 | insertChar_InputWidget_(d, '\t'); | ||
2196 | contentsWereChanged_InputWidget_(d); | ||
2197 | return iTrue; | ||
2198 | } | ||
1836 | /* Allow focus switching. */ | 2199 | /* Allow focus switching. */ |
1837 | return processEvent_Widget(as_Widget(d), ev); | 2200 | return processEvent_Widget(as_Widget(d), ev); |
1838 | case SDLK_UP: | 2201 | case SDLK_UP: |
@@ -1878,6 +2241,8 @@ struct Impl_MarkPainter { | |||
1878 | const iInputLine * line; | 2241 | const iInputLine * line; |
1879 | iInt2 pos; | 2242 | iInt2 pos; |
1880 | iRanges mark; | 2243 | iRanges mark; |
2244 | iRect firstMarkRect; | ||
2245 | iRect lastMarkRect; | ||
1881 | }; | 2246 | }; |
1882 | 2247 | ||
1883 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, | 2248 | static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int origin, int advance, |
@@ -1916,7 +2281,11 @@ static iBool draw_MarkPainter_(iWrapText *wrapText, iRangecc wrappedText, int or | |||
1916 | } | 2281 | } |
1917 | rect.size.x = iMax(gap_UI / 3, rect.size.x); | 2282 | rect.size.x = iMax(gap_UI / 3, rect.size.x); |
1918 | mp->pos.y += lineHeight_Text(mp->d->font); | 2283 | mp->pos.y += lineHeight_Text(mp->d->font); |
1919 | fillRect_Paint(mp->paint, rect, uiMarked_ColorId); | 2284 | fillRect_Paint(mp->paint, rect, uiMarked_ColorId | opaque_ColorId); |
2285 | if (deviceType_App() != desktop_AppDeviceType) { | ||
2286 | if (isEmpty_Rect(mp->firstMarkRect)) mp->firstMarkRect = rect; | ||
2287 | mp->lastMarkRect = rect; | ||
2288 | } | ||
1920 | return iTrue; | 2289 | return iTrue; |
1921 | } | 2290 | } |
1922 | 2291 | ||
@@ -1924,8 +2293,9 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1924 | const iWidget *w = constAs_Widget(d); | 2293 | const iWidget *w = constAs_Widget(d); |
1925 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); | 2294 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1926 | iBool isHint = isHintVisible_InputWidget_(d); | 2295 | iBool isHint = isHintVisible_InputWidget_(d); |
1927 | const iBool isFocused = isFocused_Widget(w); | 2296 | const iBool isFocused = isFocused_Widget(w); |
1928 | const iBool isHover = isHover_Widget(w) && | 2297 | const iBool isHover = deviceType_App() == desktop_AppDeviceType && |
2298 | isHover_Widget(w) && | ||
1929 | contains_InputWidget_(d, mouseCoord_Window(get_Window(), 0)); | 2299 | contains_InputWidget_(d, mouseCoord_Window(get_Window(), 0)); |
1930 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { | 2300 | if (d->inFlags & needUpdateBuffer_InputWidgetFlag) { |
1931 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); | 2301 | updateBuffered_InputWidget_(iConstCast(iInputWidget *, d)); |
@@ -1955,6 +2325,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1955 | }; | 2325 | }; |
1956 | const iRangei visLines = visibleLineRange_InputWidget_(d); | 2326 | const iRangei visLines = visibleLineRange_InputWidget_(d); |
1957 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); | 2327 | const int visLineOffsetY = visLineOffsetY_InputWidget_(d); |
2328 | iRect markerRects[2] = { zero_Rect(), zero_Rect() }; | ||
1958 | /* If buffered, just draw the buffered copy. */ | 2329 | /* If buffered, just draw the buffered copy. */ |
1959 | if (d->buffered && !isFocused) { | 2330 | if (d->buffered && !isFocused) { |
1960 | /* Most input widgets will use this, since only one is focused at a time. */ | 2331 | /* Most input widgets will use this, since only one is focused at a time. */ |
@@ -1970,7 +2341,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1970 | .paint = &p, | 2341 | .paint = &p, |
1971 | .d = d, | 2342 | .d = d, |
1972 | .contentBounds = contentBounds, | 2343 | .contentBounds = contentBounds, |
1973 | .mark = mark_InputWidget_(d) | 2344 | .mark = mark_InputWidget_(d), |
1974 | }; | 2345 | }; |
1975 | wrapText.context = ▮ | 2346 | wrapText.context = ▮ |
1976 | wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ | 2347 | wrapText.wrapFunc = isFocused ? draw_MarkPainter_ : NULL; /* mark is drawn under each line of text */ |
@@ -1981,11 +2352,14 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1981 | marker.pos = drawPos; | 2352 | marker.pos = drawPos; |
1982 | addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ | 2353 | addv_I2(&drawPos, draw_WrapText(&wrapText, d->font, drawPos, fg).advance); /* lines end with \n */ |
1983 | } | 2354 | } |
2355 | markerRects[0] = marker.firstMarkRect; | ||
2356 | markerRects[1] = marker.lastMarkRect; | ||
1984 | wrapText.wrapFunc = NULL; | 2357 | wrapText.wrapFunc = NULL; |
1985 | wrapText.context = NULL; | 2358 | wrapText.context = NULL; |
1986 | } | 2359 | } |
1987 | /* Draw the insertion point. */ | 2360 | /* Draw the insertion point. */ |
1988 | if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y)) { | 2361 | if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && |
2362 | (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) { | ||
1989 | iInt2 curSize; | 2363 | iInt2 curSize; |
1990 | iRangecc cursorChar = iNullRange; | 2364 | iRangecc cursorChar = iNullRange; |
1991 | int visWrapsAbove = 0; | 2365 | int visWrapsAbove = 0; |
@@ -1998,8 +2372,8 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1998 | cursorChar.start = charPos_InputWidget_(d, d->cursor); | 2372 | cursorChar.start = charPos_InputWidget_(d, d->cursor); |
1999 | iChar ch = 0; | 2373 | iChar ch = 0; |
2000 | int n = decodeBytes_MultibyteChar(cursorChar.start, | 2374 | int n = decodeBytes_MultibyteChar(cursorChar.start, |
2001 | constEnd_String(&constCursorLine_InputWidget_(d)->text), | 2375 | constEnd_String(&constCursorLine_InputWidget_(d)->text), |
2002 | &ch); | 2376 | &ch); |
2003 | cursorChar.end = cursorChar.start + iMax(n, 0); | 2377 | cursorChar.end = cursorChar.start + iMax(n, 0); |
2004 | if (ch) { | 2378 | if (ch) { |
2005 | if (d->inFlags & isSensitive_InputWidgetFlag) { | 2379 | if (d->inFlags & isSensitive_InputWidgetFlag) { |
@@ -2033,6 +2407,11 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
2033 | } | 2407 | } |
2034 | } | 2408 | } |
2035 | unsetClip_Paint(&p); | 2409 | unsetClip_Paint(&p); |
2410 | if (!isEmpty_Rect(markerRects[0])) { | ||
2411 | for (int i = 0; i < 2; ++i) { | ||
2412 | drawPin_Paint(&p, markerRects[i], i, uiTextCaution_ColorId); | ||
2413 | } | ||
2414 | } | ||
2036 | drawChildren_Widget(w); | 2415 | drawChildren_Widget(w); |
2037 | } | 2416 | } |
2038 | 2417 | ||
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index 0d327ca6..f70c81af 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -68,6 +68,7 @@ int minLines_InputWidget (const iInputWidget *); | |||
68 | int maxLines_InputWidget (const iInputWidget *); | 68 | int maxLines_InputWidget (const iInputWidget *); |
69 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); | 69 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); |
70 | const iString * text_InputWidget (const iInputWidget *); | 70 | const iString * text_InputWidget (const iInputWidget *); |
71 | int font_InputWidget (const iInputWidget *); | ||
71 | 72 | ||
72 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { | 73 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { |
73 | return cstr_String(text_InputWidget(d)); | 74 | return cstr_String(text_InputWidget(d)); |
diff --git a/src/ui/keys.c b/src/ui/keys.c index 6de30f57..30072572 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c | |||
@@ -213,6 +213,7 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = | |||
213 | { 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, | 213 | { 46, { "${keys.link.homerow.hover}", 'h', 0, "document.linkkeys arg:1 hover:1" }, 0 }, |
214 | { 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 }, | 214 | { 47, { "${keys.link.homerow.next}", '.', 0, "document.linkkeys more:1" }, 0 }, |
215 | { 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 }, | 215 | { 50, { "${keys.bookmark.add}", 'd', KMOD_PRIMARY, "bookmark.add" }, 0 }, |
216 | { 51, { "${keys.bookmark.addfolder}", 'n', KMOD_SHIFT, "bookmarks.addfolder" }, 0 }, | ||
216 | { 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 }, | 217 | { 55, { "${keys.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, 0 }, |
217 | { 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 }, | 218 | { 60, { "${keys.findtext}", 'f', KMOD_PRIMARY, "focus.set id:find.input" }, 0 }, |
218 | { 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, | 219 | { 70, { "${keys.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, 0 }, |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index ef306ab9..cfc81863 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -44,11 +44,13 @@ struct Impl_LabelWidget { | |||
44 | iString command; | 44 | iString command; |
45 | iClick click; | 45 | iClick click; |
46 | struct { | 46 | struct { |
47 | uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ | 47 | uint8_t alignVisual : 1; /* align according to visible bounds, not font metrics */ |
48 | uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ | 48 | uint8_t noAutoMinHeight : 1; /* minimum height is not set automatically */ |
49 | uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ | 49 | uint8_t drawAsOutline : 1; /* draw as outline, filled with background color */ |
50 | uint8_t noTopFrame : 1; | 50 | uint8_t noTopFrame : 1; |
51 | uint8_t wrap : 1; | 51 | uint8_t wrap : 1; |
52 | uint8_t allCaps : 1; | ||
53 | uint8_t removeTrailingColon : 1; | ||
52 | } flags; | 54 | } flags; |
53 | }; | 55 | }; |
54 | 56 | ||
@@ -68,7 +70,7 @@ static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) { | |||
68 | : corner == 1 ? init_I2(w->padding[2], w->padding[1]) | 70 | : corner == 1 ? init_I2(w->padding[2], w->padding[1]) |
69 | : corner == 2 ? init_I2(w->padding[2], w->padding[3]) | 71 | : corner == 2 ? init_I2(w->padding[2], w->padding[3]) |
70 | : init_I2(w->padding[0], w->padding[3])); | 72 | : init_I2(w->padding[0], w->padding[3])); |
71 | #if defined (iPlatformAppleMobile) | 73 | #if defined (iPlatformMobile) |
72 | return add_I2(widgetPad, | 74 | return add_I2(widgetPad, |
73 | init_I2(flags & tight_WidgetFlag ? 2 * gap_UI : (4 * gap_UI), | 75 | init_I2(flags & tight_WidgetFlag ? 2 * gap_UI : (4 * gap_UI), |
74 | (flags & extraPadding_WidgetFlag ? 1.5f : 1.0f) * 3 * gap_UI / 2)); | 76 | (flags & extraPadding_WidgetFlag ? 1.5f : 1.0f) * 3 * gap_UI / 2)); |
@@ -124,6 +126,11 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { | |||
124 | updateKey_LabelWidget_(d); | 126 | updateKey_LabelWidget_(d); |
125 | return iFalse; | 127 | return iFalse; |
126 | } | 128 | } |
129 | else if (isCommand_Widget(w, ev, "focus.gained") || | ||
130 | isCommand_Widget(w, ev, "focus.lost")) { | ||
131 | refresh_Widget(d); | ||
132 | return iFalse; | ||
133 | } | ||
127 | if (!isEmpty_String(&d->command)) { | 134 | if (!isEmpty_String(&d->command)) { |
128 | #if 0 && defined (iPlatformAppleMobile) | 135 | #if 0 && defined (iPlatformAppleMobile) |
129 | /* Touch allows activating any button on release. */ | 136 | /* Touch allows activating any button on release. */ |
@@ -159,8 +166,15 @@ static iBool processEvent_LabelWidget_(iLabelWidget *d, const SDL_Event *ev) { | |||
159 | switch (ev->type) { | 166 | switch (ev->type) { |
160 | case SDL_KEYDOWN: { | 167 | case SDL_KEYDOWN: { |
161 | const int mods = ev->key.keysym.mod; | 168 | const int mods = ev->key.keysym.mod; |
162 | if (d->key && ev->key.keysym.sym == d->key && checkModifiers_(mods, d->kmods)) { | 169 | const int sym = ev->key.keysym.sym; |
170 | if (d->key && sym == d->key && checkModifiers_(mods, d->kmods)) { | ||
171 | trigger_LabelWidget_(d); | ||
172 | return iTrue; | ||
173 | } | ||
174 | if (isFocused_Widget(d) && mods == 0 && | ||
175 | (sym == SDLK_RETURN || sym == SDLK_KP_ENTER)) { | ||
163 | trigger_LabelWidget_(d); | 176 | trigger_LabelWidget_(d); |
177 | refresh_Widget(d); | ||
164 | return iTrue; | 178 | return iTrue; |
165 | } | 179 | } |
166 | break; | 180 | break; |
@@ -174,14 +188,17 @@ static void keyStr_LabelWidget_(const iLabelWidget *d, iString *str) { | |||
174 | toString_Sym(d->key, d->kmods, str); | 188 | toString_Sym(d->key, d->kmods, str); |
175 | } | 189 | } |
176 | 190 | ||
177 | static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *frame1, int *frame2) { | 191 | static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int *frame1, int *frame2, |
192 | int *icon, int *meta) { | ||
178 | const iWidget *w = constAs_Widget(d); | 193 | const iWidget *w = constAs_Widget(d); |
179 | const int64_t flags = flags_Widget(w); | 194 | const int64_t flags = flags_Widget(w); |
195 | const iBool isFocus = (flags & focusable_WidgetFlag && isFocused_Widget(d)); | ||
180 | const iBool isPress = (flags & pressed_WidgetFlag) != 0; | 196 | const iBool isPress = (flags & pressed_WidgetFlag) != 0; |
181 | const iBool isSel = (flags & selected_WidgetFlag) != 0; | 197 | const iBool isSel = (flags & selected_WidgetFlag) != 0; |
182 | const iBool isFrameless = (flags & frameless_WidgetFlag) != 0; | 198 | const iBool isFrameless = (flags & frameless_WidgetFlag) != 0; |
183 | const iBool isButton = d->click.button != 0; | 199 | const iBool isButton = d->click.button != 0; |
184 | const iBool isKeyRoot = (w->root == get_Window()->keyRoot); | 200 | const iBool isKeyRoot = (w->root == get_Window()->keyRoot); |
201 | const iBool isDarkTheme = isDark_ColorTheme(colorTheme_App()); | ||
185 | /* Default color state. */ | 202 | /* Default color state. */ |
186 | *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ? | 203 | *bg = isButton && ~flags & noBackground_WidgetFlag ? (d->widget.bgColor != none_ColorId ? |
187 | d->widget.bgColor : uiBackground_ColorId) | 204 | d->widget.bgColor : uiBackground_ColorId) |
@@ -189,8 +206,12 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
189 | *fg = uiText_ColorId; | 206 | *fg = uiText_ColorId; |
190 | *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; | 207 | *frame1 = isButton ? uiEmboss1_ColorId : d->widget.frameColor; |
191 | *frame2 = isButton ? uiEmboss2_ColorId : *frame1; | 208 | *frame2 = isButton ? uiEmboss2_ColorId : *frame1; |
209 | *icon = uiIcon_ColorId; | ||
210 | *meta = uiTextShortcut_ColorId; | ||
192 | if (flags & disabled_WidgetFlag && isButton) { | 211 | if (flags & disabled_WidgetFlag && isButton) { |
193 | *fg = uiTextDisabled_ColorId; | 212 | *icon = uiTextDisabled_ColorId; |
213 | *fg = uiTextDisabled_ColorId; | ||
214 | *meta = uiTextDisabled_ColorId; | ||
194 | } | 215 | } |
195 | if (isSel) { | 216 | if (isSel) { |
196 | *bg = uiBackgroundSelected_ColorId; | 217 | *bg = uiBackgroundSelected_ColorId; |
@@ -210,9 +231,15 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
210 | } | 231 | } |
211 | } | 232 | } |
212 | } | 233 | } |
234 | if (isFocus) { | ||
235 | *frame1 = *frame2 = (isSel ? uiText_ColorId : uiInputFrameFocused_ColorId); | ||
236 | } | ||
213 | int colorEscape = none_ColorId; | 237 | int colorEscape = none_ColorId; |
214 | if (startsWith_String(&d->label, "\v")) { | 238 | if (startsWith_String(&d->label, "\v")) { |
215 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ | 239 | colorEscape = parseEscape_Color(cstr_String(&d->label), NULL); |
240 | } | ||
241 | if (colorEscape == uiTextCaution_ColorId) { | ||
242 | *icon = *meta = colorEscape; | ||
216 | } | 243 | } |
217 | if (isHover_LabelWidget_(d)) { | 244 | if (isHover_LabelWidget_(d)) { |
218 | if (isFrameless) { | 245 | if (isFrameless) { |
@@ -221,43 +248,48 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
221 | } | 248 | } |
222 | else { | 249 | else { |
223 | /* Frames matching color escaped text. */ | 250 | /* Frames matching color escaped text. */ |
224 | if (colorEscape != none_ColorId) { | 251 | if (colorEscape == uiTextCaution_ColorId) { |
225 | if (isDark_ColorTheme(colorTheme_App())) { | 252 | *frame1 = colorEscape; |
226 | *frame1 = colorEscape; | 253 | *frame2 = isDarkTheme ? darker_Color(*frame1) : lighter_Color(*frame1); |
227 | *frame2 = darker_Color(*frame1); | ||
228 | } | ||
229 | else { | ||
230 | *bg = *frame1 = *frame2 = colorEscape; | ||
231 | *fg = white_ColorId | permanent_ColorId; | ||
232 | } | ||
233 | } | 254 | } |
234 | else if (isSel) { | 255 | else if (isSel) { |
235 | *frame1 = uiEmbossSelectedHover1_ColorId; | 256 | *frame1 = uiEmbossSelectedHover1_ColorId; |
236 | *frame2 = uiEmbossSelectedHover2_ColorId; | 257 | *frame2 = uiEmbossSelectedHover2_ColorId; |
237 | } | 258 | } |
238 | else { | 259 | else { |
239 | if (isButton) *bg = uiBackgroundHover_ColorId; | ||
240 | *frame1 = uiEmbossHover1_ColorId; | 260 | *frame1 = uiEmbossHover1_ColorId; |
241 | *frame2 = uiEmbossHover2_ColorId; | 261 | *frame2 = uiEmbossHover2_ColorId; |
242 | } | 262 | } |
243 | } | 263 | } |
264 | if (colorEscape == uiTextCaution_ColorId) { | ||
265 | *icon = *meta = *fg = colorEscape; | ||
266 | *bg = isDarkTheme ? darker_Color(colorEscape) : lighter_Color(colorEscape); | ||
267 | } | ||
268 | } | ||
269 | if (d->forceFg >= 0) { | ||
270 | *fg = *icon = *meta = d->forceFg; | ||
244 | } | 271 | } |
245 | if (isPress) { | 272 | if (isPress) { |
246 | *bg = uiBackgroundPressed_ColorId | permanent_ColorId; | 273 | if (colorEscape == uiTextAction_ColorId || colorEscape == uiTextCaution_ColorId) { |
247 | if (isButton) { | 274 | *bg = colorEscape; |
248 | *frame1 = uiEmbossPressed1_ColorId; | 275 | *frame1 = *bg; |
249 | *frame2 = colorEscape != none_ColorId ? colorEscape : uiEmbossPressed2_ColorId; | 276 | *frame2 = *bg; |
250 | } | 277 | *fg = *icon = *meta = (isDarkTheme ? black_ColorId : white_ColorId) | permanent_ColorId; |
251 | if (colorEscape == none_ColorId || colorEscape == uiTextAction_ColorId) { | ||
252 | *fg = uiTextPressed_ColorId | permanent_ColorId; | ||
253 | } | 278 | } |
254 | else { | 279 | else { |
255 | *fg = isDark_ColorTheme(colorTheme_App()) ? white_ColorId : black_ColorId; | 280 | *bg = uiBackgroundPressed_ColorId | permanent_ColorId; |
281 | if (isButton) { | ||
282 | *frame1 = uiEmbossPressed1_ColorId; | ||
283 | *frame2 = colorEscape != none_ColorId ? colorEscape : uiEmbossPressed2_ColorId; | ||
284 | } | ||
285 | //if (colorEscape == none_ColorId || colorEscape == uiTextAction_ColorId) { | ||
286 | *fg = *icon = *meta = uiTextPressed_ColorId | permanent_ColorId; | ||
287 | // } | ||
288 | // else { | ||
289 | // *fg = (isDark_ColorTheme(colorTheme_App()) ? white_ColorId : black_ColorId) | permanent_ColorId; | ||
290 | // } | ||
256 | } | 291 | } |
257 | } | 292 | } |
258 | if (d->forceFg >= 0) { | ||
259 | *fg = d->forceFg; | ||
260 | } | ||
261 | } | 293 | } |
262 | 294 | ||
263 | iLocalDef int iconPadding_LabelWidget_(const iLabelWidget *d) { | 295 | iLocalDef int iconPadding_LabelWidget_(const iLabelWidget *d) { |
@@ -287,13 +319,18 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
287 | } | 319 | } |
288 | iPaint p; | 320 | iPaint p; |
289 | init_Paint(&p); | 321 | init_Paint(&p); |
290 | int bg, fg, frame, frame2; | 322 | int bg, fg, frame, frame2, iconColor, metaColor; |
291 | getColors_LabelWidget_(d, &bg, &fg, &frame, &frame2); | 323 | getColors_LabelWidget_(d, &bg, &fg, &frame, &frame2, &iconColor, &metaColor); |
292 | const iBool isCaution = startsWith_String(&d->label, uiTextCaution_ColorEscape); | 324 | const enum iColorId colorEscape = parseEscape_Color(cstr_String(&d->label), NULL); |
325 | const iBool isCaution = (colorEscape == uiTextCaution_ColorId); | ||
293 | if (bg >= 0) { | 326 | if (bg >= 0) { |
294 | fillRect_Paint(&p, rect, isCaution && isHover ? uiMarked_ColorId : bg); | 327 | fillRect_Paint(&p, rect, bg); |
328 | } | ||
329 | if (isFocused_Widget(w)) { | ||
330 | iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1)); | ||
331 | drawRectThickness_Paint(&p, frameRect, gap_UI / 4, frame); | ||
295 | } | 332 | } |
296 | if (~flags & frameless_WidgetFlag) { | 333 | else if (~flags & frameless_WidgetFlag) { |
297 | iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1)); | 334 | iRect frameRect = adjusted_Rect(rect, zero_I2(), init1_I2(-1)); |
298 | if (isButton) { | 335 | if (isButton) { |
299 | iInt2 points[] = { | 336 | iInt2 points[] = { |
@@ -310,12 +347,18 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
310 | } | 347 | } |
311 | #endif | 348 | #endif |
312 | drawLines_Paint(&p, points + 2, 3, frame2); | 349 | drawLines_Paint(&p, points + 2, 3, frame2); |
313 | drawLines_Paint( | 350 | drawLines_Paint(&p, |
314 | &p, points, !isHover && d->flags.noTopFrame ? 2 : 3, frame); | 351 | points, |
352 | isFocused_Widget(w) ? 3 : (!isHover && d->flags.noTopFrame ? 2 : 3), | ||
353 | frame); | ||
315 | } | 354 | } |
316 | } | 355 | } |
317 | setClip_Paint(&p, rect); | 356 | setClip_Paint(&p, rect); |
318 | const int iconPad = iconPadding_LabelWidget_(d); | 357 | const int iconPad = iconPadding_LabelWidget_(d); |
358 | // const int iconColor = isCaution ? uiTextCaution_ColorId | ||
359 | // : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg | ||
360 | // : isHover ? uiIconHover_ColorId | ||
361 | // : uiIcon_ColorId; | ||
319 | if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ | 362 | if (d->icon && d->icon != 0x20) { /* no need to draw an empty icon */ |
320 | iString str; | 363 | iString str; |
321 | initUnicodeN_String(&str, &d->icon, 1); | 364 | initUnicodeN_String(&str, &d->icon, 1); |
@@ -329,16 +372,13 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
329 | -gap_UI / 8)), | 372 | -gap_UI / 8)), |
330 | init_I2(iconPad, lineHeight_Text(d->font)) }, | 373 | init_I2(iconPad, lineHeight_Text(d->font)) }, |
331 | iTrue, | 374 | iTrue, |
332 | isCaution ? uiTextCaution_ColorId | 375 | iconColor, |
333 | : flags & (disabled_WidgetFlag | pressed_WidgetFlag) ? fg | ||
334 | : isHover ? uiIconHover_ColorId | ||
335 | : uiIcon_ColorId, | ||
336 | "%s", | 376 | "%s", |
337 | cstr_String(&str)); | 377 | cstr_String(&str)); |
338 | deinit_String(&str); | 378 | deinit_String(&str); |
339 | } | 379 | } |
340 | if (d->flags.wrap) { | 380 | if (d->flags.wrap) { |
341 | const iRect cont = contentBounds_LabelWidget_(d); //djusted_Rect(innerBounds_Widget(w), init_I2(iconPad, 0), zero_I2()); | 381 | const iRect cont = contentBounds_LabelWidget_(d); |
342 | drawWrapRange_Text( | 382 | drawWrapRange_Text( |
343 | d->font, topLeft_Rect(cont), width_Rect(cont), fg, range_String(&d->label)); | 383 | d->font, topLeft_Rect(cont), width_Rect(cont), fg, range_String(&d->label)); |
344 | } | 384 | } |
@@ -353,9 +393,11 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
353 | add_I2(topRight_Rect(bounds), | 393 | add_I2(topRight_Rect(bounds), |
354 | addX_I2(negX_I2(padding_LabelWidget_(d, 1)), | 394 | addX_I2(negX_I2(padding_LabelWidget_(d, 1)), |
355 | deviceType_App() == tablet_AppDeviceType ? gap_UI : 0)), | 395 | deviceType_App() == tablet_AppDeviceType ? gap_UI : 0)), |
356 | flags & pressed_WidgetFlag ? fg | 396 | metaColor,/* |
357 | : isCaution ? uiTextCaution_ColorId | 397 | isHover || flags & pressed_WidgetFlag ? fg |
358 | : uiTextShortcut_ColorId, | 398 | // : isCaution ? uiTextCaution_ColorId |
399 | : colorEscape != none_ColorId ? colorEscape | ||
400 | : uiTextShortcut_ColorId,*/ | ||
359 | right_Alignment, | 401 | right_Alignment, |
360 | cstr_String(&str)); | 402 | cstr_String(&str)); |
361 | deinit_String(&str); | 403 | deinit_String(&str); |
@@ -385,7 +427,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
385 | drawCentered_Text(d->font, | 427 | drawCentered_Text(d->font, |
386 | (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), | 428 | (iRect){ addX_I2(topRight_Rect(chRect), -iconPad), |
387 | init_I2(chSize, height_Rect(chRect)) }, | 429 | init_I2(chSize, height_Rect(chRect)) }, |
388 | iTrue, uiSeparator_ColorId, rightAngle_Icon); | 430 | iTrue, iconColor, rightAngle_Icon); |
389 | } | 431 | } |
390 | unsetClip_Paint(&p); | 432 | unsetClip_Paint(&p); |
391 | } | 433 | } |
@@ -442,11 +484,18 @@ void updateSize_LabelWidget(iLabelWidget *d) { | |||
442 | 484 | ||
443 | static void replaceVariables_LabelWidget_(iLabelWidget *d) { | 485 | static void replaceVariables_LabelWidget_(iLabelWidget *d) { |
444 | translate_Lang(&d->label); | 486 | translate_Lang(&d->label); |
487 | if (d->flags.allCaps) { | ||
488 | set_String(&d->label, collect_String(upper_String(&d->label))); | ||
489 | } | ||
490 | if (d->flags.removeTrailingColon && endsWith_String(&d->label, ":")) { | ||
491 | removeEnd_String(&d->label, 1); | ||
492 | } | ||
445 | } | 493 | } |
446 | 494 | ||
447 | void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | 495 | void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { |
448 | iWidget *w = &d->widget; | 496 | iWidget *w = &d->widget; |
449 | init_Widget(w); | 497 | init_Widget(w); |
498 | iZap(d->flags); | ||
450 | d->font = uiLabel_FontId; | 499 | d->font = uiLabel_FontId; |
451 | d->forceFg = none_ColorId; | 500 | d->forceFg = none_ColorId; |
452 | d->icon = 0; | 501 | d->icon = 0; |
@@ -463,12 +512,7 @@ void init_LabelWidget(iLabelWidget *d, const char *label, const char *cmd) { | |||
463 | d->key = 0; | 512 | d->key = 0; |
464 | d->kmods = 0; | 513 | d->kmods = 0; |
465 | init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); | 514 | init_Click(&d->click, d, !isEmpty_String(&d->command) ? SDL_BUTTON_LEFT : 0); |
466 | setFlags_Widget(w, hover_WidgetFlag, d->click.button != 0); | 515 | setFlags_Widget(w, focusable_WidgetFlag | hover_WidgetFlag, d->click.button != 0); |
467 | d->flags.alignVisual = iFalse; | ||
468 | d->flags.noAutoMinHeight = iFalse; | ||
469 | d->flags.drawAsOutline = iFalse; | ||
470 | d->flags.noTopFrame = iFalse; | ||
471 | d->flags.wrap = iFalse; | ||
472 | updateSize_LabelWidget(d); | 516 | updateSize_LabelWidget(d); |
473 | updateKey_LabelWidget_(d); /* could be bound to another key */ | 517 | updateKey_LabelWidget_(d); /* could be bound to another key */ |
474 | } | 518 | } |
@@ -499,6 +543,14 @@ void setText_LabelWidget(iLabelWidget *d, const iString *text) { | |||
499 | } | 543 | } |
500 | } | 544 | } |
501 | 545 | ||
546 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | ||
547 | updateTextCStr_LabelWidget(d, text); | ||
548 | updateSize_LabelWidget(d); | ||
549 | if (isWrapped_LabelWidget(d)) { | ||
550 | sizeChanged_LabelWidget_(d); | ||
551 | } | ||
552 | } | ||
553 | |||
502 | void setAlignVisually_LabelWidget(iLabelWidget *d, iBool alignVisual) { | 554 | void setAlignVisually_LabelWidget(iLabelWidget *d, iBool alignVisual) { |
503 | d->flags.alignVisual = alignVisual; | 555 | d->flags.alignVisual = alignVisual; |
504 | } | 556 | } |
@@ -525,6 +577,20 @@ void setOutline_LabelWidget(iLabelWidget *d, iBool drawAsOutline) { | |||
525 | } | 577 | } |
526 | } | 578 | } |
527 | 579 | ||
580 | void setAllCaps_LabelWidget(iLabelWidget *d, iBool allCaps) { | ||
581 | if (d) { | ||
582 | d->flags.allCaps = allCaps; | ||
583 | replaceVariables_LabelWidget_(d); | ||
584 | } | ||
585 | } | ||
586 | |||
587 | void setRemoveTrailingColon_LabelWidget(iLabelWidget *d, iBool removeTrailingColon) { | ||
588 | if (d) { | ||
589 | d->flags.removeTrailingColon = removeTrailingColon; | ||
590 | replaceVariables_LabelWidget_(d); | ||
591 | } | ||
592 | } | ||
593 | |||
528 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { | 594 | void updateText_LabelWidget(iLabelWidget *d, const iString *text) { |
529 | set_String(&d->label, text); | 595 | set_String(&d->label, text); |
530 | set_String(&d->srcLabel, text); | 596 | set_String(&d->srcLabel, text); |
@@ -533,10 +599,12 @@ void updateText_LabelWidget(iLabelWidget *d, const iString *text) { | |||
533 | } | 599 | } |
534 | 600 | ||
535 | void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | 601 | void updateTextCStr_LabelWidget(iLabelWidget *d, const char *text) { |
536 | setCStr_String(&d->label, text); | 602 | if (d) { |
537 | set_String(&d->srcLabel, &d->label); | 603 | setCStr_String(&d->label, text); |
538 | replaceVariables_LabelWidget_(d); | 604 | set_String(&d->srcLabel, &d->label); |
539 | refresh_Widget(&d->widget); | 605 | replaceVariables_LabelWidget_(d); |
606 | refresh_Widget(&d->widget); | ||
607 | } | ||
540 | } | 608 | } |
541 | 609 | ||
542 | void updateTextAndResizeWidthCStr_LabelWidget(iLabelWidget *d, const char *text) { | 610 | void updateTextAndResizeWidthCStr_LabelWidget(iLabelWidget *d, const char *text) { |
@@ -544,13 +612,6 @@ void updateTextAndResizeWidthCStr_LabelWidget(iLabelWidget *d, const char *text) | |||
544 | d->widget.rect.size.x = defaultSize_LabelWidget(d).x; | 612 | d->widget.rect.size.x = defaultSize_LabelWidget(d).x; |
545 | } | 613 | } |
546 | 614 | ||
547 | void setTextCStr_LabelWidget(iLabelWidget *d, const char *text) { | ||
548 | setCStr_String(&d->label, text); | ||
549 | set_String(&d->srcLabel, &d->label); | ||
550 | replaceVariables_LabelWidget_(d); | ||
551 | updateSize_LabelWidget(d); | ||
552 | } | ||
553 | |||
554 | void setCommand_LabelWidget(iLabelWidget *d, const iString *command) { | 615 | void setCommand_LabelWidget(iLabelWidget *d, const iString *command) { |
555 | set_String(&d->command, command); | 616 | set_String(&d->command, command); |
556 | } | 617 | } |
@@ -567,19 +628,8 @@ iBool checkIcon_LabelWidget(iLabelWidget *d) { | |||
567 | d->icon = 0; | 628 | d->icon = 0; |
568 | return iFalse; | 629 | return iFalse; |
569 | } | 630 | } |
570 | iStringConstIterator iter; | 631 | d->icon = removeIconPrefix_String(&d->label); |
571 | init_StringConstIterator(&iter, &d->label); | 632 | return d->icon != 0; |
572 | const iChar icon = iter.value; | ||
573 | next_StringConstIterator(&iter); | ||
574 | if (iter.value == ' ' && icon >= 0x100) { | ||
575 | d->icon = icon; | ||
576 | remove_Block(&d->label.chars, 0, iter.next - constBegin_String(&d->label)); | ||
577 | return iTrue; | ||
578 | } | ||
579 | else { | ||
580 | d->icon = 0; | ||
581 | } | ||
582 | return iFalse; | ||
583 | } | 633 | } |
584 | 634 | ||
585 | iChar icon_LabelWidget(const iLabelWidget *d) { | 635 | iChar icon_LabelWidget(const iLabelWidget *d) { |
diff --git a/src/ui/labelwidget.h b/src/ui/labelwidget.h index b8b6fd87..6275d2c8 100644 --- a/src/ui/labelwidget.h +++ b/src/ui/labelwidget.h | |||
@@ -30,10 +30,12 @@ iDeclareWidgetClass(LabelWidget) | |||
30 | iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *command) | 30 | iDeclareObjectConstructionArgs(LabelWidget, const char *label, const char *command) |
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); | 34 | void setNoTopFrame_LabelWidget (iLabelWidget *, iBool noTopFrame); |
35 | void setWrap_LabelWidget (iLabelWidget *, iBool wrap); | 35 | void setWrap_LabelWidget (iLabelWidget *, iBool wrap); |
36 | void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); | 36 | void setOutline_LabelWidget (iLabelWidget *, iBool drawAsOutline); |
37 | void setAllCaps_LabelWidget (iLabelWidget *, iBool allCaps); | ||
38 | void setRemoveTrailingColon_LabelWidget (iLabelWidget *, iBool removeTrailingColon); | ||
37 | void setFont_LabelWidget (iLabelWidget *, int fontId); | 39 | void setFont_LabelWidget (iLabelWidget *, int fontId); |
38 | void setTextColor_LabelWidget (iLabelWidget *, int color); | 40 | void setTextColor_LabelWidget (iLabelWidget *, int color); |
39 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ | 41 | void setText_LabelWidget (iLabelWidget *, const iString *text); /* resizes widget */ |
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index d51516d1..ca15cc20 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -32,8 +32,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | #include <the_Foundation/intset.h> | 32 | #include <the_Foundation/intset.h> |
33 | 33 | ||
34 | void init_ListItem(iListItem *d) { | 34 | void init_ListItem(iListItem *d) { |
35 | d->isSeparator = iFalse; | 35 | d->isSeparator = iFalse; |
36 | d->isSelected = iFalse; | 36 | d->isSelected = iFalse; |
37 | d->isDraggable = iFalse; | ||
38 | d->isDropTarget = iFalse; | ||
37 | } | 39 | } |
38 | 40 | ||
39 | void deinit_ListItem(iListItem *d) { | 41 | void deinit_ListItem(iListItem *d) { |
@@ -54,6 +56,8 @@ struct Impl_ListWidget { | |||
54 | int itemHeight; | 56 | int itemHeight; |
55 | iPtrArray items; | 57 | iPtrArray items; |
56 | size_t hoverItem; | 58 | size_t hoverItem; |
59 | size_t dragItem; | ||
60 | iInt2 dragOrigin; /* offset from mouse to drag item's top-left corner */ | ||
57 | iClick click; | 61 | iClick click; |
58 | iIntSet invalidItems; | 62 | iIntSet invalidItems; |
59 | iVisBuf *visBuf; | 63 | iVisBuf *visBuf; |
@@ -95,6 +99,8 @@ void init_ListWidget(iListWidget *d) { | |||
95 | d->noHoverWhileScrolling = iFalse; | 99 | d->noHoverWhileScrolling = iFalse; |
96 | init_PtrArray(&d->items); | 100 | init_PtrArray(&d->items); |
97 | d->hoverItem = iInvalidPos; | 101 | d->hoverItem = iInvalidPos; |
102 | d->dragItem = iInvalidPos; | ||
103 | d->dragOrigin = zero_I2(); | ||
98 | init_Click(&d->click, d, SDL_BUTTON_LEFT); | 104 | init_Click(&d->click, d, SDL_BUTTON_LEFT); |
99 | init_IntSet(&d->invalidItems); | 105 | init_IntSet(&d->invalidItems); |
100 | d->visBuf = new_VisBuf(); | 106 | d->visBuf = new_VisBuf(); |
@@ -248,6 +254,10 @@ const iAnyObject *constItem_ListWidget(const iListWidget *d, size_t index) { | |||
248 | return NULL; | 254 | return NULL; |
249 | } | 255 | } |
250 | 256 | ||
257 | const iAnyObject *constDragItem_ListWidget(const iListWidget *d) { | ||
258 | return constItem_ListWidget(d, d->dragItem); | ||
259 | } | ||
260 | |||
251 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { | 261 | const iAnyObject *constHoverItem_ListWidget(const iListWidget *d) { |
252 | return constItem_ListWidget(d, d->hoverItem); | 262 | return constItem_ListWidget(d, d->hoverItem); |
253 | } | 263 | } |
@@ -267,7 +277,7 @@ size_t hoverItemIndex_ListWidget(const iListWidget *d) { | |||
267 | return d->hoverItem; | 277 | return d->hoverItem; |
268 | } | 278 | } |
269 | 279 | ||
270 | static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { | 280 | void setHoverItem_ListWidget(iListWidget *d, size_t index) { |
271 | if (index < size_PtrArray(&d->items)) { | 281 | if (index < size_PtrArray(&d->items)) { |
272 | const iListItem *item = at_PtrArray(&d->items, index); | 282 | const iListItem *item = at_PtrArray(&d->items, index); |
273 | if (item->isSeparator) { | 283 | if (item->isSeparator) { |
@@ -284,7 +294,7 @@ static void setHoverItem_ListWidget_(iListWidget *d, size_t index) { | |||
284 | 294 | ||
285 | void updateMouseHover_ListWidget(iListWidget *d) { | 295 | void updateMouseHover_ListWidget(iListWidget *d) { |
286 | const iInt2 mouse = mouseCoord_Window(get_Window(), 0); | 296 | const iInt2 mouse = mouseCoord_Window(get_Window(), 0); |
287 | setHoverItem_ListWidget_(d, itemIndex_ListWidget(d, mouse)); | 297 | setHoverItem_ListWidget(d, itemIndex_ListWidget(d, mouse)); |
288 | } | 298 | } |
289 | 299 | ||
290 | void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { | 300 | void sort_ListWidget(iListWidget *d, int (*cmp)(const iListItem **item1, const iListItem **item2)) { |
@@ -308,7 +318,51 @@ static void updateHover_ListWidget_(iListWidget *d, const iInt2 mouse) { | |||
308 | contains_Widget(constAs_Widget(d), mouse)) { | 318 | contains_Widget(constAs_Widget(d), mouse)) { |
309 | hover = itemIndex_ListWidget(d, mouse); | 319 | hover = itemIndex_ListWidget(d, mouse); |
310 | } | 320 | } |
311 | setHoverItem_ListWidget_(d, hover); | 321 | setHoverItem_ListWidget(d, hover); |
322 | } | ||
323 | |||
324 | static size_t resolveDragDestination_ListWidget_(const iListWidget *d, iInt2 dstPos, iBool *isOnto) { | ||
325 | size_t index = itemIndex_ListWidget(d, dstPos); | ||
326 | const iListItem *item = constItem_ListWidget(d, index); | ||
327 | if (!item) { | ||
328 | index = (dstPos.y < mid_Rect(bounds_Widget(constAs_Widget(d))).y ? 0 : (numItems_ListWidget(d) - 1)); | ||
329 | item = constItem_ListWidget(d, index); | ||
330 | } | ||
331 | const iRect rect = itemRect_ListWidget(d, index); | ||
332 | const iRangei span = ySpan_Rect(rect); | ||
333 | if (item->isDropTarget) { | ||
334 | const int pad = size_Range(&span) / 3; | ||
335 | if (dstPos.y >= span.start + pad && dstPos.y < span.end - pad) { | ||
336 | *isOnto = iTrue; | ||
337 | return index; | ||
338 | } | ||
339 | } | ||
340 | if (dstPos.y - span.start > span.end - dstPos.y) { | ||
341 | index++; | ||
342 | } | ||
343 | index = iMin(index, numItems_ListWidget(d)); | ||
344 | *isOnto = iFalse; | ||
345 | return index; | ||
346 | } | ||
347 | |||
348 | static iBool endDrag_ListWidget_(iListWidget *d, iInt2 endPos) { | ||
349 | if (d->dragItem == iInvalidPos) { | ||
350 | return iFalse; | ||
351 | } | ||
352 | stop_Anim(&d->scrollY.pos); | ||
353 | iBool isOnto; | ||
354 | const size_t index = resolveDragDestination_ListWidget_(d, endPos, &isOnto); | ||
355 | if (index != d->dragItem) { | ||
356 | if (isOnto) { | ||
357 | postCommand_Widget(d, "list.dragged arg:%zu onto:%zu", d->dragItem, index); | ||
358 | } | ||
359 | else { | ||
360 | postCommand_Widget(d, "list.dragged arg:%zu before:%zu", d->dragItem, index); | ||
361 | } | ||
362 | } | ||
363 | invalidateItem_ListWidget(d, d->dragItem); | ||
364 | d->dragItem = iInvalidPos; | ||
365 | return iTrue; | ||
312 | } | 366 | } |
313 | 367 | ||
314 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | 368 | static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { |
@@ -333,10 +387,35 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
333 | d->noHoverWhileScrolling = iFalse; | 387 | d->noHoverWhileScrolling = iFalse; |
334 | } | 388 | } |
335 | if (ev->type == SDL_MOUSEMOTION) { | 389 | if (ev->type == SDL_MOUSEMOTION) { |
336 | if (ev->motion.which != SDL_TOUCH_MOUSEID) { | 390 | const iInt2 mousePos = init_I2(ev->motion.x, ev->motion.y); |
337 | d->noHoverWhileScrolling = iFalse; | 391 | if (ev->motion.state == 0 /* not dragging */) { |
392 | if (ev->motion.which != SDL_TOUCH_MOUSEID) { | ||
393 | d->noHoverWhileScrolling = iFalse; | ||
394 | } | ||
395 | updateHover_ListWidget_(d, mousePos); | ||
396 | } | ||
397 | else if (d->dragItem != iInvalidPos) { | ||
398 | /* Start scrolling if near the ends. */ | ||
399 | const int zone = 2 * d->itemHeight; | ||
400 | const iRect bounds = bounds_Widget(w); | ||
401 | float scrollSpeed = 0.0f; | ||
402 | if (mousePos.y > bottom_Rect(bounds) - zone) { | ||
403 | scrollSpeed = (mousePos.y - bottom_Rect(bounds) + zone) / (float) zone; | ||
404 | } | ||
405 | else if (mousePos.y < top_Rect(bounds) + zone) { | ||
406 | scrollSpeed = -(top_Rect(bounds) + zone - mousePos.y) / (float) zone; | ||
407 | } | ||
408 | scrollSpeed = iClamp(scrollSpeed, -1.0f, 1.0f); | ||
409 | if (iAbs(scrollSpeed) < 0.001f) { | ||
410 | stop_Anim(&d->scrollY.pos); | ||
411 | refresh_Widget(d); | ||
412 | } | ||
413 | else { | ||
414 | setValueSpeed_Anim(&d->scrollY.pos, scrollSpeed < 0 ? 0 : scrollMax_ListWidget_(d), | ||
415 | scrollSpeed * scrollSpeed * gap_UI * 400); | ||
416 | refreshWhileScrolling_ListWidget_(d); | ||
417 | } | ||
338 | } | 418 | } |
339 | updateHover_ListWidget_(d, init_I2(ev->motion.x, ev->motion.y)); | ||
340 | } | 419 | } |
341 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { | 420 | if (ev->type == SDL_MOUSEWHEEL && isHover_Widget(w)) { |
342 | int amount = -ev->wheel.y; | 421 | int amount = -ev->wheel.y; |
@@ -359,12 +438,33 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
359 | redrawHoverItem_ListWidget_(d); | 438 | redrawHoverItem_ListWidget_(d); |
360 | return iTrue; | 439 | return iTrue; |
361 | case aborted_ClickResult: | 440 | case aborted_ClickResult: |
441 | // endDrag_ListWidget_(d, pos_Click(&d->click)); | ||
442 | if (d->dragItem != iInvalidPos) { | ||
443 | stop_Anim(&d->scrollY.pos); | ||
444 | invalidateItem_ListWidget(d, d->dragItem); | ||
445 | d->dragItem = iInvalidPos; | ||
446 | } | ||
362 | redrawHoverItem_ListWidget_(d); | 447 | redrawHoverItem_ListWidget_(d); |
363 | break; | 448 | break; |
449 | case drag_ClickResult: | ||
450 | if (d->dragItem == iInvalidPos && length_I2(delta_Click(&d->click)) > gap_UI) { | ||
451 | const size_t over = itemIndex_ListWidget(d, d->click.startPos); | ||
452 | if (over != iInvalidPos && | ||
453 | ((const iListItem *) item_ListWidget(d, over))->isDraggable) { | ||
454 | d->dragItem = over; | ||
455 | d->dragOrigin = sub_I2(topLeft_Rect(itemRect_ListWidget(d, over)), | ||
456 | d->click.startPos); | ||
457 | invalidateItem_ListWidget(d, d->dragItem); | ||
458 | } | ||
459 | } | ||
460 | return d->dragItem != iInvalidPos; | ||
364 | case finished_ClickResult: | 461 | case finished_ClickResult: |
462 | if (endDrag_ListWidget_(d, pos_Click(&d->click))) { | ||
463 | return iTrue; | ||
464 | } | ||
365 | redrawHoverItem_ListWidget_(d); | 465 | redrawHoverItem_ListWidget_(d); |
366 | if (contains_Rect(innerBounds_Widget(w), pos_Click(&d->click)) && | 466 | if (contains_Rect(itemRect_ListWidget(d, d->hoverItem), pos_Click(&d->click)) && |
367 | d->hoverItem != iInvalidSize) { | 467 | d->hoverItem != iInvalidPos) { |
368 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", | 468 | postCommand_Widget(w, "list.clicked arg:%zu item:%p", |
369 | d->hoverItem, constHoverItem_ListWidget(d)); | 469 | d->hoverItem, constHoverItem_ListWidget(d)); |
370 | } | 470 | } |
@@ -391,6 +491,7 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
391 | const int scrollY = pos_SmoothScroll(&d->scrollY); | 491 | const int scrollY = pos_SmoothScroll(&d->scrollY); |
392 | iPaint p; | 492 | iPaint p; |
393 | init_Paint(&p); | 493 | init_Paint(&p); |
494 | drawLayerEffects_Widget(w); | ||
394 | drawBackground_Widget(w); | 495 | drawBackground_Widget(w); |
395 | alloc_VisBuf(d->visBuf, bounds.size, d->itemHeight); | 496 | alloc_VisBuf(d->visBuf, bounds.size, d->itemHeight); |
396 | /* Update invalid regions/items. */ { | 497 | /* Update invalid regions/items. */ { |
@@ -433,7 +534,9 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
433 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 534 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
434 | beginTarget_Paint(&p, buf->texture); | 535 | beginTarget_Paint(&p, buf->texture); |
435 | fillRect_Paint(&p, itemRect, bg[i]); | 536 | fillRect_Paint(&p, itemRect, bg[i]); |
436 | class_ListItem(item)->draw(item, &p, itemRect, d); | 537 | if (index != d->dragItem) { |
538 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
539 | } | ||
437 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); | 540 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); |
438 | } | 541 | } |
439 | } | 542 | } |
@@ -447,7 +550,9 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
447 | const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin), | 550 | const iRect itemRect = { init_I2(0, j * d->itemHeight - buf->origin), |
448 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 551 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
449 | fillRect_Paint(&p, itemRect, bg[i]); | 552 | fillRect_Paint(&p, itemRect, bg[i]); |
450 | class_ListItem(item)->draw(item, &p, itemRect, d); | 553 | if (j != d->dragItem) { |
554 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
555 | } | ||
451 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); | 556 | fillRect_Paint(&p, moved_Rect(sbBlankRect, init_I2(0, top_Rect(itemRect))), bg[i]); |
452 | } | 557 | } |
453 | } | 558 | } |
@@ -458,6 +563,32 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
458 | } | 563 | } |
459 | setClip_Paint(&p, bounds_Widget(w)); | 564 | setClip_Paint(&p, bounds_Widget(w)); |
460 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); | 565 | draw_VisBuf(d->visBuf, addY_I2(topLeft_Rect(bounds), -scrollY), ySpan_Rect(bounds)); |
566 | if (d->dragItem != iInvalidPos) { | ||
567 | const iInt2 mousePos = mouseCoord_Window(get_Window(), 0); | ||
568 | iInt2 pos = add_I2(mousePos, d->dragOrigin); | ||
569 | const iListItem *item = constAt_PtrArray(&d->items, d->dragItem); | ||
570 | const iRect itemRect = { init_I2(left_Rect(bounds), pos.y), init_I2(d->visBuf->texSize.x, d->itemHeight) }; | ||
571 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | ||
572 | iBool dstOnto; | ||
573 | const size_t dstIndex = resolveDragDestination_ListWidget_(d, mousePos, &dstOnto); | ||
574 | if (dstIndex != d->dragItem) { | ||
575 | const iRect dstRect = itemRect_ListWidget(d, dstIndex); | ||
576 | p.alpha = 0xff; | ||
577 | if (dstOnto) { | ||
578 | drawRectThickness_Paint(&p, dstRect, gap_UI / 2, uiTextAction_ColorId); | ||
579 | } | ||
580 | else if (dstIndex != d->dragItem + 1) { | ||
581 | fillRect_Paint(&p, (iRect){ addY_I2(dstRect.pos, -gap_UI / 4), | ||
582 | init_I2(width_Rect(dstRect), gap_UI / 2) }, | ||
583 | uiTextAction_ColorId); | ||
584 | } | ||
585 | } | ||
586 | p.alpha = 0x80; | ||
587 | setOpacity_Text(0.5f); | ||
588 | class_ListItem(item)->draw(item, &p, itemRect, d); | ||
589 | setOpacity_Text(1.0f); | ||
590 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | ||
591 | } | ||
461 | unsetClip_Paint(&p); | 592 | unsetClip_Paint(&p); |
462 | drawChildren_Widget(w); | 593 | drawChildren_Widget(w); |
463 | } | 594 | } |
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index 314c183a..dfa24c07 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h | |||
@@ -39,6 +39,8 @@ struct Impl_ListItem { | |||
39 | iObject object; | 39 | iObject object; |
40 | iBool isSeparator; | 40 | iBool isSeparator; |
41 | iBool isSelected; | 41 | iBool isSelected; |
42 | iBool isDraggable; | ||
43 | iBool isDropTarget; /* may drag-and-drop another item on this */ | ||
42 | }; | 44 | }; |
43 | 45 | ||
44 | iDeclareObjectConstruction(ListItem) | 46 | iDeclareObjectConstruction(ListItem) |
@@ -64,6 +66,7 @@ void scrollToItem_ListWidget (iListWidget *, size_t index); | |||
64 | void scrollOffset_ListWidget (iListWidget *, int offset); | 66 | void scrollOffset_ListWidget (iListWidget *, int offset); |
65 | void updateVisible_ListWidget (iListWidget *); | 67 | void updateVisible_ListWidget (iListWidget *); |
66 | void updateMouseHover_ListWidget (iListWidget *); | 68 | void updateMouseHover_ListWidget (iListWidget *); |
69 | void setHoverItem_ListWidget (iListWidget *, size_t index); | ||
67 | 70 | ||
68 | void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2)); | 71 | void sort_ListWidget (iListWidget *, int (*cmp)(const iListItem **item1, const iListItem **item2)); |
69 | 72 | ||
@@ -75,6 +78,7 @@ int visCount_ListWidget (const iListWidget *); | |||
75 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); | 78 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); |
76 | iRect itemRect_ListWidget (const iListWidget *, size_t index); | 79 | iRect itemRect_ListWidget (const iListWidget *, size_t index); |
77 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); | 80 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); |
81 | const iAnyObject * constDragItem_ListWidget (const iListWidget *); | ||
78 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); | 82 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); |
79 | size_t hoverItemIndex_ListWidget (const iListWidget *); | 83 | size_t hoverItemIndex_ListWidget (const iListWidget *); |
80 | 84 | ||
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index a0a507ca..ab649eee 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -171,6 +171,9 @@ static float scoreMatch_(const iRegExp *pattern, iRangecc text) { | |||
171 | } | 171 | } |
172 | 172 | ||
173 | static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) { | 173 | static float bookmarkRelevance_LookupJob_(const iLookupJob *d, const iBookmark *bm) { |
174 | if (isFolder_Bookmark(bm)) { | ||
175 | return 0.0f; | ||
176 | } | ||
174 | iUrl parts; | 177 | iUrl parts; |
175 | init_Url(&parts, &bm->url); | 178 | init_Url(&parts, &bm->url); |
176 | const float t = scoreMatch_(d->term, range_String(&bm->title)); | 179 | const float t = scoreMatch_(d->term, range_String(&bm->title)); |
@@ -388,7 +391,7 @@ void init_LookupWidget(iLookupWidget *d) { | |||
388 | init_Widget(w); | 391 | init_Widget(w); |
389 | setId_Widget(w, "lookup"); | 392 | setId_Widget(w, "lookup"); |
390 | setFlags_Widget(w, focusable_WidgetFlag, iTrue); | 393 | setFlags_Widget(w, focusable_WidgetFlag, iTrue); |
391 | #if defined (iPlatformAppleMobile) | 394 | #if defined (iPlatformMobile) |
392 | setFlags_Widget(w, unhittable_WidgetFlag, iTrue); | 395 | setFlags_Widget(w, unhittable_WidgetFlag, iTrue); |
393 | #endif | 396 | #endif |
394 | d->list = addChildFlags_Widget(w, iClob(new_ListWidget()), | 397 | d->list = addChildFlags_Widget(w, iClob(new_ListWidget()), |
@@ -747,12 +750,19 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | |||
747 | return iTrue; | 750 | return iTrue; |
748 | } | 751 | } |
749 | } | 752 | } |
750 | if (isVisible_Widget(w) && | 753 | /* Focus switching between URL bar and lookup results. */ |
751 | key == SDLK_DOWN && !mods && focus_Widget() == findWidget_App("url") && | 754 | if (isVisible_Widget(w)) { |
752 | numItems_ListWidget(d->list)) { | 755 | if (((key == SDLK_DOWN && !mods) || key == SDLK_TAB) && |
753 | setCursor_LookupWidget_(d, 1); /* item 0 is always the first heading */ | 756 | focus_Widget() == findWidget_App("url") && |
754 | setFocus_Widget(w); | 757 | numItems_ListWidget(d->list)) { |
755 | return iTrue; | 758 | setCursor_LookupWidget_(d, 1); /* item 0 is always the first heading */ |
759 | setFocus_Widget(w); | ||
760 | return iTrue; | ||
761 | } | ||
762 | else if (key == SDLK_TAB && isFocused_Widget(w)) { | ||
763 | setFocus_Widget(findWidget_App("url")); | ||
764 | return iTrue; | ||
765 | } | ||
756 | } | 766 | } |
757 | } | 767 | } |
758 | return processEvent_Widget(w, ev); | 768 | return processEvent_Widget(w, ev); |
diff --git a/src/ui/metrics.c b/src/ui/metrics.c index 32561ed7..53a52afb 100644 --- a/src/ui/metrics.c +++ b/src/ui/metrics.c | |||
@@ -33,7 +33,7 @@ iInt2 gap2_UI = { defaultGap_Metrics, defaultGap_Metrics }; | |||
33 | int fontSize_UI = defaultFontSize_Metrics; | 33 | int fontSize_UI = defaultFontSize_Metrics; |
34 | 34 | ||
35 | void setScale_Metrics(float scale) { | 35 | void setScale_Metrics(float scale) { |
36 | #if defined (iPlatformAppleMobile) | 36 | #if defined (iPlatformMobile) |
37 | /* iPad needs a bit larger UI elements as the viewing distance is generally longer.*/ | 37 | /* iPad needs a bit larger UI elements as the viewing distance is generally longer.*/ |
38 | if (deviceType_App() == tablet_AppDeviceType) { | 38 | if (deviceType_App() == tablet_AppDeviceType) { |
39 | scale *= 1.1f; | 39 | scale *= 1.1f; |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 0ff3fe85..3cb6e631 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -36,7 +36,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
36 | # include "ios.h" | 36 | # include "ios.h" |
37 | #endif | 37 | #endif |
38 | 38 | ||
39 | static iBool useMobileSheetLayout_(void) { | 39 | iBool isUsingPanelLayout_Mobile(void) { |
40 | return deviceType_App() != desktop_AppDeviceType; | 40 | return deviceType_App() != desktop_AppDeviceType; |
41 | } | 41 | } |
42 | 42 | ||
@@ -57,11 +57,12 @@ static enum iFontId labelBoldFont_(void) { | |||
57 | 57 | ||
58 | static void updatePanelSheetMetrics_(iWidget *sheet) { | 58 | static void updatePanelSheetMetrics_(iWidget *sheet) { |
59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | 59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); |
60 | iWidget *naviPad = child_Widget(navi, 0); | ||
61 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; | 60 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; |
61 | #if defined (iPlatformMobile) | ||
62 | float left = 0.0f, right = 0.0f, top = 0.0f, bottom = 0.0f; | ||
62 | #if defined (iPlatformAppleMobile) | 63 | #if defined (iPlatformAppleMobile) |
63 | float left, right, top, bottom; | ||
64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
65 | #endif | ||
65 | setPadding_Widget(sheet, left, 0, right, 0); | 66 | setPadding_Widget(sheet, left, 0, right, 0); |
66 | navi->rect.pos = init_I2(left, top); | 67 | navi->rect.pos = init_I2(left, top); |
67 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | 68 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { |
@@ -87,17 +88,28 @@ static void unselectAllPanelButtons_(iWidget *topPanel) { | |||
87 | } | 88 | } |
88 | } | 89 | } |
89 | 90 | ||
91 | static iWidget *findTitleLabel_(iWidget *panel) { | ||
92 | iForEach(ObjectList, i, children_Widget(panel)) { | ||
93 | iWidget *child = i.object; | ||
94 | if (flags_Widget(child) & collapse_WidgetFlag && | ||
95 | isInstance_Object(child, &Class_LabelWidget)) { | ||
96 | return child; | ||
97 | } | ||
98 | } | ||
99 | return NULL; | ||
100 | } | ||
101 | |||
90 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { | 102 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { |
91 | if (equal_Command(cmd, "window.resized")) { | 103 | if (equal_Command(cmd, "window.resized")) { |
92 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 104 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
93 | const iRect safeRoot = safeRect_Root(mainDetailSplit->root); | 105 | const iRect safeRoot = safeRect_Root(mainDetailSplit->root); |
94 | setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); | ||
95 | setFixedSize_Widget(mainDetailSplit, safeRoot.size); | ||
96 | iWidget * sheet = parent_Widget(mainDetailSplit); | 106 | iWidget * sheet = parent_Widget(mainDetailSplit); |
97 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); | 107 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); |
98 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); | 108 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); |
99 | const size_t numPanels = childCount_Widget(detailStack); | 109 | const size_t numPanels = childCount_Widget(detailStack); |
100 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; | 110 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; |
111 | setPos_Widget(mainDetailSplit, topLeft_Rect(safeRoot)); | ||
112 | setFixedSize_Widget(mainDetailSplit, safeRoot.size); | ||
101 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); | 113 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); |
102 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); | 114 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); |
103 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); | 115 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); |
@@ -107,7 +119,7 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
107 | iAssert(topPanel); | 119 | iAssert(topPanel); |
108 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? | 120 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? |
109 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); | 121 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); |
110 | } | 122 | } |
111 | if (deviceType_App() == tablet_AppDeviceType) { | 123 | if (deviceType_App() == tablet_AppDeviceType) { |
112 | setPadding_Widget(topPanel, pad, 0, pad, pad); | 124 | setPadding_Widget(topPanel, pad, 0, pad, pad); |
113 | if (numPanels == 0) { | 125 | if (numPanels == 0) { |
@@ -118,8 +130,15 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
118 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); | 130 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); |
119 | } | 131 | } |
120 | } | 132 | } |
133 | iWidget *detailTitle = findChild_Widget(navi, "detailtitle"); { | ||
134 | setPos_Widget(detailTitle, init_I2(width_Widget(topPanel), 0)); | ||
135 | setFixedSize_Widget(detailTitle, | ||
136 | init_I2(width_Widget(detailStack), height_Widget(navi))); | ||
137 | setFlags_Widget(detailTitle, hidden_WidgetFlag, !isSideBySide); | ||
138 | } | ||
121 | iForEach(ObjectList, i, children_Widget(detailStack)) { | 139 | iForEach(ObjectList, i, children_Widget(detailStack)) { |
122 | iWidget *panel = i.object; | 140 | iWidget *panel = i.object; |
141 | setFlags_Widget(findTitleLabel_(panel), hidden_WidgetFlag, isSideBySide); | ||
123 | setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide); | 142 | setFlags_Widget(panel, leftEdgeDraggable_WidgetFlag, !isSideBySide); |
124 | if (isSideBySide) { | 143 | if (isSideBySide) { |
125 | setVisualOffset_Widget(panel, 0, 0, 0); | 144 | setVisualOffset_Widget(panel, 0, 0, 0); |
@@ -128,9 +147,27 @@ static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) | |||
128 | } | 147 | } |
129 | arrange_Widget(mainDetailSplit); | 148 | arrange_Widget(mainDetailSplit); |
130 | } | 149 | } |
150 | else if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd)) { | ||
151 | if (focus_Widget() && class_Widget(focus_Widget()) == &Class_InputWidget) { | ||
152 | setFocus_Widget(NULL); | ||
153 | return iTrue; | ||
154 | } | ||
155 | } | ||
131 | return iFalse; | 156 | return iFalse; |
132 | } | 157 | } |
133 | 158 | ||
159 | size_t currentPanelIndex_Mobile(const iWidget *panels) { | ||
160 | size_t index = 0; | ||
161 | iConstForEach(ObjectList, i, children_Widget(findChild_Widget(panels, "detailstack"))) { | ||
162 | const iWidget *child = i.object; | ||
163 | if (isVisible_Widget(child)) { | ||
164 | return index; | ||
165 | } | ||
166 | index++; | ||
167 | } | ||
168 | return iInvalidPos; | ||
169 | } | ||
170 | |||
134 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | 171 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { |
135 | const iBool isPortrait = !isSideBySideLayout_(); | 172 | const iBool isPortrait = !isSideBySideLayout_(); |
136 | if (equal_Command(cmd, "panel.open")) { | 173 | if (equal_Command(cmd, "panel.open")) { |
@@ -147,6 +184,12 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
147 | setupSheetTransition_Mobile(panel, iTrue); | 184 | setupSheetTransition_Mobile(panel, iTrue); |
148 | } | 185 | } |
149 | } | 186 | } |
187 | iLabelWidget *detailTitle = | ||
188 | findChild_Widget(parent_Widget(parent_Widget(topPanel)), "detailtitle"); | ||
189 | // setFlags_Widget(as_Widget(detailTitle), hidden_WidgetFlag, !isSideBySideLayout_()); | ||
190 | setFont_LabelWidget(detailTitle, uiLabelLargeBold_FontId); | ||
191 | setTextColor_LabelWidget(detailTitle, uiHeading_ColorId); | ||
192 | setText_LabelWidget(detailTitle, text_LabelWidget((iLabelWidget *) findTitleLabel_(panel))); | ||
150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | 193 | setFlags_Widget(button, selected_WidgetFlag, iTrue); |
151 | return iTrue; | 194 | return iTrue; |
152 | } | 195 | } |
@@ -171,7 +214,22 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
171 | } | 214 | } |
172 | unselectAllPanelButtons_(topPanel); | 215 | unselectAllPanelButtons_(topPanel); |
173 | if (!wasClosed) { | 216 | if (!wasClosed) { |
174 | postCommand_App("prefs.dismiss"); | 217 | /* TODO: Should come up with a more general-purpose approach here. */ |
218 | if (findWidget_App("prefs")) { | ||
219 | postCommand_App("prefs.dismiss"); | ||
220 | } | ||
221 | else if (findWidget_App("upload")) { | ||
222 | postCommand_App("upload.cancel"); | ||
223 | } | ||
224 | else if (findWidget_App("ident")) { | ||
225 | postCommand_Widget(topPanel, "ident.cancel"); | ||
226 | } | ||
227 | else if (findWidget_App("xlt")) { | ||
228 | postCommand_Widget(topPanel, "translation.cancel"); | ||
229 | } | ||
230 | else { | ||
231 | postCommand_Widget(topPanel, "cancel"); | ||
232 | } | ||
175 | } | 233 | } |
176 | return iTrue; | 234 | return iTrue; |
177 | } | 235 | } |
@@ -186,6 +244,7 @@ static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | |||
186 | return iFalse; | 244 | return iFalse; |
187 | } | 245 | } |
188 | 246 | ||
247 | #if 0 | ||
189 | static iBool isTwoColumnPage_(iWidget *d) { | 248 | static iBool isTwoColumnPage_(iWidget *d) { |
190 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | 249 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || |
191 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | 250 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { |
@@ -273,12 +332,13 @@ static void stripTrailingColon_(iLabelWidget *label) { | |||
273 | delete_String(mod); | 332 | delete_String(mod); |
274 | } | 333 | } |
275 | } | 334 | } |
335 | #endif | ||
276 | 336 | ||
277 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | 337 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { |
278 | iLabelWidget *btn = new_LabelWidget(text, command); | 338 | iLabelWidget *btn = new_LabelWidget(text, command); |
279 | setFlags_Widget(as_Widget(btn), | 339 | setFlags_Widget(as_Widget(btn), |
280 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | 340 | borderTop_WidgetFlag | borderBottom_WidgetFlag | alignLeft_WidgetFlag | |
281 | frameless_WidgetFlag | extraPadding_WidgetFlag, | 341 | frameless_WidgetFlag | extraPadding_WidgetFlag, |
282 | iTrue); | 342 | iTrue); |
283 | checkIcon_LabelWidget(btn); | 343 | checkIcon_LabelWidget(btn); |
284 | setFont_LabelWidget(btn, labelFont_()); | 344 | setFont_LabelWidget(btn, labelFont_()); |
@@ -298,11 +358,9 @@ static iWidget *makeValuePadding_(iWidget *value) { | |||
298 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | 358 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); |
299 | addChild_Widget(pad, iClob(value)); | 359 | addChild_Widget(pad, iClob(value)); |
300 | setFlags_Widget(pad, | 360 | setFlags_Widget(pad, |
301 | borderBottom_WidgetFlag | | 361 | borderTop_WidgetFlag | borderBottom_WidgetFlag | arrangeVertical_WidgetFlag | |
302 | arrangeVertical_WidgetFlag | | 362 | resizeToParentWidth_WidgetFlag | resizeWidthOfChildren_WidgetFlag | |
303 | resizeToParentWidth_WidgetFlag | | 363 | arrangeHeight_WidgetFlag, |
304 | resizeWidthOfChildren_WidgetFlag | | ||
305 | arrangeHeight_WidgetFlag, | ||
306 | iTrue); | 364 | iTrue); |
307 | return pad; | 365 | return pad; |
308 | } | 366 | } |
@@ -311,7 +369,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
311 | const iBool isInput = isInstance_Object(value, &Class_InputWidget); | 369 | const iBool isInput = isInstance_Object(value, &Class_InputWidget); |
312 | iWidget *div = new_Widget(); | 370 | iWidget *div = new_Widget(); |
313 | setFlags_Widget(div, | 371 | setFlags_Widget(div, |
314 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | 372 | borderTop_WidgetFlag | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | |
315 | resizeWidthOfChildren_WidgetFlag | | 373 | resizeWidthOfChildren_WidgetFlag | |
316 | arrangeHorizontal_WidgetFlag, iTrue); | 374 | arrangeHorizontal_WidgetFlag, iTrue); |
317 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | 375 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); |
@@ -321,7 +379,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
321 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | 379 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); |
322 | setFont_LabelWidget(heading, labelFont_()); | 380 | setFont_LabelWidget(heading, labelFont_()); |
323 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | 381 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); |
324 | if (isInput) { | 382 | if (isInput && ~value->flags & fixedWidth_WidgetFlag) { |
325 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | 383 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); |
326 | } | 384 | } |
327 | else if (isInstance_Object(value, &Class_LabelWidget) && | 385 | else if (isInstance_Object(value, &Class_LabelWidget) && |
@@ -337,7 +395,7 @@ static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *val | |||
337 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | 395 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); |
338 | addChild_Widget(div, iClob(value)); | 396 | addChild_Widget(div, iClob(value)); |
339 | } | 397 | } |
340 | printTree_Widget(div); | 398 | // printTree_Widget(div); |
341 | return div; | 399 | return div; |
342 | } | 400 | } |
343 | 401 | ||
@@ -347,6 +405,7 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | |||
347 | setId_Widget(panel, "panel"); | 405 | setId_Widget(panel, "panel"); |
348 | setUserData_Object(panelButton, panel); | 406 | setUserData_Object(panelButton, panel); |
349 | setBackgroundColor_Widget(panel, uiBackground_ColorId); | 407 | setBackgroundColor_Widget(panel, uiBackground_ColorId); |
408 | setDrawBufferEnabled_Widget(panel, iTrue); | ||
350 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); | 409 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); |
351 | if (titleText) { | 410 | if (titleText) { |
352 | iLabelWidget *title = | 411 | iLabelWidget *title = |
@@ -366,7 +425,396 @@ static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | |||
366 | return panel; | 425 | return panel; |
367 | } | 426 | } |
368 | 427 | ||
369 | void finalizeSheet_Mobile(iWidget *sheet) { | 428 | //void finalizeSheet_Mobile(iWidget *sheet) { |
429 | // arrange_Widget(sheet); | ||
430 | // postRefresh_App(); | ||
431 | //} | ||
432 | |||
433 | static size_t countItems_(const iMenuItem *itemsNullTerminated) { | ||
434 | size_t num = 0; | ||
435 | for (; itemsNullTerminated->label; num++, itemsNullTerminated++) {} | ||
436 | return num; | ||
437 | } | ||
438 | |||
439 | static iBool dropdownHeadingHandler_(iWidget *d, const char *cmd) { | ||
440 | if (isVisible_Widget(d) && | ||
441 | equal_Command(cmd, "mouse.clicked") && contains_Widget(d, coord_Command(cmd)) && | ||
442 | arg_Command(cmd)) { | ||
443 | postCommand_Widget(userData_Object(d), | ||
444 | cstr_String(command_LabelWidget(userData_Object(d)))); | ||
445 | return iTrue; | ||
446 | } | ||
447 | return iFalse; | ||
448 | } | ||
449 | |||
450 | static iBool inputHeadingHandler_(iWidget *d, const char *cmd) { | ||
451 | if (isVisible_Widget(d) && | ||
452 | equal_Command(cmd, "mouse.clicked") && contains_Widget(d, coord_Command(cmd)) && | ||
453 | arg_Command(cmd)) { | ||
454 | setFocus_Widget(userData_Object(d)); | ||
455 | return iTrue; | ||
456 | } | ||
457 | return iFalse; | ||
458 | } | ||
459 | |||
460 | void makePanelItem_Mobile(iWidget *panel, const iMenuItem *item) { | ||
461 | iWidget * widget = NULL; | ||
462 | iLabelWidget *heading = NULL; | ||
463 | iWidget * value = NULL; | ||
464 | const char * spec = item->label; | ||
465 | const char * id = cstr_Rangecc(range_Command(spec, "id")); | ||
466 | const char * label = hasLabel_Command(spec, "text") | ||
467 | ? suffixPtr_Command(spec, "text") | ||
468 | : format_CStr("${%s}", id); | ||
469 | if (hasLabel_Command(spec, "device") && deviceType_App() != argLabel_Command(spec, "device")) { | ||
470 | return; | ||
471 | } | ||
472 | if (equal_Command(spec, "title")) { | ||
473 | iLabelWidget *title = addChildFlags_Widget(panel, | ||
474 | iClob(new_LabelWidget(label, NULL)), | ||
475 | alignLeft_WidgetFlag | frameless_WidgetFlag | | ||
476 | collapse_WidgetFlag); | ||
477 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
478 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
479 | setAllCaps_LabelWidget(title, iTrue); | ||
480 | setId_Widget(as_Widget(title), id); | ||
481 | } | ||
482 | else if (equal_Command(spec, "heading")) { | ||
483 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
484 | heading = makeHeading_Widget(label); | ||
485 | setAllCaps_LabelWidget(heading, iTrue); | ||
486 | setRemoveTrailingColon_LabelWidget(heading, iTrue); | ||
487 | addChild_Widget(panel, iClob(heading)); | ||
488 | setId_Widget(as_Widget(heading), id); | ||
489 | } | ||
490 | else if (equal_Command(spec, "toggle")) { | ||
491 | iLabelWidget *toggle = (iLabelWidget *) makeToggle_Widget(id); | ||
492 | setFont_LabelWidget(toggle, labelFont_()); | ||
493 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), | ||
494 | as_Widget(toggle)); | ||
495 | } | ||
496 | else if (equal_Command(spec, "dropdown")) { | ||
497 | const iMenuItem *dropItems = item->data; | ||
498 | iLabelWidget *drop = makeMenuButton_LabelWidget(dropItems[0].label, | ||
499 | dropItems, countItems_(dropItems)); | ||
500 | value = as_Widget(drop); | ||
501 | setFont_LabelWidget(drop, labelFont_()); | ||
502 | setFlags_Widget(as_Widget(drop), | ||
503 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
504 | frameless_WidgetFlag, iTrue); | ||
505 | setId_Widget(as_Widget(drop), id); | ||
506 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), as_Widget(drop)); | ||
507 | setCommandHandler_Widget(widget, dropdownHeadingHandler_); | ||
508 | setUserData_Object(widget, drop); | ||
509 | } | ||
510 | else if (equal_Command(spec, "radio") || equal_Command(spec, "buttons")) { | ||
511 | const iBool isRadio = equal_Command(spec, "radio"); | ||
512 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
513 | iLabelWidget *head = makeHeading_Widget(label); | ||
514 | setAllCaps_LabelWidget(head, iTrue); | ||
515 | setRemoveTrailingColon_LabelWidget(head, iTrue); | ||
516 | addChild_Widget(panel, iClob(head)); | ||
517 | widget = new_Widget(); | ||
518 | setBackgroundColor_Widget(widget, uiBackgroundSidebar_ColorId); | ||
519 | setPadding_Widget(widget, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
520 | setFlags_Widget(widget, | ||
521 | borderTop_WidgetFlag | | ||
522 | borderBottom_WidgetFlag | | ||
523 | arrangeHorizontal_WidgetFlag | | ||
524 | arrangeHeight_WidgetFlag | | ||
525 | resizeToParentWidth_WidgetFlag | | ||
526 | resizeWidthOfChildren_WidgetFlag, | ||
527 | iTrue); | ||
528 | setId_Widget(widget, id); | ||
529 | for (const iMenuItem *radioItem = item->data; radioItem->label; radioItem++) { | ||
530 | const char * radId = cstr_Rangecc(range_Command(radioItem->label, "id")); | ||
531 | int64_t flags = noBackground_WidgetFlag; | ||
532 | iLabelWidget *button; | ||
533 | if (isRadio) { | ||
534 | const char *radLabel = | ||
535 | hasLabel_Command(radioItem->label, "label") | ||
536 | ? format_CStr("${%s}", | ||
537 | cstr_Rangecc(range_Command(radioItem->label, "label"))) | ||
538 | : suffixPtr_Command(radioItem->label, "text"); | ||
539 | button = new_LabelWidget(radLabel, radioItem->command); | ||
540 | flags |= radio_WidgetFlag; | ||
541 | } | ||
542 | else { | ||
543 | button = (iLabelWidget *) makeToggle_Widget(radId); | ||
544 | setTextCStr_LabelWidget(button, format_CStr("${%s}", radId)); | ||
545 | setFlags_Widget(as_Widget(button), fixedWidth_WidgetFlag, iFalse); | ||
546 | updateSize_LabelWidget(button); | ||
547 | } | ||
548 | setId_Widget(as_Widget(button), radId); | ||
549 | setFont_LabelWidget(button, defaultMedium_FontId); | ||
550 | addChildFlags_Widget(widget, iClob(button), flags); | ||
551 | } | ||
552 | } | ||
553 | else if (equal_Command(spec, "input")) { | ||
554 | iInputWidget *input = new_InputWidget(argU32Label_Command(spec, "maxlen")); | ||
555 | if (hasLabel_Command(spec, "hint")) { | ||
556 | setHint_InputWidget(input, cstr_Lang(cstr_Rangecc(range_Command(spec, "hint")))); | ||
557 | } | ||
558 | setId_Widget(as_Widget(input), id); | ||
559 | setUrlContent_InputWidget(input, argLabel_Command(spec, "url")); | ||
560 | setSelectAllOnFocus_InputWidget(input, argLabel_Command(spec, "selectall")); | ||
561 | setFont_InputWidget(input, labelFont_()); | ||
562 | if (argLabel_Command(spec, "noheading")) { | ||
563 | widget = makeValuePadding_(as_Widget(input)); | ||
564 | setFlags_Widget(widget, expand_WidgetFlag, iTrue); | ||
565 | } | ||
566 | else { | ||
567 | setContentPadding_InputWidget(input, 3 * gap_UI, 0); | ||
568 | if (hasLabel_Command(spec, "unit")) { | ||
569 | iWidget *unit = addChildFlags_Widget( | ||
570 | as_Widget(input), | ||
571 | iClob(new_LabelWidget( | ||
572 | format_CStr("${%s}", cstr_Rangecc(range_Command(spec, "unit"))), NULL)), | ||
573 | frameless_WidgetFlag | moveToParentRightEdge_WidgetFlag | | ||
574 | resizeToParentHeight_WidgetFlag); | ||
575 | setContentPadding_InputWidget(input, -1, width_Widget(unit) - 4 * gap_UI); | ||
576 | } | ||
577 | widget = makeValuePaddingWithHeading_(heading = makeHeading_Widget(label), | ||
578 | as_Widget(input)); | ||
579 | setCommandHandler_Widget(widget, inputHeadingHandler_); | ||
580 | setUserData_Object(widget, input); | ||
581 | } | ||
582 | } | ||
583 | else if (equal_Command(spec, "button")) { | ||
584 | widget = as_Widget(heading = makePanelButton_(label, item->command)); | ||
585 | setFlags_Widget(widget, selected_WidgetFlag, argLabel_Command(spec, "selected") != 0); | ||
586 | } | ||
587 | else if (equal_Command(spec, "label")) { | ||
588 | iLabelWidget *lab = new_LabelWidget(label, NULL); | ||
589 | widget = as_Widget(lab); | ||
590 | setId_Widget(widget, id); | ||
591 | setWrap_LabelWidget(lab, !argLabel_Command(spec, "nowrap")); | ||
592 | setFlags_Widget(widget, | ||
593 | fixedHeight_WidgetFlag | | ||
594 | (!argLabel_Command(spec, "frame") ? frameless_WidgetFlag : 0), | ||
595 | iTrue); | ||
596 | } | ||
597 | else if (equal_Command(spec, "padding")) { | ||
598 | float height = 1.5f; | ||
599 | if (hasLabel_Command(spec, "arg")) { | ||
600 | height *= argfLabel_Command(spec, "arg"); | ||
601 | } | ||
602 | widget = makePadding_Widget(lineHeight_Text(labelFont_()) * height); | ||
603 | } | ||
604 | /* Apply common styling to the heading. */ | ||
605 | if (heading) { | ||
606 | setRemoveTrailingColon_LabelWidget(heading, iTrue); | ||
607 | const iChar icon = toInt_String(string_Command(item->label, "icon")); | ||
608 | if (icon) { | ||
609 | setIcon_LabelWidget(heading, icon); | ||
610 | } | ||
611 | if (value && as_Widget(heading) != value) { | ||
612 | as_Widget(heading)->sizeRef = value; /* heading height matches value widget */ | ||
613 | } | ||
614 | } | ||
615 | if (widget) { | ||
616 | setFlags_Widget(widget, | ||
617 | collapse_WidgetFlag | hidden_WidgetFlag, | ||
618 | argLabel_Command(spec, "collapse") != 0); | ||
619 | addChild_Widget(panel, iClob(widget)); | ||
620 | } | ||
621 | } | ||
622 | |||
623 | void makePanelItems_Mobile(iWidget *panel, const iMenuItem *itemsNullTerminated) { | ||
624 | for (const iMenuItem *item = itemsNullTerminated; item->label; item++) { | ||
625 | makePanelItem_Mobile(panel, item); | ||
626 | } | ||
627 | } | ||
628 | |||
629 | static const iMenuItem *findDialogCancelAction_(const iMenuItem *items, size_t n) { | ||
630 | if (n <= 1) { | ||
631 | return NULL; | ||
632 | } | ||
633 | for (size_t i = 0; i < n; i++) { | ||
634 | if (!iCmpStr(items[i].label, "${cancel}") || !iCmpStr(items[i].label, "${close}")) { | ||
635 | return &items[i]; | ||
636 | } | ||
637 | } | ||
638 | return NULL; | ||
639 | } | ||
640 | |||
641 | iWidget *makePanels_Mobile(const char *id, | ||
642 | const iMenuItem *itemsNullTerminated, | ||
643 | const iMenuItem *actions, size_t numActions) { | ||
644 | return makePanelsParent_Mobile(get_Root()->widget, id, itemsNullTerminated, actions, numActions); | ||
645 | } | ||
646 | |||
647 | iWidget *makePanelsParent_Mobile(iWidget *parentWidget, | ||
648 | const char *id, | ||
649 | const iMenuItem *itemsNullTerminated, | ||
650 | const iMenuItem *actions, size_t numActions) { | ||
651 | iWidget *panels = new_Widget(); | ||
652 | setId_Widget(panels, id); | ||
653 | initPanels_Mobile(panels, parentWidget, itemsNullTerminated, actions, numActions); | ||
654 | return panels; | ||
655 | } | ||
656 | |||
657 | void initPanels_Mobile(iWidget *panels, iWidget *parentWidget, | ||
658 | const iMenuItem *itemsNullTerminated, | ||
659 | const iMenuItem *actions, size_t numActions) { | ||
660 | /* A multipanel widget has a top panel and one or more detail panels. In a horizontal layout, | ||
661 | the detail panels slide in from the right and cover the top panel. In a landscape layout, | ||
662 | the detail panels are always visible on the side. */ | ||
663 | setBackgroundColor_Widget(panels, uiBackground_ColorId); | ||
664 | setFlags_Widget(panels, | ||
665 | resizeToParentWidth_WidgetFlag | resizeToParentHeight_WidgetFlag | | ||
666 | frameless_WidgetFlag | focusRoot_WidgetFlag | commandOnClick_WidgetFlag | | ||
667 | /*overflowScrollable_WidgetFlag |*/ leftEdgeDraggable_WidgetFlag, | ||
668 | iTrue); | ||
669 | setFlags_Widget(panels, overflowScrollable_WidgetFlag, iFalse); | ||
670 | /* The top-level split between main and detail panels. */ | ||
671 | iWidget *mainDetailSplit = makeHDiv_Widget(); { | ||
672 | setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); | ||
673 | setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
674 | setId_Widget(mainDetailSplit, "mdsplit"); | ||
675 | addChild_Widget(panels, iClob(mainDetailSplit)); | ||
676 | } | ||
677 | /* The panel roots. */ | ||
678 | iWidget *topPanel = new_Widget(); { | ||
679 | setId_Widget(topPanel, "panel.top"); | ||
680 | setDrawBufferEnabled_Widget(topPanel, iTrue); | ||
681 | setCommandHandler_Widget(topPanel, topPanelHandler_); | ||
682 | setFlags_Widget(topPanel, | ||
683 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
684 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
685 | commandOnClick_WidgetFlag, | ||
686 | iTrue); | ||
687 | addChild_Widget(mainDetailSplit, iClob(topPanel)); | ||
688 | setId_Widget(addChild_Widget(topPanel, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
689 | } | ||
690 | iWidget *detailStack = new_Widget(); { | ||
691 | setId_Widget(detailStack, "detailstack"); | ||
692 | setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | ||
693 | addChild_Widget(mainDetailSplit, iClob(detailStack)); | ||
694 | } | ||
695 | /* Slide top panel with detail panels. */ { | ||
696 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); | ||
697 | topPanel->offsetRef = detailStack; | ||
698 | } | ||
699 | /* Navigation bar at the top. */ | ||
700 | iLabelWidget *naviBack; | ||
701 | iWidget *navi = new_Widget(); { | ||
702 | setId_Widget(navi, "panel.navi"); | ||
703 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
704 | setId_Widget(addChildFlags_Widget(navi, | ||
705 | iClob(new_LabelWidget("", NULL)), | ||
706 | alignLeft_WidgetFlag | fixedPosition_WidgetFlag | | ||
707 | fixedSize_WidgetFlag | hidden_WidgetFlag | | ||
708 | frameless_WidgetFlag), | ||
709 | "detailtitle"); | ||
710 | naviBack = addChildFlags_Widget( | ||
711 | navi, | ||
712 | iClob(newKeyMods_LabelWidget( | ||
713 | leftAngle_Icon " ${panel.back}", SDLK_ESCAPE, 0, "panel.close")), | ||
714 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | ||
715 | extraPadding_WidgetFlag); | ||
716 | checkIcon_LabelWidget(naviBack); | ||
717 | setId_Widget(as_Widget(naviBack), "panel.back"); | ||
718 | setFont_LabelWidget(naviBack, labelFont_()); | ||
719 | addChildFlags_Widget(panels, iClob(navi), | ||
720 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
721 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
722 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
723 | } | ||
724 | iBool haveDetailPanels = iFalse; | ||
725 | /* Create panel contents based on provided items. */ | ||
726 | for (size_t i = 0; itemsNullTerminated[i].label; i++) { | ||
727 | const iMenuItem *item = &itemsNullTerminated[i]; | ||
728 | if (equal_Command(item->label, "panel")) { | ||
729 | haveDetailPanels = iTrue; | ||
730 | const char *id = cstr_Rangecc(range_Command(item->label, "id")); | ||
731 | const iString *label = hasLabel_Command(item->label, "text") | ||
732 | ? collect_String(suffix_Command(item->label, "text")) | ||
733 | : collectNewFormat_String("${%s}", id); | ||
734 | iLabelWidget * button = | ||
735 | addChildFlags_Widget(topPanel, | ||
736 | iClob(makePanelButton_(cstr_String(label), "panel.open")), | ||
737 | chevron_WidgetFlag | borderTop_WidgetFlag); | ||
738 | const iChar icon = toInt_String(string_Command(item->label, "icon")); | ||
739 | if (icon) { | ||
740 | setIcon_LabelWidget(button, icon); | ||
741 | } | ||
742 | iWidget *panel = addChildPanel_(detailStack, button, NULL); | ||
743 | makePanelItems_Mobile(panel, item->data); | ||
744 | } | ||
745 | else { | ||
746 | makePanelItem_Mobile(topPanel, item); | ||
747 | } | ||
748 | } | ||
749 | /* Actions. */ | ||
750 | if (numActions) { | ||
751 | /* Some actions go in the navigation bar and some go on the top panel. */ | ||
752 | const iMenuItem *cancelItem = findDialogCancelAction_(actions, numActions); | ||
753 | const iMenuItem *defaultItem = &actions[numActions - 1]; | ||
754 | iAssert(defaultItem); | ||
755 | if (defaultItem && !cancelItem) { | ||
756 | updateTextCStr_LabelWidget(naviBack, defaultItem->label); | ||
757 | setCommand_LabelWidget(naviBack, collectNewCStr_String(defaultItem->command)); | ||
758 | setFlags_Widget(as_Widget(naviBack), alignLeft_WidgetFlag, iFalse); | ||
759 | setFlags_Widget(as_Widget(naviBack), alignRight_WidgetFlag, iTrue); | ||
760 | setIcon_LabelWidget(naviBack, 0); | ||
761 | setFont_LabelWidget(naviBack, labelBoldFont_()); | ||
762 | } | ||
763 | else if (defaultItem && defaultItem != cancelItem) { | ||
764 | if (!haveDetailPanels) { | ||
765 | updateTextCStr_LabelWidget(naviBack, cancelItem->label); | ||
766 | setCommand_LabelWidget(naviBack, collectNewCStr_String(cancelItem->command | ||
767 | ? cancelItem->command | ||
768 | : "cancel")); | ||
769 | } | ||
770 | iLabelWidget *defaultButton = new_LabelWidget(defaultItem->label, defaultItem->command); | ||
771 | setFont_LabelWidget(defaultButton, labelBoldFont_()); | ||
772 | setFlags_Widget(as_Widget(defaultButton), | ||
773 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
774 | noBackground_WidgetFlag, | ||
775 | iTrue); | ||
776 | addChildFlags_Widget(as_Widget(naviBack), iClob(defaultButton), | ||
777 | moveToParentRightEdge_WidgetFlag); | ||
778 | updateSize_LabelWidget(defaultButton); | ||
779 | } | ||
780 | /* All other actions are added as buttons. */ | ||
781 | iBool needPadding = iTrue; | ||
782 | for (size_t i = 0; i < numActions; i++) { | ||
783 | const iMenuItem *act = &actions[i]; | ||
784 | if (act == cancelItem || act == defaultItem) { | ||
785 | continue; | ||
786 | } | ||
787 | const char *label = act->label; | ||
788 | if (*label == '*' || *label == '&') { | ||
789 | continue; /* Special value selection items for a Question dialog. */ | ||
790 | } | ||
791 | if (!iCmpStr(label, "---")) { | ||
792 | continue; /* Separator. */ | ||
793 | } | ||
794 | if (needPadding) { | ||
795 | makePanelItem_Mobile(topPanel, &(iMenuItem){ "padding" }); | ||
796 | needPadding = iFalse; | ||
797 | } | ||
798 | makePanelItem_Mobile( | ||
799 | topPanel, | ||
800 | &(iMenuItem){ format_CStr("button text:" uiTextAction_ColorEscape "%s", act->label), | ||
801 | 0, | ||
802 | 0, | ||
803 | act->command }); | ||
804 | } | ||
805 | } | ||
806 | /* Finalize the layout. */ | ||
807 | if (parentWidget) { | ||
808 | addChild_Widget(parentWidget, iClob(panels)); | ||
809 | } | ||
810 | mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ | ||
811 | updatePanelSheetMetrics_(panels); | ||
812 | arrange_Widget(panels); | ||
813 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
814 | // printTree_Widget(panels); | ||
815 | } | ||
816 | |||
817 | #if 0 | ||
370 | /* The sheet contents are completely rearranged and restyled on a phone. | 818 | /* The sheet contents are completely rearranged and restyled on a phone. |
371 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | 819 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already |
372 | scrollable so they can be taller than the display. In hindsight, it may have been | 820 | scrollable so they can be taller than the display. In hindsight, it may have been |
@@ -397,7 +845,7 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
397 | │ │ └┤ ││ │ │└┤ ││ | 845 | │ │ └┤ ││ │ │└┤ ││ |
398 | │ │ └───────────────────┘│ │ │ └──────┘ | 846 | │ │ └───────────────────┘│ │ │ └──────┘ |
399 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ | 847 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ |
400 | offscreen | 848 | underneath |
401 | */ | 849 | */ |
402 | /* Modify the top sheet to act as a fullscreen background. */ | 850 | /* Modify the top sheet to act as a fullscreen background. */ |
403 | setPadding1_Widget(sheet, 0); | 851 | setPadding1_Widget(sheet, 0); |
@@ -772,9 +1220,10 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
772 | } | 1220 | } |
773 | postRefresh_App(); | 1221 | postRefresh_App(); |
774 | } | 1222 | } |
1223 | #endif | ||
775 | 1224 | ||
776 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | 1225 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { |
777 | if (!useMobileSheetLayout_()) { | 1226 | if (!isUsingPanelLayout_Mobile()) { |
778 | return; | 1227 | return; |
779 | } | 1228 | } |
780 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; | 1229 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; |
@@ -794,8 +1243,10 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
794 | } | 1243 | } |
795 | } | 1244 | } |
796 | 1245 | ||
797 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | 1246 | void setupSheetTransition_Mobile(iWidget *sheet, int flags) { |
798 | if (!useMobileSheetLayout_()) { | 1247 | const iBool isIncoming = (flags & incoming_TransitionFlag) != 0; |
1248 | const int dir = flags & dirMask_TransitionFlag; | ||
1249 | if (!isUsingPanelLayout_Mobile()) { | ||
799 | if (prefs_App()->uiAnimations) { | 1250 | if (prefs_App()->uiAnimations) { |
800 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); | 1251 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); |
801 | if (isIncoming) { | 1252 | if (isIncoming) { |
@@ -808,17 +1259,51 @@ void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
808 | } | 1259 | } |
809 | return; | 1260 | return; |
810 | } | 1261 | } |
811 | if(isSideBySideLayout_()) { | 1262 | if (isSideBySideLayout_()) { |
1263 | /* TODO: Landscape transitions? */ | ||
812 | return; | 1264 | return; |
813 | } | 1265 | } |
814 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | 1266 | setFlags_Widget(sheet, |
1267 | horizontalOffset_WidgetFlag, | ||
1268 | dir == right_TransitionDir || dir == left_TransitionDir); | ||
815 | if (isIncoming) { | 1269 | if (isIncoming) { |
816 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | 1270 | switch (dir) { |
1271 | case right_TransitionDir: | ||
1272 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | ||
1273 | break; | ||
1274 | case left_TransitionDir: | ||
1275 | setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 0, 0); | ||
1276 | break; | ||
1277 | case top_TransitionDir: | ||
1278 | setVisualOffset_Widget( | ||
1279 | sheet, -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), 0, 0); | ||
1280 | break; | ||
1281 | case bottom_TransitionDir: | ||
1282 | setVisualOffset_Widget(sheet, height_Widget(sheet), 0, 0); | ||
1283 | break; | ||
1284 | } | ||
817 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); | 1285 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); |
818 | } | 1286 | } |
819 | else { | 1287 | else { |
820 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; | 1288 | switch (dir) { |
821 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | 1289 | case right_TransitionDir: { |
822 | wasDragged ? 0 : easeIn_AnimFlag); | 1290 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; |
1291 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | ||
1292 | wasDragged ? 0 : easeIn_AnimFlag); | ||
1293 | break; | ||
1294 | } | ||
1295 | case left_TransitionDir: | ||
1296 | setVisualOffset_Widget(sheet, -size_Root(sheet->root).x, 200, easeIn_AnimFlag); | ||
1297 | break; | ||
1298 | case top_TransitionDir: | ||
1299 | setVisualOffset_Widget(sheet, | ||
1300 | -bottom_Rect(boundsWithoutVisualOffset_Widget(sheet)), | ||
1301 | 200, | ||
1302 | easeIn_AnimFlag); | ||
1303 | break; | ||
1304 | case bottom_TransitionDir: | ||
1305 | setVisualOffset_Widget(sheet, height_Widget(sheet), 200, easeIn_AnimFlag); | ||
1306 | break; | ||
1307 | } | ||
823 | } | 1308 | } |
824 | } | 1309 | } |
diff --git a/src/ui/mobile.h b/src/ui/mobile.h index 44134389..9d7ac8e4 100644 --- a/src/ui/mobile.h +++ b/src/ui/mobile.h | |||
@@ -22,11 +22,36 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include <the_Foundation/defs.h> | 25 | #include <the_Foundation/rect.h> |
26 | 26 | ||
27 | iDeclareType(Widget) | 27 | iDeclareType(Widget) |
28 | 28 | iDeclareType(MenuItem) | |
29 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | 29 | |
30 | void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); | 30 | iBool isUsingPanelLayout_Mobile (void); |
31 | 31 | iWidget * makePanels_Mobile (const char *id, | |
32 | void finalizeSheet_Mobile (iWidget *sheet); | 32 | const iMenuItem *itemsNullTerminated, |
33 | const iMenuItem *actions, size_t numActions); | ||
34 | iWidget * makePanelsParent_Mobile (iWidget *parent, | ||
35 | const char *id, | ||
36 | const iMenuItem *itemsNullTerminated, | ||
37 | const iMenuItem *actions, size_t numActions); | ||
38 | void initPanels_Mobile (iWidget *panels, iWidget *parentWidget, | ||
39 | const iMenuItem *itemsNullTerminated, | ||
40 | const iMenuItem *actions, size_t numActions); | ||
41 | |||
42 | size_t currentPanelIndex_Mobile (const iWidget *panels); | ||
43 | |||
44 | enum iTransitionFlags { | ||
45 | incoming_TransitionFlag = iBit(1), | ||
46 | dirMask_TransitionFlag = iBit(2) | iBit(3), | ||
47 | }; | ||
48 | |||
49 | enum iTransitionDir { | ||
50 | right_TransitionDir = 0, | ||
51 | bottom_TransitionDir = 2, | ||
52 | left_TransitionDir = 4, | ||
53 | top_TransitionDir = 6, | ||
54 | }; | ||
55 | |||
56 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | ||
57 | void setupSheetTransition_Mobile (iWidget *sheet, int flags); | ||
diff --git a/src/ui/paint.c b/src/ui/paint.c index 79adb7d1..5506f845 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c | |||
@@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | 24 | ||
25 | #include <SDL_version.h> | 25 | #include <SDL_version.h> |
26 | 26 | ||
27 | iInt2 origin_Paint; | ||
28 | |||
27 | iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { | 29 | iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { |
28 | iAssert(d->dst); | 30 | iAssert(d->dst); |
29 | return d->dst->render; | 31 | return d->dst->render; |
@@ -31,7 +33,8 @@ iLocalDef SDL_Renderer *renderer_Paint_(const iPaint *d) { | |||
31 | 33 | ||
32 | static void setColor_Paint_(const iPaint *d, int color) { | 34 | static void setColor_Paint_(const iPaint *d, int color) { |
33 | const iColor clr = get_Color(color & mask_ColorId); | 35 | const iColor clr = get_Color(color & mask_ColorId); |
34 | SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, clr.a * d->alpha / 255); | 36 | SDL_SetRenderDrawColor(renderer_Paint_(d), clr.r, clr.g, clr.b, |
37 | (color & opaque_ColorId ? 255 : clr.a) * d->alpha / 255); | ||
35 | } | 38 | } |
36 | 39 | ||
37 | void init_Paint(iPaint *d) { | 40 | void init_Paint(iPaint *d) { |
@@ -62,17 +65,40 @@ void endTarget_Paint(iPaint *d) { | |||
62 | } | 65 | } |
63 | 66 | ||
64 | void setClip_Paint(iPaint *d, iRect rect) { | 67 | void setClip_Paint(iPaint *d, iRect rect) { |
65 | rect = intersect_Rect(rect, rect_Root(get_Root())); | 68 | //rect = intersect_Rect(rect, rect_Root(get_Root())); |
69 | addv_I2(&rect.pos, origin_Paint); | ||
66 | if (isEmpty_Rect(rect)) { | 70 | if (isEmpty_Rect(rect)) { |
67 | rect = init_Rect(0, 0, 1, 1); | 71 | rect = init_Rect(0, 0, 1, 1); |
68 | } | 72 | } |
73 | // iRect root = rect_Root(get_Root()); | ||
74 | iRect targetRect = zero_Rect(); | ||
75 | SDL_Texture *target = SDL_GetRenderTarget(renderer_Paint_(d)); | ||
76 | if (target) { | ||
77 | SDL_QueryTexture(target, NULL, NULL, &targetRect.size.x, &targetRect.size.y); | ||
78 | rect = intersect_Rect(rect, targetRect); | ||
79 | } | ||
80 | else { | ||
81 | rect = intersect_Rect(rect, rect_Root(get_Root())); | ||
82 | } | ||
83 | |||
84 | /*if (rect.pos.x < 0) { | ||
85 | adjustEdges_Rect(&rect, 0, 0, 0, -rect.pos.x); | ||
86 | } | ||
87 | if (rect.pos.y < 0) { | ||
88 | adjustEdges_Rect(&rect, -rect.pos.y, 0, 0, 0); | ||
89 | } | ||
90 | if (right_Rect(rect) > right_Rect(root)) { | ||
91 | adjustEdges_Rect(&rect, 0, right_Rect(root) - right_Rect(rect), 0, 0); | ||
92 | } | ||
93 | if (bottom_Rect(rect) > bottom_Rect(root)) { | ||
94 | adjustEdges_Rect(&rect, 0, bottom_Rect(root) - bottom_Rect(rect), 0, 0); | ||
95 | }*/ | ||
69 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | 96 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); |
70 | } | 97 | } |
71 | 98 | ||
72 | void unsetClip_Paint(iPaint *d) { | 99 | void unsetClip_Paint(iPaint *d) { |
73 | if (numRoots_Window(get_Window()) > 1) { | 100 | if (numRoots_Window(get_Window()) > 1) { |
74 | const iRect rect = rect_Root(get_Root()); | 101 | setClip_Paint(d, rect_Root(get_Root())); |
75 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | ||
76 | return; | 102 | return; |
77 | } | 103 | } |
78 | #if SDL_VERSION_ATLEAST(2, 0, 12) | 104 | #if SDL_VERSION_ATLEAST(2, 0, 12) |
@@ -85,6 +111,7 @@ void unsetClip_Paint(iPaint *d) { | |||
85 | } | 111 | } |
86 | 112 | ||
87 | void drawRect_Paint(const iPaint *d, iRect rect, int color) { | 113 | void drawRect_Paint(const iPaint *d, iRect rect, int color) { |
114 | addv_I2(&rect.pos, origin_Paint); | ||
88 | iInt2 br = bottomRight_Rect(rect); | 115 | iInt2 br = bottomRight_Rect(rect); |
89 | /* Keep the right/bottom edge visible in the window. */ | 116 | /* Keep the right/bottom edge visible in the window. */ |
90 | if (br.x == d->dst->size.x) br.x--; | 117 | if (br.x == d->dst->size.x) br.x--; |
@@ -115,13 +142,19 @@ void drawRectThickness_Paint(const iPaint *d, iRect rect, int thickness, int col | |||
115 | } | 142 | } |
116 | 143 | ||
117 | void fillRect_Paint(const iPaint *d, iRect rect, int color) { | 144 | void fillRect_Paint(const iPaint *d, iRect rect, int color) { |
145 | addv_I2(&rect.pos, origin_Paint); | ||
118 | setColor_Paint_(d, color); | 146 | setColor_Paint_(d, color); |
147 | // printf("fillRect_Paint: %d,%d %dx%d (%d)\n", rect.pos.x, rect.pos.y, rect.size.x, rect.size.y, color); | ||
119 | SDL_RenderFillRect(renderer_Paint_(d), (SDL_Rect *) &rect); | 148 | SDL_RenderFillRect(renderer_Paint_(d), (SDL_Rect *) &rect); |
120 | } | 149 | } |
121 | 150 | ||
122 | void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color, int alpha) { | 151 | void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color, int alpha) { |
152 | addv_I2(&inner.pos, origin_Paint); | ||
123 | SDL_Renderer *render = renderer_Paint_(d); | 153 | SDL_Renderer *render = renderer_Paint_(d); |
124 | SDL_Texture *shadow = get_Window()->borderShadow; | 154 | SDL_Texture *shadow = get_Window()->borderShadow; |
155 | if (!shadow) { | ||
156 | return; | ||
157 | } | ||
125 | const iInt2 size = size_SDLTexture(shadow); | 158 | const iInt2 size = size_SDLTexture(shadow); |
126 | const iRect outer = expanded_Rect(inner, init1_I2(thickness)); | 159 | const iRect outer = expanded_Rect(inner, init1_I2(thickness)); |
127 | const iColor clr = get_Color(color); | 160 | const iColor clr = get_Color(color); |
@@ -146,9 +179,30 @@ void drawSoftShadow_Paint(const iPaint *d, iRect inner, int thickness, int color | |||
146 | &(SDL_Rect){ outer.pos.x, inner.pos.y, thickness, inner.size.y }); | 179 | &(SDL_Rect){ outer.pos.x, inner.pos.y, thickness, inner.size.y }); |
147 | } | 180 | } |
148 | 181 | ||
149 | void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t count, int color) { | 182 | void drawLines_Paint(const iPaint *d, const iInt2 *points, size_t n, int color) { |
150 | setColor_Paint_(d, color); | 183 | setColor_Paint_(d, color); |
151 | SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) points, count); | 184 | iInt2 *offsetPoints = malloc(sizeof(iInt2) * n); |
185 | for (size_t i = 0; i < n; i++) { | ||
186 | offsetPoints[i] = add_I2(points[i], origin_Paint); | ||
187 | } | ||
188 | SDL_RenderDrawLines(renderer_Paint_(d), (const SDL_Point *) offsetPoints, n); | ||
189 | free(offsetPoints); | ||
190 | } | ||
191 | |||
192 | void drawPin_Paint(iPaint *d, iRect rangeRect, int dir, int pinColor) { | ||
193 | const int height = height_Rect(rangeRect); | ||
194 | iRect pin; | ||
195 | if (dir == 0) { | ||
196 | pin = (iRect){ add_I2(topLeft_Rect(rangeRect), init_I2(-gap_UI / 4, -gap_UI)), | ||
197 | init_I2(gap_UI / 2, height + gap_UI) }; | ||
198 | } | ||
199 | else { | ||
200 | pin = (iRect){ addX_I2(topRight_Rect(rangeRect), -gap_UI / 4), | ||
201 | init_I2(gap_UI / 2, height + gap_UI) }; | ||
202 | } | ||
203 | fillRect_Paint(d, pin, pinColor); | ||
204 | fillRect_Paint(d, initCentered_Rect(dir == 0 ? topMid_Rect(pin) : bottomMid_Rect(pin), | ||
205 | init1_I2(gap_UI * 2)), pinColor); | ||
152 | } | 206 | } |
153 | 207 | ||
154 | iInt2 size_SDLTexture(SDL_Texture *d) { | 208 | iInt2 size_SDLTexture(SDL_Texture *d) { |
diff --git a/src/ui/paint.h b/src/ui/paint.h index 90cc2aef..e894b62f 100644 --- a/src/ui/paint.h +++ b/src/ui/paint.h | |||
@@ -36,6 +36,8 @@ struct Impl_Paint { | |||
36 | uint8_t alpha; | 36 | uint8_t alpha; |
37 | }; | 37 | }; |
38 | 38 | ||
39 | extern iInt2 origin_Paint; /* add this to all drawn positions so buffered graphics are correctly offset */ | ||
40 | |||
39 | void init_Paint (iPaint *); | 41 | void init_Paint (iPaint *); |
40 | 42 | ||
41 | void beginTarget_Paint (iPaint *, SDL_Texture *target); | 43 | void beginTarget_Paint (iPaint *, SDL_Texture *target); |
@@ -61,4 +63,6 @@ iLocalDef void drawVLine_Paint(const iPaint *d, iInt2 pos, int len, int color) { | |||
61 | drawLine_Paint(d, pos, addY_I2(pos, len), color); | 63 | drawLine_Paint(d, pos, addY_I2(pos, len), color); |
62 | } | 64 | } |
63 | 65 | ||
66 | void drawPin_Paint (iPaint *, iRect rangeRect, int dir, int pinColor); | ||
67 | |||
64 | iInt2 size_SDLTexture (SDL_Texture *); | 68 | iInt2 size_SDLTexture (SDL_Texture *); |
diff --git a/src/ui/root.c b/src/ui/root.c index a8b9f998..90c0c6e4 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -57,49 +57,49 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
57 | static const iMenuItem navMenuItems_[] = { | 57 | static const iMenuItem navMenuItems_[] = { |
58 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 58 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
59 | { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, | 59 | { "${menu.openlocation}", SDLK_l, KMOD_PRIMARY, "navigate.focus" }, |
60 | { "---", 0, 0, NULL }, | 60 | { "---" }, |
61 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, | 61 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" }, |
62 | { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" }, | 62 | { "${menu.page.copysource}", SDLK_c, KMOD_PRIMARY, "copy" }, |
63 | { "---", 0, 0, NULL }, | 63 | { "---" }, |
64 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 64 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
65 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | 65 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, |
66 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, | 66 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, |
67 | { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, | 67 | { "${menu.zoom.in}", SDLK_EQUALS, KMOD_PRIMARY, "zoom.delta arg:10" }, |
68 | { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, | 68 | { "${menu.zoom.out}", SDLK_MINUS, KMOD_PRIMARY, "zoom.delta arg:-10" }, |
69 | { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, | 69 | { "${menu.zoom.reset}", SDLK_0, KMOD_PRIMARY, "zoom.set arg:100" }, |
70 | { "---", 0, 0, NULL }, | 70 | { "---" }, |
71 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | 71 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, |
72 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | 72 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, |
73 | { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" }, | 73 | { "${menu.bookmarks.bytime}", 0, 0, "!open url:about:bookmarks?created" }, |
74 | { "---", 0, 0, NULL }, | 74 | { "---" }, |
75 | { "${menu.downloads}", 0, 0, "downloads.open" }, | 75 | { "${menu.downloads}", 0, 0, "downloads.open" }, |
76 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | 76 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, |
77 | { "---", 0, 0, NULL }, | 77 | { "---" }, |
78 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 78 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
79 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | 79 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, |
80 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | 80 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, |
81 | { "---", 0, 0, NULL }, | 81 | { "---" }, |
82 | { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } | 82 | { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } |
83 | }; | 83 | }; |
84 | #endif | 84 | #endif |
85 | 85 | ||
86 | #if defined (iPlatformAppleMobile) | 86 | #if defined (iPlatformMobile) |
87 | /* Tablet menu. */ | 87 | /* Tablet menu. */ |
88 | static const iMenuItem tabletNavMenuItems_[] = { | 88 | static const iMenuItem tabletNavMenuItems_[] = { |
89 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, | 89 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, |
90 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 90 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
91 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 91 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
92 | { "---", 0, 0, NULL }, | 92 | { "---" }, |
93 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | 93 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, |
94 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 94 | { leftHalf_Icon " ${menu.sidebar.left}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
95 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, | 95 | { rightHalf_Icon " ${menu.sidebar.right}", SDLK_p, KMOD_PRIMARY | KMOD_SHIFT, "sidebar2.toggle" }, |
96 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, | 96 | { "${menu.view.split}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, |
97 | { "---", 0, 0, NULL }, | 97 | { "---" }, |
98 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | 98 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, |
99 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, | 99 | { "${menu.bookmarks.bytag}", 0, 0, "!open url:about:bookmarks?tags" }, |
100 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | 100 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, |
101 | { "${menu.downloads}", 0, 0, "downloads.open" }, | 101 | { "${menu.downloads}", 0, 0, "downloads.open" }, |
102 | { "---", 0, 0, NULL }, | 102 | { "---" }, |
103 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 103 | { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
104 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, | 104 | { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, |
105 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, | 105 | { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, |
@@ -110,39 +110,39 @@ static const iMenuItem phoneNavMenuItems_[] = { | |||
110 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, | 110 | { folder_Icon " ${menu.openfile}", SDLK_o, KMOD_PRIMARY, "file.open" }, |
111 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, | 111 | { add_Icon " ${menu.newtab}", 't', KMOD_PRIMARY, "tabs.new" }, |
112 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, | 112 | { close_Icon " ${menu.closetab}", 'w', KMOD_PRIMARY, "tabs.close" }, |
113 | { "---", 0, 0, NULL }, | 113 | { "---" }, |
114 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, | 114 | { magnifyingGlass_Icon " ${menu.find}", 0, 0, "focus.set id:find.input" }, |
115 | { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, | 115 | { leftHalf_Icon " ${menu.sidebar}", SDLK_l, KMOD_PRIMARY | KMOD_SHIFT, "sidebar.toggle" }, |
116 | { "---", 0, 0, NULL }, | 116 | { "---" }, |
117 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, | 117 | { book_Icon " ${menu.bookmarks.list}", 0, 0, "!open url:about:bookmarks" }, |
118 | { "${menu.downloads}", 0, 0, "downloads.open" }, | 118 | { "${menu.downloads}", 0, 0, "downloads.open" }, |
119 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, | 119 | { "${menu.feeds.entrylist}", 0, 0, "!open url:about:feeds" }, |
120 | { "---", 0, 0, NULL }, | 120 | { "---" }, |
121 | { gear_Icon " Settings...", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, | 121 | { gear_Icon " ${menu.settings}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, |
122 | }; | 122 | }; |
123 | #endif /* AppleMobile */ | 123 | #endif /* Mobile */ |
124 | 124 | ||
125 | #if defined (iPlatformAppleMobile) | 125 | #if defined (iPlatformMobile) |
126 | static const iMenuItem identityButtonMenuItems_[] = { | 126 | static const iMenuItem identityButtonMenuItems_[] = { |
127 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | 127 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, |
128 | { "---", 0, 0, NULL }, | 128 | { "---" }, |
129 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, | 129 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, |
130 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | 130 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, |
131 | { "---", 0, 0, NULL }, | 131 | { "---" }, |
132 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, | 132 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, |
133 | }; | 133 | }; |
134 | #else /* desktop */ | 134 | #else /* desktop */ |
135 | static const iMenuItem identityButtonMenuItems_[] = { | 135 | static const iMenuItem identityButtonMenuItems_[] = { |
136 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | 136 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, |
137 | { "---", 0, 0, NULL }, | 137 | { "---" }, |
138 | # if !defined (iPlatformAppleDesktop) | 138 | # if !defined (iPlatformAppleDesktop) |
139 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, | 139 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, |
140 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | 140 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, |
141 | { "---", 0, 0, NULL }, | 141 | { "---" }, |
142 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | 142 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, |
143 | # else | 143 | # else |
144 | { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" }, | 144 | { add_Icon " ${menu.identity.new}", 0, 0, "ident.new" }, |
145 | { "---", 0, 0, NULL }, | 145 | { "---" }, |
146 | { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" }, | 146 | { person_Icon " ${menu.show.identities}", 0, 0, "sidebar.mode arg:3 show:1" }, |
147 | # endif | 147 | # endif |
148 | }; | 148 | }; |
@@ -271,13 +271,16 @@ void destroyPending_Root(iRoot *d) { | |||
271 | setCurrent_Root(d); | 271 | setCurrent_Root(d); |
272 | iForEach(PtrSet, i, d->pendingDestruction) { | 272 | iForEach(PtrSet, i, d->pendingDestruction) { |
273 | iWidget *widget = *i.value; | 273 | iWidget *widget = *i.value; |
274 | iAssert(widget->root == d); | ||
274 | if (!isFinished_Anim(&widget->visualOffset) || | 275 | if (!isFinished_Anim(&widget->visualOffset) || |
275 | isBeingVisuallyOffsetByReference_Widget(widget)) { | 276 | isBeingVisuallyOffsetByReference_Widget(widget)) { |
276 | continue; | 277 | continue; |
277 | } | 278 | } |
278 | if (widget->flags & keepOnTop_WidgetFlag) { | 279 | if (widget->flags & keepOnTop_WidgetFlag) { |
279 | removeOne_PtrArray(onTop_Root(widget->root), widget); | 280 | removeOne_PtrArray(d->onTop, widget); |
281 | widget->flags &= ~keepOnTop_WidgetFlag; | ||
280 | } | 282 | } |
283 | iAssert(indexOf_PtrArray(d->onTop, widget) == iInvalidPos); | ||
281 | if (widget->parent) { | 284 | if (widget->parent) { |
282 | removeChild_Widget(widget->parent, widget); | 285 | removeChild_Widget(widget->parent, widget); |
283 | } | 286 | } |
@@ -285,17 +288,14 @@ void destroyPending_Root(iRoot *d) { | |||
285 | iRelease(widget); | 288 | iRelease(widget); |
286 | remove_PtrSetIterator(&i); | 289 | remove_PtrSetIterator(&i); |
287 | } | 290 | } |
288 | setCurrent_Root(oldRoot); | 291 | #if 0 |
289 | } | 292 | printf("Root %p onTop (%zu):\n", d, size_PtrArray(d->onTop)); |
290 | 293 | iConstForEach(PtrArray, t, d->onTop) { | |
291 | void postArrange_Root(iRoot *d) { | 294 | const iWidget *p = *t.value; |
292 | if (!d->pendingArrange) { | 295 | printf(" - %p {%s}\n", p, cstr_String(id_Widget(p))); |
293 | d->pendingArrange = iTrue; | ||
294 | SDL_Event ev = { .type = SDL_USEREVENT }; | ||
295 | ev.user.code = arrange_UserEventCode; | ||
296 | ev.user.data2 = d; | ||
297 | SDL_PushEvent(&ev); | ||
298 | } | 296 | } |
297 | #endif | ||
298 | setCurrent_Root(oldRoot); | ||
299 | } | 299 | } |
300 | 300 | ||
301 | iPtrArray *onTop_Root(iRoot *d) { | 301 | iPtrArray *onTop_Root(iRoot *d) { |
@@ -312,7 +312,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
312 | iWidget *menu = findChild_Widget(button, "menu"); | 312 | iWidget *menu = findChild_Widget(button, "menu"); |
313 | iAssert(menu); | 313 | iAssert(menu); |
314 | if (!isVisible_Widget(menu)) { | 314 | if (!isVisible_Widget(menu)) { |
315 | openMenu_Widget(menu, bottomLeft_Rect(bounds_Widget(button))); | 315 | openMenu_Widget(menu, topLeft_Rect(bounds_Widget(button))); |
316 | } | 316 | } |
317 | else { | 317 | else { |
318 | closeMenu_Widget(menu); | 318 | closeMenu_Widget(menu); |
@@ -373,18 +373,18 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
373 | else if (equal_Command(cmd, "window.setrect")) { | 373 | else if (equal_Command(cmd, "window.setrect")) { |
374 | const int snap = argLabel_Command(cmd, "snap"); | 374 | const int snap = argLabel_Command(cmd, "snap"); |
375 | if (snap) { | 375 | if (snap) { |
376 | iWindow *window = get_Window(); | 376 | iMainWindow *window = get_MainWindow(); |
377 | iInt2 coord = coord_Command(cmd); | 377 | iInt2 coord = coord_Command(cmd); |
378 | iInt2 size = init_I2(argLabel_Command(cmd, "width"), | 378 | iInt2 size = init_I2(argLabel_Command(cmd, "width"), |
379 | argLabel_Command(cmd, "height")); | 379 | argLabel_Command(cmd, "height")); |
380 | SDL_SetWindowPosition(window->win, coord.x, coord.y); | 380 | SDL_SetWindowPosition(window->base.win, coord.x, coord.y); |
381 | SDL_SetWindowSize(window->win, size.x, size.y); | 381 | SDL_SetWindowSize(window->base.win, size.x, size.y); |
382 | window->place.snap = snap; | 382 | window->place.snap = snap; |
383 | return iTrue; | 383 | return iTrue; |
384 | } | 384 | } |
385 | } | 385 | } |
386 | else if (equal_Command(cmd, "window.restore")) { | 386 | else if (equal_Command(cmd, "window.restore")) { |
387 | setSnap_Window(get_Window(), none_WindowSnap); | 387 | setSnap_MainWindow(get_MainWindow(), none_WindowSnap); |
388 | return iTrue; | 388 | return iTrue; |
389 | } | 389 | } |
390 | else if (equal_Command(cmd, "window.minimize")) { | 390 | else if (equal_Command(cmd, "window.minimize")) { |
@@ -400,8 +400,6 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
400 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); | 400 | iSidebarWidget *sidebar = findChild_Widget(root, "sidebar"); |
401 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); | 401 | iSidebarWidget *sidebar2 = findChild_Widget(root, "sidebar2"); |
402 | removeChild_Widget(parent_Widget(sidebar), sidebar); | 402 | removeChild_Widget(parent_Widget(sidebar), sidebar); |
403 | setButtonFont_SidebarWidget(sidebar, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
404 | setButtonFont_SidebarWidget(sidebar2, isLandscape_App() ? uiLabel_FontId : defaultBig_FontId); | ||
405 | // setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), | 403 | // setBackgroundColor_Widget(findChild_Widget(as_Widget(sidebar), "buttons"), |
406 | // isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId | 404 | // isPortrait_App() ? uiBackgroundUnfocusedSelection_ColorId |
407 | // : uiBackgroundSidebar_ColorId); | 405 | // : uiBackgroundSidebar_ColorId); |
@@ -432,19 +430,19 @@ static void updateNavBarIdentity_(iWidget *navBar) { | |||
432 | const iGmIdentity *ident = | 430 | const iGmIdentity *ident = |
433 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); | 431 | identityForUrl_GmCerts(certs_App(), url_DocumentWidget(document_App())); |
434 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); | 432 | iWidget *button = findChild_Widget(navBar, "navbar.ident"); |
435 | iLabelWidget *toolButton = findWidget_App("toolbar.ident"); | 433 | iWidget *menu = findChild_Widget(button, "menu"); |
436 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); | 434 | setFlags_Widget(button, selected_WidgetFlag, ident != NULL); |
437 | setOutline_LabelWidget(toolButton, ident == NULL); | ||
438 | /* Update menu. */ | 435 | /* Update menu. */ |
439 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); | ||
440 | const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; | 436 | const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; |
441 | setTextCStr_LabelWidget( | 437 | const char * idLabel = subjectName |
442 | idItem, | 438 | ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) |
443 | subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) | 439 | : "${menu.identity.notactive}"; |
444 | : "${menu.identity.notactive}"); | 440 | setMenuItemLabelByIndex_Widget(menu, 0, idLabel); |
445 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); | 441 | setMenuItemDisabledByIndex_Widget(menu, 0, !ident); |
442 | iLabelWidget *toolButton = findWidget_App("toolbar.ident"); | ||
446 | iLabelWidget *toolName = findWidget_App("toolbar.name"); | 443 | iLabelWidget *toolName = findWidget_App("toolbar.name"); |
447 | if (toolName) { | 444 | if (toolName) { |
445 | setOutline_LabelWidget(toolButton, ident == NULL); | ||
448 | updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); | 446 | updateTextCStr_LabelWidget(toolName, subjectName ? cstr_String(subjectName) : ""); |
449 | setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId); | 447 | setFont_LabelWidget(toolButton, subjectName ? defaultMedium_FontId : uiLabelLarge_FontId); |
450 | arrange_Widget(parent_Widget(toolButton)); | 448 | arrange_Widget(parent_Widget(toolButton)); |
@@ -498,9 +496,10 @@ static void checkLoadAnimation_Root_(iRoot *d) { | |||
498 | 496 | ||
499 | void updatePadding_Root(iRoot *d) { | 497 | void updatePadding_Root(iRoot *d) { |
500 | if (d == NULL) return; | 498 | if (d == NULL) return; |
501 | #if defined (iPlatformAppleMobile) | ||
502 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | 499 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); |
503 | float left, top, right, bottom; | 500 | float bottom = 0.0f; |
501 | #if defined (iPlatformAppleMobile) | ||
502 | float left, top, right; | ||
504 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 503 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
505 | /* Respect the safe area insets. */ { | 504 | /* Respect the safe area insets. */ { |
506 | setPadding_Widget(findChild_Widget(d->widget, "navdiv"), left, top, right, 0); | 505 | setPadding_Widget(findChild_Widget(d->widget, "navdiv"), left, top, right, 0); |
@@ -508,6 +507,7 @@ void updatePadding_Root(iRoot *d) { | |||
508 | setPadding_Widget(toolBar, left, 0, right, bottom); | 507 | setPadding_Widget(toolBar, left, 0, right, bottom); |
509 | } | 508 | } |
510 | } | 509 | } |
510 | #endif | ||
511 | if (toolBar) { | 511 | if (toolBar) { |
512 | /* TODO: get this from toolBar height, but it's buggy for some reason */ | 512 | /* TODO: get this from toolBar height, but it's buggy for some reason */ |
513 | const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; | 513 | const int sidebarBottomPad = isPortrait_App() ? 11 * gap_UI + bottom : 0; |
@@ -517,14 +517,15 @@ void updatePadding_Root(iRoot *d) { | |||
517 | are not arranged correctly until it's hidden and reshown. */ | 517 | are not arranged correctly until it's hidden and reshown. */ |
518 | } | 518 | } |
519 | /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ | 519 | /* Note that `handleNavBarCommands_` also adjusts padding and spacing. */ |
520 | #endif | ||
521 | } | 520 | } |
522 | 521 | ||
523 | void updateToolbarColors_Root(iRoot *d) { | 522 | void updateToolbarColors_Root(iRoot *d) { |
524 | #if defined (iPlatformMobile) | 523 | #if defined (iPlatformMobile) |
525 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); | 524 | iWidget *toolBar = findChild_Widget(d->widget, "toolbar"); |
526 | if (toolBar) { | 525 | if (toolBar) { |
527 | const iBool isSidebarVisible = isVisible_Widget(findChild_Widget(d->widget, "sidebar")); | 526 | const iBool isSidebarVisible = |
527 | isVisible_Widget(findChild_Widget(d->widget, "sidebar")) || | ||
528 | isVisible_Widget(findChild_Widget(d->widget, "sidebar2")); | ||
528 | const int bg = isSidebarVisible ? uiBackgroundSidebar_ColorId : | 529 | const int bg = isSidebarVisible ? uiBackgroundSidebar_ColorId : |
529 | tmBannerBackground_ColorId; | 530 | tmBannerBackground_ColorId; |
530 | setBackgroundColor_Widget(toolBar, bg); | 531 | setBackgroundColor_Widget(toolBar, bg); |
@@ -684,6 +685,35 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) { | |||
684 | } | 685 | } |
685 | return iTrue; | 686 | return iTrue; |
686 | } | 687 | } |
688 | else if (deviceType_App() != desktop_AppDeviceType && | ||
689 | (equal_Command(cmd, "focus.gained") || equal_Command(cmd, "focus.lost"))) { | ||
690 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
691 | if (pointer_Command(cmd) == url) { | ||
692 | const iBool isFocused = equal_Command(cmd, "focus.gained"); | ||
693 | setFlags_Widget(findChild_Widget(navBar, "navbar.lock"), hidden_WidgetFlag, isFocused); | ||
694 | setFlags_Widget(findChild_Widget(navBar, "navbar.clear"), hidden_WidgetFlag, !isFocused); | ||
695 | showCollapsed_Widget(findChild_Widget(navBar, "navbar.cancel"), isFocused); | ||
696 | showCollapsed_Widget(findChild_Widget(navBar, "pagemenubutton"), !isFocused); | ||
697 | showCollapsed_Widget(findChild_Widget(navBar, "reload"), !isFocused); | ||
698 | } | ||
699 | return iFalse; | ||
700 | } | ||
701 | else if (equal_Command(cmd, "navbar.clear")) { | ||
702 | iInputWidget *url = findChild_Widget(navBar, "url"); | ||
703 | selectAll_InputWidget(url); | ||
704 | /* Emulate a Backspace keypress. */ | ||
705 | class_InputWidget(url)->processEvent( | ||
706 | as_Widget(url), | ||
707 | (SDL_Event *) &(SDL_KeyboardEvent){ .type = SDL_KEYDOWN, | ||
708 | .timestamp = SDL_GetTicks(), | ||
709 | .state = SDL_PRESSED, | ||
710 | .keysym = { .sym = SDLK_BACKSPACE } }); | ||
711 | return iTrue; | ||
712 | } | ||
713 | else if (equal_Command(cmd, "navbar.cancel")) { | ||
714 | setFocus_Widget(NULL); | ||
715 | return iTrue; | ||
716 | } | ||
687 | else if (equal_Command(cmd, "input.edited")) { | 717 | else if (equal_Command(cmd, "input.edited")) { |
688 | iAnyObject * url = findChild_Widget(navBar, "url"); | 718 | iAnyObject * url = findChild_Widget(navBar, "url"); |
689 | const iString *text = text_InputWidget(url); | 719 | const iString *text = text_InputWidget(url); |
@@ -829,13 +859,13 @@ static iBool handleSearchBarCommands_(iWidget *searchBar, const char *cmd) { | |||
829 | return iFalse; | 859 | return iFalse; |
830 | } | 860 | } |
831 | 861 | ||
832 | #if defined (iPlatformAppleMobile) | 862 | #if defined (iPlatformMobile) |
833 | static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) { | 863 | static void dismissSidebar_(iWidget *sidebar, const char *toolButtonId) { |
834 | if (isVisible_Widget(sidebar)) { | 864 | if (isVisible_Widget(sidebar)) { |
835 | postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar))); | 865 | postCommandf_App("%s.toggle", cstr_String(id_Widget(sidebar))); |
836 | if (toolButtonId) { | 866 | // if (toolButtonId) { |
837 | // setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue); | 867 | // setFlags_Widget(findWidget_App(toolButtonId), noBackground_WidgetFlag, iTrue); |
838 | } | 868 | // } |
839 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); | 869 | setVisualOffset_Widget(sidebar, height_Widget(sidebar), 250, easeIn_AnimFlag); |
840 | } | 870 | } |
841 | } | 871 | } |
@@ -909,7 +939,7 @@ static iBool handleToolBarCommands_(iWidget *toolBar, const char *cmd) { | |||
909 | } | 939 | } |
910 | return iFalse; | 940 | return iFalse; |
911 | } | 941 | } |
912 | #endif /* defined (iPlatformAppleMobile) */ | 942 | #endif /* defined (iPlatformMobile) */ |
913 | 943 | ||
914 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { | 944 | static iLabelWidget *newLargeIcon_LabelWidget(const char *text, const char *cmd) { |
915 | iLabelWidget *lab = newIcon_LabelWidget(text, 0, 0, cmd); | 945 | iLabelWidget *lab = newIcon_LabelWidget(text, 0, 0, cmd); |
@@ -940,7 +970,7 @@ void updateMetrics_Root(iRoot *d) { | |||
940 | setFixedSize_Widget(appIcon, init_I2(appIconSize_Root(), appMin->rect.size.y)); | 970 | setFixedSize_Widget(appIcon, init_I2(appIconSize_Root(), appMin->rect.size.y)); |
941 | } | 971 | } |
942 | iWidget *navBar = findChild_Widget(d->widget, "navbar"); | 972 | iWidget *navBar = findChild_Widget(d->widget, "navbar"); |
943 | iWidget *lock = findChild_Widget(navBar, "navbar.lock"); | 973 | // iWidget *lock = findChild_Widget(navBar, "navbar.lock"); |
944 | iWidget *url = findChild_Widget(d->widget, "url"); | 974 | iWidget *url = findChild_Widget(d->widget, "url"); |
945 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | 975 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); |
946 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | 976 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); |
@@ -1043,6 +1073,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1043 | /* Navigation bar. */ { | 1073 | /* Navigation bar. */ { |
1044 | navBar = new_Widget(); | 1074 | navBar = new_Widget(); |
1045 | setId_Widget(navBar, "navbar"); | 1075 | setId_Widget(navBar, "navbar"); |
1076 | setDrawBufferEnabled_Widget(navBar, iTrue); | ||
1046 | setFlags_Widget(navBar, | 1077 | setFlags_Widget(navBar, |
1047 | hittable_WidgetFlag | /* context menu */ | 1078 | hittable_WidgetFlag | /* context menu */ |
1048 | arrangeHeight_WidgetFlag | | 1079 | arrangeHeight_WidgetFlag | |
@@ -1094,6 +1125,16 @@ void createUserInterface_Root(iRoot *d) { | |||
1094 | setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); | 1125 | setFont_LabelWidget(lock, symbols_FontId + uiNormal_FontSize); |
1095 | updateTextCStr_LabelWidget(lock, "\U0001f512"); | 1126 | updateTextCStr_LabelWidget(lock, "\U0001f512"); |
1096 | } | 1127 | } |
1128 | /* Button for clearing the URL bar contents. */ { | ||
1129 | iLabelWidget *clear = addChildFlags_Widget( | ||
1130 | as_Widget(url), | ||
1131 | iClob(newIcon_LabelWidget(delete_Icon, 0, 0, "navbar.clear")), | ||
1132 | hidden_WidgetFlag | embedFlags | moveToParentLeftEdge_WidgetFlag | tight_WidgetFlag); | ||
1133 | setId_Widget(as_Widget(clear), "navbar.clear"); | ||
1134 | setFont_LabelWidget(clear, symbols2_FontId + uiNormal_FontSize); | ||
1135 | // setFlags_Widget(as_Widget(clear), noBackground_WidgetFlag, iFalse); | ||
1136 | // setBackgroundColor_Widget(as_Widget(clear), uiBackground_ColorId); | ||
1137 | } | ||
1097 | iWidget *rightEmbed = new_Widget(); | 1138 | iWidget *rightEmbed = new_Widget(); |
1098 | setId_Widget(rightEmbed, "url.rightembed"); | 1139 | setId_Widget(rightEmbed, "url.rightembed"); |
1099 | addChildFlags_Widget(as_Widget(url), | 1140 | addChildFlags_Widget(as_Widget(url), |
@@ -1150,6 +1191,13 @@ void createUserInterface_Root(iRoot *d) { | |||
1150 | setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | 1191 | setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); |
1151 | /* Mobile page menu. */ | 1192 | /* Mobile page menu. */ |
1152 | if (deviceType_App() != desktop_AppDeviceType) { | 1193 | if (deviceType_App() != desktop_AppDeviceType) { |
1194 | iLabelWidget *navCancel = new_LabelWidget("${cancel}", "navbar.cancel"); | ||
1195 | addChildFlags_Widget(urlButtons, iClob(navCancel), | ||
1196 | (embedFlags | tight_WidgetFlag | hidden_WidgetFlag | | ||
1197 | collapse_WidgetFlag) /*& ~noBackground_WidgetFlag*/); | ||
1198 | as_Widget(navCancel)->sizeRef = as_Widget(url); | ||
1199 | setFont_LabelWidget(navCancel, uiContentBold_FontId); | ||
1200 | setId_Widget(as_Widget(navCancel), "navbar.cancel"); | ||
1153 | iLabelWidget *pageMenuButton; | 1201 | iLabelWidget *pageMenuButton; |
1154 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | 1202 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ |
1155 | pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, | 1203 | pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, |
@@ -1157,13 +1205,13 @@ void createUserInterface_Root(iRoot *d) { | |||
1157 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | 1205 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, |
1158 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | 1206 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, |
1159 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | 1207 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, |
1160 | { "---", 0, 0, NULL }, | 1208 | { "---" }, |
1161 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 1209 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
1162 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | 1210 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, |
1163 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 1211 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
1164 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | 1212 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, |
1165 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, | 1213 | { upload_Icon " ${menu.page.upload}", 0, 0, "document.upload" }, |
1166 | { "---", 0, 0, NULL }, | 1214 | { "---" }, |
1167 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | 1215 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, |
1168 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | 1216 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, |
1169 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | 1217 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, |
@@ -1171,13 +1219,14 @@ void createUserInterface_Root(iRoot *d) { | |||
1171 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); | 1219 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); |
1172 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); | 1220 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); |
1173 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); | 1221 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); |
1174 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags | tight_WidgetFlag); | 1222 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), |
1223 | embedFlags | tight_WidgetFlag | collapse_WidgetFlag); | ||
1175 | updateSize_LabelWidget(pageMenuButton); | 1224 | updateSize_LabelWidget(pageMenuButton); |
1176 | } | 1225 | } |
1177 | /* Reload button. */ { | 1226 | /* Reload button. */ { |
1178 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | 1227 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); |
1179 | setId_Widget(as_Widget(reload), "reload"); | 1228 | setId_Widget(as_Widget(reload), "reload"); |
1180 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags); | 1229 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags | collapse_WidgetFlag); |
1181 | updateSize_LabelWidget(reload); | 1230 | updateSize_LabelWidget(reload); |
1182 | } | 1231 | } |
1183 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); | 1232 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); |
@@ -1198,8 +1247,8 @@ void createUserInterface_Root(iRoot *d) { | |||
1198 | #if defined (iPlatformMobile) | 1247 | #if defined (iPlatformMobile) |
1199 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); | 1248 | const iBool isPhone = (deviceType_App() == phone_AppDeviceType); |
1200 | #endif | 1249 | #endif |
1201 | #if !defined (iHaveNativeMenus) | 1250 | #if !defined (iHaveNativeMenus) || defined (iPlatformMobile) |
1202 | # if defined (iPlatformAppleMobile) | 1251 | # if defined (iPlatformMobile) |
1203 | iLabelWidget *navMenu = | 1252 | iLabelWidget *navMenu = |
1204 | makeMenuButton_LabelWidget(menu_Icon, isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, | 1253 | makeMenuButton_LabelWidget(menu_Icon, isPhone ? phoneNavMenuItems_ : tabletNavMenuItems_, |
1205 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); | 1254 | isPhone ? iElemCount(phoneNavMenuItems_) : iElemCount(tabletNavMenuItems_)); |
@@ -1280,12 +1329,13 @@ void createUserInterface_Root(iRoot *d) { | |||
1280 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); | 1329 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(" \u2b9d ", 'g', KMOD_PRIMARY | KMOD_SHIFT, "find.prev"))); |
1281 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(close_Icon, SDLK_ESCAPE, 0, "find.close"))); | 1330 | addChild_Widget(searchBar, iClob(newIcon_LabelWidget(close_Icon, SDLK_ESCAPE, 0, "find.close"))); |
1282 | } | 1331 | } |
1283 | #if defined (iPlatformAppleMobile) | 1332 | #if defined (iPlatformMobile) |
1284 | /* Bottom toolbar. */ | 1333 | /* Bottom toolbar. */ |
1285 | if (isPhone_iOS()) { | 1334 | if (deviceType_App() == phone_AppDeviceType) { |
1286 | iWidget *toolBar = new_Widget(); | 1335 | iWidget *toolBar = new_Widget(); |
1287 | addChild_Widget(root, iClob(toolBar)); | 1336 | addChild_Widget(root, iClob(toolBar)); |
1288 | setId_Widget(toolBar, "toolbar"); | 1337 | setId_Widget(toolBar, "toolbar"); |
1338 | setDrawBufferEnabled_Widget(toolBar, iTrue); | ||
1289 | setCommandHandler_Widget(toolBar, handleToolBarCommands_); | 1339 | setCommandHandler_Widget(toolBar, handleToolBarCommands_); |
1290 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | | 1340 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | |
1291 | parentCannotResizeHeight_WidgetFlag | | 1341 | parentCannotResizeHeight_WidgetFlag | |
@@ -1346,35 +1396,50 @@ void createUserInterface_Root(iRoot *d) { | |||
1346 | (iMenuItem[]){ | 1396 | (iMenuItem[]){ |
1347 | { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" }, | 1397 | { close_Icon " ${menu.closetab}", 0, 0, "tabs.close" }, |
1348 | { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" }, | 1398 | { copy_Icon " ${menu.duptab}", 0, 0, "tabs.new duplicate:1" }, |
1349 | { "---", 0, 0, NULL }, | 1399 | { "---" }, |
1350 | { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, | 1400 | { "${menu.closetab.other}", 0, 0, "tabs.close toleft:1 toright:1" }, |
1351 | { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, | 1401 | { barLeftArrow_Icon " ${menu.closetab.left}", 0, 0, "tabs.close toleft:1" }, |
1352 | { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, | 1402 | { barRightArrow_Icon " ${menu.closetab.right}", 0, 0, "tabs.close toright:1" }, |
1353 | }, | 1403 | }, |
1354 | 6); | 1404 | 6); |
1355 | iWidget *barMenu = | 1405 | iWidget *barMenu = |
1356 | makeMenu_Widget(root, | 1406 | makeMenu_Widget(root, |
1357 | (iMenuItem[]){ | 1407 | (iMenuItem[]){ |
1358 | { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, | 1408 | { leftHalf_Icon " ${menu.sidebar.left}", 0, 0, "sidebar.toggle" }, |
1359 | { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, | 1409 | { rightHalf_Icon " ${menu.sidebar.right}", 0, 0, "sidebar2.toggle" }, |
1360 | }, | 1410 | }, |
1361 | deviceType_App() == phone_AppDeviceType ? 1 : 2); | 1411 | deviceType_App() == phone_AppDeviceType ? 1 : 2); |
1362 | iWidget *clipMenu = makeMenu_Widget(root, | 1412 | iWidget *clipMenu = makeMenu_Widget(root, |
1363 | (iMenuItem[]){ | 1413 | #if defined (iPlatformMobile) |
1364 | { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, | 1414 | (iMenuItem[]){ |
1365 | { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, | 1415 | { ">>>" scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, |
1366 | { "---", 0, 0, NULL }, | 1416 | { ">>>" clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, |
1367 | { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, | 1417 | { ">>>" clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, |
1368 | }, | 1418 | { "---" }, |
1369 | 4); | 1419 | { ">>>" delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, |
1420 | { ">>>" select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, | ||
1421 | { ">>>" undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, | ||
1422 | }, 7); | ||
1423 | #else | ||
1424 | (iMenuItem[]){ | ||
1425 | { scissor_Icon " ${menu.cut}", 0, 0, "input.copy cut:1" }, | ||
1426 | { clipCopy_Icon " ${menu.copy}", 0, 0, "input.copy" }, | ||
1427 | { clipboard_Icon " ${menu.paste}", 0, 0, "input.paste" }, | ||
1428 | { "---" }, | ||
1429 | { delete_Icon " " uiTextCaution_ColorEscape "${menu.delete}", 0, 0, "input.delete" }, | ||
1430 | { undo_Icon " ${menu.undo}", 0, 0, "input.undo" }, | ||
1431 | { "---" }, | ||
1432 | { select_Icon " ${menu.selectall}", 0, 0, "input.selectall" }, | ||
1433 | }, 8); | ||
1434 | #endif | ||
1370 | iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ | 1435 | iWidget *splitMenu = makeMenu_Widget(root, (iMenuItem[]){ |
1371 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, | 1436 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, |
1372 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, | 1437 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, |
1373 | { "---", 0, 0, NULL }, | 1438 | { "---" }, |
1374 | { "${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0" }, | 1439 | { "${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0" }, |
1375 | { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" }, | 1440 | { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" }, |
1376 | { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" }, | 1441 | { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" }, |
1377 | { "---", 0, 0, NULL }, | 1442 | { "---" }, |
1378 | { "${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1" }, | 1443 | { "${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1" }, |
1379 | { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" }, | 1444 | { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" }, |
1380 | { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" }, | 1445 | { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" }, |
@@ -1440,7 +1505,7 @@ iRect rect_Root(const iRoot *d) { | |||
1440 | } | 1505 | } |
1441 | 1506 | ||
1442 | iRect safeRect_Root(const iRoot *d) { | 1507 | iRect safeRect_Root(const iRoot *d) { |
1443 | iRect rect = { zero_I2(), size_Root(d) }; | 1508 | iRect rect = rect_Root(d); |
1444 | #if defined (iPlatformAppleMobile) | 1509 | #if defined (iPlatformAppleMobile) |
1445 | float left, top, right, bottom; | 1510 | float left, top, right, bottom; |
1446 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | 1511 | safeAreaInsets_iOS(&left, &top, &right, &bottom); |
@@ -1450,5 +1515,5 @@ iRect safeRect_Root(const iRoot *d) { | |||
1450 | } | 1515 | } |
1451 | 1516 | ||
1452 | iInt2 visibleSize_Root(const iRoot *d) { | 1517 | iInt2 visibleSize_Root(const iRoot *d) { |
1453 | return addY_I2(size_Root(d), -get_Window()->keyboardHeight); | 1518 | return addY_I2(size_Root(d), -get_MainWindow()->keyboardHeight); |
1454 | } | 1519 | } |
diff --git a/src/ui/root.h b/src/ui/root.h index 740e97c9..04dd5e16 100644 --- a/src/ui/root.h +++ b/src/ui/root.h | |||
@@ -9,6 +9,7 @@ iDeclareType(Root) | |||
9 | 9 | ||
10 | struct Impl_Root { | 10 | struct Impl_Root { |
11 | iWidget * widget; | 11 | iWidget * widget; |
12 | iWindow * window; | ||
12 | iPtrArray *onTop; /* order is important; last one is topmost */ | 13 | iPtrArray *onTop; /* order is important; last one is topmost */ |
13 | iPtrSet * pendingDestruction; | 14 | iPtrSet * pendingDestruction; |
14 | iBool pendingArrange; | 15 | iBool pendingArrange; |
@@ -29,7 +30,6 @@ iAnyObject *findWidget_Root (const char *id); /* under curre | |||
29 | 30 | ||
30 | iPtrArray * onTop_Root (iRoot *); | 31 | iPtrArray * onTop_Root (iRoot *); |
31 | void destroyPending_Root (iRoot *); | 32 | void destroyPending_Root (iRoot *); |
32 | void postArrange_Root (iRoot *); | ||
33 | 33 | ||
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? */ |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index 0bab601a..b6f73b6c 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -90,8 +90,22 @@ static int thumbSize_ScrollWidget_(const iScrollWidget *d) { | |||
90 | return iMax(gap_UI * 6, d->thumbSize); | 90 | return iMax(gap_UI * 6, d->thumbSize); |
91 | } | 91 | } |
92 | 92 | ||
93 | static iRect bounds_ScrollWidget_(const iScrollWidget *d) { | ||
94 | const iWidget *w = constAs_Widget(d); | ||
95 | iRect bounds = bounds_Widget(w); | ||
96 | if (deviceType_App() == phone_AppDeviceType && isPortrait_App()) { | ||
97 | /* Account for the hidable toolbar. */ | ||
98 | int toolbarHeight = lineHeight_Text(uiLabelLarge_FontId) + 3 * gap_UI; | ||
99 | int excess = bottom_Rect(bounds) - (bottom_Rect(safeRect_Root(w->root)) - toolbarHeight); | ||
100 | if (excess > 0) { | ||
101 | adjustEdges_Rect(&bounds, 0, 0, -excess, 0); | ||
102 | } | ||
103 | } | ||
104 | return bounds; | ||
105 | } | ||
106 | |||
93 | static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) { | 107 | static iRect thumbRect_ScrollWidget_(const iScrollWidget *d) { |
94 | const iRect bounds = bounds_Widget(constAs_Widget(d)); | 108 | const iRect bounds = bounds_ScrollWidget_(d); |
95 | iRect rect = init_Rect(bounds.pos.x, bounds.pos.y, bounds.size.x, 0); | 109 | iRect rect = init_Rect(bounds.pos.x, bounds.pos.y, bounds.size.x, 0); |
96 | const int total = size_Range(&d->range); | 110 | const int total = size_Range(&d->range); |
97 | if (total > 0) { | 111 | if (total > 0) { |
@@ -181,7 +195,7 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | |||
181 | refresh_Widget(w); | 195 | refresh_Widget(w); |
182 | return iTrue; | 196 | return iTrue; |
183 | case drag_ClickResult: { | 197 | case drag_ClickResult: { |
184 | const iRect bounds = bounds_Widget(w); | 198 | const iRect bounds = bounds_ScrollWidget_(d); |
185 | const int offset = delta_Click(&d->click).y; | 199 | const int offset = delta_Click(&d->click).y; |
186 | const int total = size_Range(&d->range); | 200 | const int total = size_Range(&d->range); |
187 | int dpos = (float) offset / (float) (height_Rect(bounds) - thumbSize_ScrollWidget_(d)) * total; | 201 | int dpos = (float) offset / (float) (height_Rect(bounds) - thumbSize_ScrollWidget_(d)) * total; |
@@ -218,7 +232,7 @@ static iBool processEvent_ScrollWidget_(iScrollWidget *d, const SDL_Event *ev) { | |||
218 | 232 | ||
219 | static void draw_ScrollWidget_(const iScrollWidget *d) { | 233 | static void draw_ScrollWidget_(const iScrollWidget *d) { |
220 | const iWidget *w = constAs_Widget(d); | 234 | const iWidget *w = constAs_Widget(d); |
221 | const iRect bounds = bounds_Widget(w); | 235 | const iRect bounds = bounds_ScrollWidget_(d); |
222 | const iBool isPressed = (flags_Widget(w) & pressed_WidgetFlag) != 0; | 236 | const iBool isPressed = (flags_Widget(w) & pressed_WidgetFlag) != 0; |
223 | if (bounds.size.x > 0) { | 237 | if (bounds.size.x > 0) { |
224 | iPaint p; | 238 | iPaint p; |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index c2ad7bc6..3018f16d 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -100,7 +100,7 @@ struct Impl_SidebarWidget { | |||
100 | int modeScroll[max_SidebarMode]; | 100 | int modeScroll[max_SidebarMode]; |
101 | iLabelWidget * modeButtons[max_SidebarMode]; | 101 | iLabelWidget * modeButtons[max_SidebarMode]; |
102 | int maxButtonLabelWidth; | 102 | int maxButtonLabelWidth; |
103 | int widthAsGaps; | 103 | float widthAsGaps; |
104 | int buttonFont; | 104 | int buttonFont; |
105 | int itemFonts[2]; | 105 | int itemFonts[2]; |
106 | size_t numUnreadEntries; | 106 | size_t numUnreadEntries; |
@@ -108,6 +108,7 @@ struct Impl_SidebarWidget { | |||
108 | iWidget * menu; | 108 | iWidget * menu; |
109 | iSidebarItem * contextItem; /* list item accessed in the context menu */ | 109 | iSidebarItem * contextItem; /* list item accessed in the context menu */ |
110 | size_t contextIndex; /* index of list item accessed in the context menu */ | 110 | size_t contextIndex; /* index of list item accessed in the context menu */ |
111 | iIntSet * closedFolders; /* otherwise open */ | ||
111 | }; | 112 | }; |
112 | 113 | ||
113 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) | 114 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) |
@@ -116,23 +117,66 @@ static iBool isResizing_SidebarWidget_(const iSidebarWidget *d) { | |||
116 | return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; | 117 | return (flags_Widget(d->resizer) & pressed_WidgetFlag) != 0; |
117 | } | 118 | } |
118 | 119 | ||
119 | static int cmpTitle_Bookmark_(const iBookmark **a, const iBookmark **b) { | 120 | iBookmark *parent_Bookmark(const iBookmark *d) { |
121 | /* TODO: Parent pointers should be prefetched! */ | ||
122 | if (d->parentId) { | ||
123 | return get_Bookmarks(bookmarks_App(), d->parentId); | ||
124 | } | ||
125 | return NULL; | ||
126 | } | ||
127 | |||
128 | iBool hasParent_Bookmark(const iBookmark *d, uint32_t parentId) { | ||
129 | /* TODO: Parent pointers should be prefetched! */ | ||
130 | while (d->parentId) { | ||
131 | if (d->parentId == parentId) { | ||
132 | return iTrue; | ||
133 | } | ||
134 | d = get_Bookmarks(bookmarks_App(), d->parentId); | ||
135 | } | ||
136 | return iFalse; | ||
137 | } | ||
138 | |||
139 | int depth_Bookmark(const iBookmark *d) { | ||
140 | /* TODO: Precalculate this! */ | ||
141 | int depth = 0; | ||
142 | for (; d->parentId; depth++) { | ||
143 | d = get_Bookmarks(bookmarks_App(), d->parentId); | ||
144 | } | ||
145 | return depth; | ||
146 | } | ||
147 | |||
148 | int cmpTree_Bookmark(const iBookmark **a, const iBookmark **b) { | ||
120 | const iBookmark *bm1 = *a, *bm2 = *b; | 149 | const iBookmark *bm1 = *a, *bm2 = *b; |
121 | if (bm2->sourceId == id_Bookmark(bm1)) { | 150 | /* Contents of a parent come after it. */ |
151 | if (hasParent_Bookmark(bm2, id_Bookmark(bm1))) { | ||
122 | return -1; | 152 | return -1; |
123 | } | 153 | } |
124 | if (bm1->sourceId == id_Bookmark(bm2)) { | 154 | if (hasParent_Bookmark(bm1, id_Bookmark(bm2))) { |
125 | return 1; | 155 | return 1; |
126 | } | 156 | } |
127 | if (bm1->sourceId == bm2->sourceId) { | 157 | /* Comparisons are only valid inside the same parent. */ |
128 | return cmpStringCase_String(&bm1->title, &bm2->title); | 158 | while (bm1->parentId != bm2->parentId) { |
129 | } | 159 | int depth1 = depth_Bookmark(bm1); |
130 | if (bm1->sourceId) { | 160 | int depth2 = depth_Bookmark(bm2); |
131 | bm1 = get_Bookmarks(bookmarks_App(), bm1->sourceId); | 161 | if (depth1 != depth2) { |
132 | } | 162 | /* Equalize the depth. */ |
133 | if (bm2->sourceId) { | 163 | while (depth1 > depth2) { |
134 | bm2 = get_Bookmarks(bookmarks_App(), bm2->sourceId); | 164 | bm1 = parent_Bookmark(bm1); |
165 | depth1--; | ||
166 | } | ||
167 | while (depth2 > depth1) { | ||
168 | bm2 = parent_Bookmark(bm2); | ||
169 | depth2--; | ||
170 | } | ||
171 | continue; | ||
172 | } | ||
173 | bm1 = parent_Bookmark(bm1); | ||
174 | depth1--; | ||
175 | bm2 = parent_Bookmark(bm2); | ||
176 | depth2--; | ||
135 | } | 177 | } |
178 | const int cmp = iCmp(bm1->order, bm2->order); | ||
179 | if (cmp) return cmp; | ||
136 | return cmpStringCase_String(&bm1->title, &bm2->title); | 180 | return cmpStringCase_String(&bm1->title, &bm2->title); |
137 | } | 181 | } |
138 | 182 | ||
@@ -143,7 +187,9 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha | |||
143 | //(deviceType_App() != desktop_AppDeviceType ? | 187 | //(deviceType_App() != desktop_AppDeviceType ? |
144 | // extraPadding_WidgetFlag : 0) | | 188 | // extraPadding_WidgetFlag : 0) | |
145 | flags); | 189 | flags); |
146 | setFont_LabelWidget(btn, d->buttonFont); | 190 | setFont_LabelWidget(btn, deviceType_App() == phone_AppDeviceType && d->side == right_SidebarSide |
191 | ? defaultBig_FontId | ||
192 | : d->buttonFont); | ||
147 | checkIcon_LabelWidget(btn); | 193 | checkIcon_LabelWidget(btn); |
148 | return btn; | 194 | return btn; |
149 | } | 195 | } |
@@ -207,6 +253,16 @@ static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) { | |||
207 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | 253 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); |
208 | } | 254 | } |
209 | 255 | ||
256 | static iBool isBookmarkFolded_SidebarWidget_(const iSidebarWidget *d, const iBookmark *bm) { | ||
257 | while (bm->parentId) { | ||
258 | if (contains_IntSet(d->closedFolders, bm->parentId)) { | ||
259 | return iTrue; | ||
260 | } | ||
261 | bm = get_Bookmarks(bookmarks_App(), bm->parentId); | ||
262 | } | ||
263 | return iFalse; | ||
264 | } | ||
265 | |||
210 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { | 266 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { |
211 | clear_ListWidget(d->list); | 267 | clear_ListWidget(d->list); |
212 | releaseChildren_Widget(d->blank); | 268 | releaseChildren_Widget(d->blank); |
@@ -328,12 +384,24 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
328 | iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption)); | 384 | iRegExp *homeTag = iClob(new_RegExp("\\b" homepage_BookmarkTag "\\b", caseSensitive_RegExpOption)); |
329 | iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption)); | 385 | iRegExp *subTag = iClob(new_RegExp("\\b" subscribed_BookmarkTag "\\b", caseSensitive_RegExpOption)); |
330 | iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); | 386 | iRegExp *remoteSourceTag = iClob(new_RegExp("\\b" remoteSource_BookmarkTag "\\b", caseSensitive_RegExpOption)); |
387 | iRegExp *remoteTag = iClob(new_RegExp("\\b" remote_BookmarkTag "\\b", caseSensitive_RegExpOption)); | ||
331 | iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); | 388 | iRegExp *linkSplitTag = iClob(new_RegExp("\\b" linkSplit_BookmarkTag "\\b", caseSensitive_RegExpOption)); |
332 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTitle_Bookmark_, NULL, NULL)) { | 389 | iConstForEach(PtrArray, i, list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, NULL, NULL)) { |
333 | const iBookmark *bm = i.ptr; | 390 | const iBookmark *bm = i.ptr; |
391 | if (isBookmarkFolded_SidebarWidget_(d, bm)) { | ||
392 | continue; /* inside a closed folder */ | ||
393 | } | ||
334 | iSidebarItem *item = new_SidebarItem(); | 394 | iSidebarItem *item = new_SidebarItem(); |
395 | item->listItem.isDraggable = iTrue; | ||
396 | item->isBold = item->listItem.isDropTarget = isFolder_Bookmark(bm); | ||
335 | item->id = id_Bookmark(bm); | 397 | item->id = id_Bookmark(bm); |
336 | item->icon = bm->icon; | 398 | item->indent = depth_Bookmark(bm); |
399 | if (isFolder_Bookmark(bm)) { | ||
400 | item->icon = contains_IntSet(d->closedFolders, item->id) ? 0x27e9 : 0xfe40; | ||
401 | } | ||
402 | else { | ||
403 | item->icon = bm->icon; | ||
404 | } | ||
337 | set_String(&item->url, &bm->url); | 405 | set_String(&item->url, &bm->url); |
338 | set_String(&item->label, &bm->title); | 406 | set_String(&item->label, &bm->title); |
339 | /* Icons for special tags. */ { | 407 | /* Icons for special tags. */ { |
@@ -347,6 +415,10 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
347 | appendChar_String(&item->meta, 0x1f3e0); | 415 | appendChar_String(&item->meta, 0x1f3e0); |
348 | } | 416 | } |
349 | init_RegExpMatch(&m); | 417 | init_RegExpMatch(&m); |
418 | if (matchString_RegExp(remoteTag, &bm->tags, &m)) { | ||
419 | item->listItem.isDraggable = iFalse; | ||
420 | } | ||
421 | init_RegExpMatch(&m); | ||
350 | if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) { | 422 | if (matchString_RegExp(remoteSourceTag, &bm->tags, &m)) { |
351 | appendChar_String(&item->meta, 0x2913); | 423 | appendChar_String(&item->meta, 0x2913); |
352 | item->isBold = iTrue; | 424 | item->isBold = iTrue; |
@@ -374,8 +446,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
374 | { "---", 0, 0, NULL }, | 446 | { "---", 0, 0, NULL }, |
375 | { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, | 447 | { delete_Icon " " uiTextCaution_ColorEscape "${bookmark.delete}", 0, 0, "bookmark.delete" }, |
376 | { "---", 0, 0, NULL }, | 448 | { "---", 0, 0, NULL }, |
377 | { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" } }, | 449 | { add_Icon " ${menu.newfolder}", 0, 0, "bookmark.addfolder" }, |
378 | 14); | 450 | { reload_Icon " ${bookmarks.reload}", 0, 0, "bookmarks.reload.remote" }, |
451 | { "---", 0, 0, NULL }, | ||
452 | { upDownArrow_Icon " ${menu.sort.alpha}", 0, 0, "bookmark.sortfolder" } }, | ||
453 | 17); | ||
379 | break; | 454 | break; |
380 | } | 455 | } |
381 | case history_SidebarMode: { | 456 | case history_SidebarMode: { |
@@ -550,6 +625,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
550 | #endif | 625 | #endif |
551 | arrange_Widget(d->actions); | 626 | arrange_Widget(d->actions); |
552 | arrange_Widget(as_Widget(d)); | 627 | arrange_Widget(as_Widget(d)); |
628 | updateMouseHover_ListWidget(d->list); | ||
553 | } | 629 | } |
554 | 630 | ||
555 | static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { | 631 | static void updateItemHeight_SidebarWidget_(iSidebarWidget *d) { |
@@ -579,6 +655,11 @@ iBool setMode_SidebarWidget(iSidebarWidget *d, enum iSidebarMode mode) { | |||
579 | return iTrue; | 655 | return iTrue; |
580 | } | 656 | } |
581 | 657 | ||
658 | void setClosedFolders_SidebarWidget(iSidebarWidget *d, const iIntSet *closedFolders) { | ||
659 | delete_IntSet(d->closedFolders); | ||
660 | d->closedFolders = copy_IntSet(closedFolders); | ||
661 | } | ||
662 | |||
582 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { | 663 | enum iSidebarMode mode_SidebarWidget(const iSidebarWidget *d) { |
583 | return d ? d->mode : 0; | 664 | return d ? d->mode : 0; |
584 | } | 665 | } |
@@ -587,6 +668,10 @@ float width_SidebarWidget(const iSidebarWidget *d) { | |||
587 | return d ? d->widthAsGaps : 0; | 668 | return d ? d->widthAsGaps : 0; |
588 | } | 669 | } |
589 | 670 | ||
671 | const iIntSet *closedFolders_SidebarWidget(const iSidebarWidget *d) { | ||
672 | return d->closedFolders; | ||
673 | } | ||
674 | |||
590 | static const char *normalModeLabels_[max_SidebarMode] = { | 675 | static const char *normalModeLabels_[max_SidebarMode] = { |
591 | book_Icon " ${sidebar.bookmarks}", | 676 | book_Icon " ${sidebar.bookmarks}", |
592 | star_Icon " ${sidebar.feeds}", | 677 | star_Icon " ${sidebar.feeds}", |
@@ -641,17 +726,17 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
641 | d->mode = -1; | 726 | d->mode = -1; |
642 | d->feedsMode = all_FeedsMode; | 727 | d->feedsMode = all_FeedsMode; |
643 | d->numUnreadEntries = 0; | 728 | d->numUnreadEntries = 0; |
644 | d->buttonFont = uiLabel_FontId; | 729 | d->buttonFont = uiLabel_FontId; /* wiil be changed later */ |
645 | d->itemFonts[0] = uiContent_FontId; | 730 | d->itemFonts[0] = uiContent_FontId; |
646 | d->itemFonts[1] = uiContentBold_FontId; | 731 | d->itemFonts[1] = uiContentBold_FontId; |
647 | #if defined (iPlatformAppleMobile) | 732 | #if defined (iPlatformMobile) |
648 | if (deviceType_App() == phone_AppDeviceType) { | 733 | if (deviceType_App() == phone_AppDeviceType) { |
649 | d->itemFonts[0] = defaultBig_FontId; | 734 | d->itemFonts[0] = defaultBig_FontId; |
650 | d->itemFonts[1] = defaultBigBold_FontId; | 735 | d->itemFonts[1] = defaultBigBold_FontId; |
651 | } | 736 | } |
652 | d->widthAsGaps = 73; | 737 | d->widthAsGaps = 73.0f; |
653 | #else | 738 | #else |
654 | d->widthAsGaps = 60; | 739 | d->widthAsGaps = 60.0f; |
655 | #endif | 740 | #endif |
656 | setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue); | 741 | setFlags_Widget(w, fixedWidth_WidgetFlag, iTrue); |
657 | iWidget *vdiv = makeVDiv_Widget(); | 742 | iWidget *vdiv = makeVDiv_Widget(); |
@@ -660,11 +745,13 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
660 | d->resizer = NULL; | 745 | d->resizer = NULL; |
661 | d->list = NULL; | 746 | d->list = NULL; |
662 | d->actions = NULL; | 747 | d->actions = NULL; |
748 | d->closedFolders = new_IntSet(); | ||
663 | /* On a phone, the right sidebar is used exclusively for Identities. */ | 749 | /* On a phone, the right sidebar is used exclusively for Identities. */ |
664 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; | 750 | const iBool isPhone = deviceType_App() == phone_AppDeviceType; |
665 | if (!isPhone || d->side == left_SidebarSide) { | 751 | if (!isPhone || d->side == left_SidebarSide) { |
666 | iWidget *buttons = new_Widget(); | 752 | iWidget *buttons = new_Widget(); |
667 | setId_Widget(buttons, "buttons"); | 753 | setId_Widget(buttons, "buttons"); |
754 | setDrawBufferEnabled_Widget(buttons, iTrue); | ||
668 | for (int i = 0; i < max_SidebarMode; i++) { | 755 | for (int i = 0; i < max_SidebarMode; i++) { |
669 | if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) { | 756 | if (deviceType_App() == phone_AppDeviceType && i == identities_SidebarMode) { |
670 | continue; | 757 | continue; |
@@ -742,16 +829,21 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
742 | 829 | ||
743 | void deinit_SidebarWidget(iSidebarWidget *d) { | 830 | void deinit_SidebarWidget(iSidebarWidget *d) { |
744 | deinit_String(&d->cmdPrefix); | 831 | deinit_String(&d->cmdPrefix); |
832 | delete_IntSet(d->closedFolders); | ||
745 | } | 833 | } |
746 | 834 | ||
747 | void setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { | 835 | iBool setButtonFont_SidebarWidget(iSidebarWidget *d, int font) { |
748 | d->buttonFont = font; | 836 | if (d->buttonFont != font) { |
749 | for (int i = 0; i < max_SidebarMode; i++) { | 837 | d->buttonFont = font; |
750 | if (d->modeButtons[i]) { | 838 | for (int i = 0; i < max_SidebarMode; i++) { |
751 | setFont_LabelWidget(d->modeButtons[i], font); | 839 | if (d->modeButtons[i]) { |
840 | setFont_LabelWidget(d->modeButtons[i], font); | ||
841 | } | ||
752 | } | 842 | } |
843 | updateMetrics_SidebarWidget_(d); | ||
844 | return iTrue; | ||
753 | } | 845 | } |
754 | updateMetrics_SidebarWidget_(d); | 846 | return iFalse; |
755 | } | 847 | } |
756 | 848 | ||
757 | static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { | 849 | static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget *d) { |
@@ -785,6 +877,17 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si | |||
785 | break; | 877 | break; |
786 | } | 878 | } |
787 | case bookmarks_SidebarMode: | 879 | case bookmarks_SidebarMode: |
880 | if (isEmpty_String(&item->url)) /* a folder */ { | ||
881 | if (contains_IntSet(d->closedFolders, item->id)) { | ||
882 | remove_IntSet(d->closedFolders, item->id); | ||
883 | } | ||
884 | else { | ||
885 | insert_IntSet(d->closedFolders, item->id); | ||
886 | } | ||
887 | updateItems_SidebarWidget_(d); | ||
888 | break; | ||
889 | } | ||
890 | /* fall through */ | ||
788 | case history_SidebarMode: { | 891 | case history_SidebarMode: { |
789 | if (!isEmpty_String(&item->url)) { | 892 | if (!isEmpty_String(&item->url)) { |
790 | postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", | 893 | postCommandf_Root(get_Root(), "open fromsidebar:1 newtab:%d url:%s", |
@@ -826,8 +929,10 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
826 | if (d->itemFonts[0] != fonts[0]) { | 929 | if (d->itemFonts[0] != fonts[0]) { |
827 | d->itemFonts[0] = fonts[0]; | 930 | d->itemFonts[0] = fonts[0]; |
828 | d->itemFonts[1] = fonts[1]; | 931 | d->itemFonts[1] = fonts[1]; |
829 | updateMetrics_SidebarWidget_(d); | 932 | // updateMetrics_SidebarWidget_(d); |
933 | updateItemHeight_SidebarWidget_(d); | ||
830 | } | 934 | } |
935 | setButtonFont_SidebarWidget(d, isPortrait_App() ? defaultBig_FontId : uiLabel_FontId); | ||
831 | } | 936 | } |
832 | const iBool isTight = | 937 | const iBool isTight = |
833 | (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); | 938 | (width_Rect(bounds_Widget(as_Widget(d->modeButtons[0]))) < d->maxButtonLabelWidth); |
@@ -857,17 +962,15 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
857 | void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | 962 | void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { |
858 | iWidget *w = as_Widget(d); | 963 | iWidget *w = as_Widget(d); |
859 | const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType; | 964 | const iBool isFixedWidth = deviceType_App() == phone_AppDeviceType; |
860 | int width = widthAsGaps * gap_UI; | 965 | int width = widthAsGaps * gap_UI; /* in pixels */ |
861 | if (!isFixedWidth) { | 966 | if (!isFixedWidth) { |
862 | /* Even less space if the other sidebar is visible, too. */ | 967 | /* Even less space if the other sidebar is visible, too. */ |
863 | const int otherWidth = | 968 | const iWidget *other = findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar"); |
864 | width_Widget(findWidget_App(d->side == left_SidebarSide ? "sidebar2" : "sidebar")); | 969 | const int otherWidth = isVisible_Widget(other) ? width_Widget(other) : 0; |
865 | width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); | 970 | width = iClamp(width, 30 * gap_UI, size_Root(w->root).x - 50 * gap_UI - otherWidth); |
866 | } | 971 | } |
867 | d->widthAsGaps = (float) width / (float) gap_UI; | 972 | d->widthAsGaps = (float) width / (float) gap_UI; |
868 | if (isVisible_Widget(w)) { | 973 | w->rect.size.x = width; |
869 | w->rect.size.x = width; | ||
870 | } | ||
871 | arrange_Widget(findWidget_Root("stack")); | 974 | arrange_Widget(findWidget_Root("stack")); |
872 | checkModeButtonLayout_SidebarWidget_(d); | 975 | checkModeButtonLayout_SidebarWidget_(d); |
873 | updateItemHeight_SidebarWidget_(d); | 976 | updateItemHeight_SidebarWidget_(d); |
@@ -934,6 +1037,7 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
934 | if (wasChanged) { | 1037 | if (wasChanged) { |
935 | postCommandf_App("%s.mode.changed arg:%d", cstr_String(id_Widget(w)), d->mode); | 1038 | postCommandf_App("%s.mode.changed arg:%d", cstr_String(id_Widget(w)), d->mode); |
936 | } | 1039 | } |
1040 | refresh_Widget(findChild_Widget(w, "buttons")); | ||
937 | return iTrue; | 1041 | return iTrue; |
938 | } | 1042 | } |
939 | else if (equal_Command(cmd, "toggle")) { | 1043 | else if (equal_Command(cmd, "toggle")) { |
@@ -989,6 +1093,43 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
989 | return iFalse; | 1093 | return iFalse; |
990 | } | 1094 | } |
991 | 1095 | ||
1096 | static void bookmarkMoved_SidebarWidget_(iSidebarWidget *d, size_t index, size_t beforeIndex) { | ||
1097 | const iSidebarItem *movingItem = item_ListWidget(d->list, index); | ||
1098 | const iBool isLast = (beforeIndex == numItems_ListWidget(d->list)); | ||
1099 | const iSidebarItem *dstItem = item_ListWidget(d->list, | ||
1100 | isLast ? numItems_ListWidget(d->list) - 1 | ||
1101 | : beforeIndex); | ||
1102 | const iBookmark *dst = get_Bookmarks(bookmarks_App(), dstItem->id); | ||
1103 | if (hasParent_Bookmark(dst, movingItem->id)) { | ||
1104 | return; | ||
1105 | } | ||
1106 | reorder_Bookmarks(bookmarks_App(), movingItem->id, dst->order + (isLast ? 1 : 0)); | ||
1107 | get_Bookmarks(bookmarks_App(), movingItem->id)->parentId = dst->parentId; | ||
1108 | updateItems_SidebarWidget_(d); | ||
1109 | /* Don't confuse the user: keep the dragged item in hover state. */ | ||
1110 | setHoverItem_ListWidget(d->list, index < beforeIndex ? beforeIndex - 1 : beforeIndex); | ||
1111 | postCommandf_App("bookmarks.changed nosidebar:%p", d); /* skip this sidebar since we updated already */ | ||
1112 | } | ||
1113 | |||
1114 | static void bookmarkMovedOntoFolder_SidebarWidget_(iSidebarWidget *d, size_t index, | ||
1115 | size_t folderIndex) { | ||
1116 | const iSidebarItem *movingItem = item_ListWidget(d->list, index); | ||
1117 | const iSidebarItem *dstItem = item_ListWidget(d->list, folderIndex); | ||
1118 | iBookmark *bm = get_Bookmarks(bookmarks_App(), movingItem->id); | ||
1119 | bm->parentId = dstItem->id; | ||
1120 | postCommand_App("bookmarks.changed"); | ||
1121 | } | ||
1122 | |||
1123 | static size_t numBookmarks_(const iPtrArray *bmList) { | ||
1124 | size_t num = 0; | ||
1125 | iConstForEach(PtrArray, i, bmList) { | ||
1126 | if (!isFolder_Bookmark(i.ptr) && !hasTag_Bookmark(i.ptr, remote_BookmarkTag)) { | ||
1127 | num++; | ||
1128 | } | ||
1129 | } | ||
1130 | return num; | ||
1131 | } | ||
1132 | |||
992 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { | 1133 | static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) { |
993 | iWidget *w = as_Widget(d); | 1134 | iWidget *w = as_Widget(d); |
994 | /* Handle commands. */ | 1135 | /* Handle commands. */ |
@@ -1040,7 +1181,9 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1040 | } | 1181 | } |
1041 | else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode || | 1182 | else if (equal_Command(cmd, "bookmarks.changed") && (d->mode == bookmarks_SidebarMode || |
1042 | d->mode == feeds_SidebarMode)) { | 1183 | d->mode == feeds_SidebarMode)) { |
1043 | updateItems_SidebarWidget_(d); | 1184 | if (pointerLabel_Command(cmd, "nosidebar") != d) { |
1185 | updateItems_SidebarWidget_(d); | ||
1186 | } | ||
1044 | } | 1187 | } |
1045 | else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { | 1188 | else if (equal_Command(cmd, "idents.changed") && d->mode == identities_SidebarMode) { |
1046 | updateItems_SidebarWidget_(d); | 1189 | updateItems_SidebarWidget_(d); |
@@ -1096,6 +1239,21 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1096 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); | 1239 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); |
1097 | return iTrue; | 1240 | return iTrue; |
1098 | } | 1241 | } |
1242 | else if (isCommand_Widget(w, ev, "list.dragged")) { | ||
1243 | iAssert(d->mode == bookmarks_SidebarMode); | ||
1244 | if (hasLabel_Command(cmd, "before")) { | ||
1245 | bookmarkMoved_SidebarWidget_(d, | ||
1246 | argU32Label_Command(cmd, "arg"), | ||
1247 | argU32Label_Command(cmd, "before")); | ||
1248 | } | ||
1249 | else { | ||
1250 | /* Dragged onto a folder. */ | ||
1251 | bookmarkMovedOntoFolder_SidebarWidget_(d, | ||
1252 | argU32Label_Command(cmd, "arg"), | ||
1253 | argU32Label_Command(cmd, "onto")); | ||
1254 | } | ||
1255 | return iTrue; | ||
1256 | } | ||
1099 | else if (isCommand_Widget(w, ev, "menu.closed")) { | 1257 | else if (isCommand_Widget(w, ev, "menu.closed")) { |
1100 | // invalidateItem_ListWidget(d->list, d->contextIndex); | 1258 | // invalidateItem_ListWidget(d->list, d->contextIndex); |
1101 | } | 1259 | } |
@@ -1128,15 +1286,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1128 | setText_InputWidget(findChild_Widget(dlg, "bmed.icon"), | 1286 | setText_InputWidget(findChild_Widget(dlg, "bmed.icon"), |
1129 | collect_String(newUnicodeN_String(&bm->icon, 1))); | 1287 | collect_String(newUnicodeN_String(&bm->icon, 1))); |
1130 | } | 1288 | } |
1131 | setFlags_Widget(findChild_Widget(dlg, "bmed.tag.home"), | 1289 | setToggle_Widget(findChild_Widget(dlg, "bmed.tag.home"), |
1132 | selected_WidgetFlag, | 1290 | hasTag_Bookmark(bm, homepage_BookmarkTag)); |
1133 | hasTag_Bookmark(bm, homepage_BookmarkTag)); | 1291 | setToggle_Widget(findChild_Widget(dlg, "bmed.tag.remote"), |
1134 | setFlags_Widget(findChild_Widget(dlg, "bmed.tag.remote"), | 1292 | hasTag_Bookmark(bm, remoteSource_BookmarkTag)); |
1135 | selected_WidgetFlag, | 1293 | setToggle_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"), |
1136 | hasTag_Bookmark(bm, remoteSource_BookmarkTag)); | 1294 | hasTag_Bookmark(bm, linkSplit_BookmarkTag)); |
1137 | setFlags_Widget(findChild_Widget(dlg, "bmed.tag.linksplit"), | ||
1138 | selected_WidgetFlag, | ||
1139 | hasTag_Bookmark(bm, linkSplit_BookmarkTag)); | ||
1140 | setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); | 1295 | setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); |
1141 | setFocus_Widget(findChild_Widget(dlg, "bmed.title")); | 1296 | setFocus_Widget(findChild_Widget(dlg, "bmed.title")); |
1142 | } | 1297 | } |
@@ -1177,9 +1332,60 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1177 | } | 1332 | } |
1178 | else if (isCommand_Widget(w, ev, "bookmark.delete")) { | 1333 | else if (isCommand_Widget(w, ev, "bookmark.delete")) { |
1179 | const iSidebarItem *item = d->contextItem; | 1334 | const iSidebarItem *item = d->contextItem; |
1180 | if (d->mode == bookmarks_SidebarMode && item && remove_Bookmarks(bookmarks_App(), item->id)) { | 1335 | if (d->mode == bookmarks_SidebarMode && item) { |
1181 | removeEntries_Feeds(item->id); | 1336 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
1182 | postCommand_App("bookmarks.changed"); | 1337 | if (isFolder_Bookmark(bm)) { |
1338 | const iPtrArray *list = list_Bookmarks(bookmarks_App(), NULL, | ||
1339 | filterInsideFolder_Bookmark, bm); | ||
1340 | /* Folder deletion requires confirmation because folders can contain | ||
1341 | any number of bookmarks and other folders. */ | ||
1342 | if (argLabel_Command(cmd, "confirmed") || isEmpty_PtrArray(list)) { | ||
1343 | iConstForEach(PtrArray, i, list) { | ||
1344 | removeEntries_Feeds(id_Bookmark(i.ptr)); | ||
1345 | } | ||
1346 | remove_Bookmarks(bookmarks_App(), item->id); | ||
1347 | postCommand_App("bookmarks.changed"); | ||
1348 | } | ||
1349 | else { | ||
1350 | const size_t numBookmarks = numBookmarks_(list); | ||
1351 | makeQuestion_Widget(uiHeading_ColorEscape "${heading.confirm.bookmarks.delete}", | ||
1352 | formatCStrs_Lang("dlg.confirm.bookmarks.delete.n", numBookmarks), | ||
1353 | (iMenuItem[]){ | ||
1354 | { "${cancel}" }, | ||
1355 | { format_CStr(uiTextCaution_ColorEscape "%s", | ||
1356 | formatCStrs_Lang("dlg.bookmarks.delete.n", numBookmarks)), | ||
1357 | 0, 0, format_CStr("!bookmark.delete confirmed:1 ptr:%p", d) }, | ||
1358 | }, 2); | ||
1359 | } | ||
1360 | } | ||
1361 | else { | ||
1362 | /* TODO: Move it to a Trash folder? */ | ||
1363 | if (remove_Bookmarks(bookmarks_App(), item->id)) { | ||
1364 | removeEntries_Feeds(item->id); | ||
1365 | postCommand_App("bookmarks.changed"); | ||
1366 | } | ||
1367 | } | ||
1368 | } | ||
1369 | return iTrue; | ||
1370 | } | ||
1371 | else if (isCommand_Widget(w, ev, "bookmark.addfolder")) { | ||
1372 | const iSidebarItem *item = d->contextItem; | ||
1373 | if (d->mode == bookmarks_SidebarMode) { | ||
1374 | postCommandf_App("bookmarks.addfolder parent:%zu", | ||
1375 | !item ? 0 | ||
1376 | : item->listItem.isDropTarget | ||
1377 | ? item->id | ||
1378 | : get_Bookmarks(bookmarks_App(), item->id)->parentId); | ||
1379 | } | ||
1380 | return iTrue; | ||
1381 | } | ||
1382 | else if (isCommand_Widget(w, ev, "bookmark.sortfolder")) { | ||
1383 | const iSidebarItem *item = d->contextItem; | ||
1384 | if (d->mode == bookmarks_SidebarMode && item) { | ||
1385 | postCommandf_App("bookmarks.sort arg:%zu", | ||
1386 | item->listItem.isDropTarget | ||
1387 | ? item->id | ||
1388 | : get_Bookmarks(bookmarks_App(), item->id)->parentId); | ||
1183 | } | 1389 | } |
1184 | return iTrue; | 1390 | return iTrue; |
1185 | } | 1391 | } |
@@ -1449,40 +1655,29 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1449 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { | 1655 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { |
1450 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), d->contextItem->id); | 1656 | const iBookmark *bm = get_Bookmarks(bookmarks_App(), d->contextItem->id); |
1451 | if (bm) { | 1657 | if (bm) { |
1452 | iLabelWidget *menuItem = findMenuItem_Widget(d->menu, | 1658 | setMenuItemLabel_Widget(d->menu, |
1453 | "bookmark.tag tag:homepage"); | 1659 | "bookmark.tag tag:homepage", |
1454 | if (menuItem) { | 1660 | hasTag_Bookmark(bm, homepage_BookmarkTag) |
1455 | setTextCStr_LabelWidget(menuItem, | 1661 | ? home_Icon " ${bookmark.untag.home}" |
1456 | hasTag_Bookmark(bm, homepage_BookmarkTag) | 1662 | : home_Icon " ${bookmark.tag.home}"); |
1457 | ? home_Icon " ${bookmark.untag.home}" | 1663 | setMenuItemLabel_Widget(d->menu, |
1458 | : home_Icon " ${bookmark.tag.home}"); | 1664 | "bookmark.tag tag:subscribed", |
1459 | checkIcon_LabelWidget(menuItem); | 1665 | hasTag_Bookmark(bm, subscribed_BookmarkTag) |
1460 | } | 1666 | ? star_Icon " ${bookmark.untag.sub}" |
1461 | menuItem = findMenuItem_Widget(d->menu, "bookmark.tag tag:subscribed"); | 1667 | : star_Icon " ${bookmark.tag.sub}"); |
1462 | if (menuItem) { | 1668 | setMenuItemLabel_Widget(d->menu, |
1463 | setTextCStr_LabelWidget(menuItem, | 1669 | "bookmark.tag tag:remotesource", |
1464 | hasTag_Bookmark(bm, subscribed_BookmarkTag) | 1670 | hasTag_Bookmark(bm, remoteSource_BookmarkTag) |
1465 | ? star_Icon " ${bookmark.untag.sub}" | 1671 | ? downArrowBar_Icon " ${bookmark.untag.remote}" |
1466 | : star_Icon " ${bookmark.tag.sub}"); | 1672 | : downArrowBar_Icon " ${bookmark.tag.remote}"); |
1467 | checkIcon_LabelWidget(menuItem); | ||
1468 | } | ||
1469 | menuItem = findMenuItem_Widget(d->menu, "bookmark.tag tag:remotesource"); | ||
1470 | if (menuItem) { | ||
1471 | setTextCStr_LabelWidget(menuItem, | ||
1472 | hasTag_Bookmark(bm, remoteSource_BookmarkTag) | ||
1473 | ? downArrowBar_Icon " ${bookmark.untag.remote}" | ||
1474 | : downArrowBar_Icon " ${bookmark.tag.remote}"); | ||
1475 | checkIcon_LabelWidget(menuItem); | ||
1476 | } | ||
1477 | } | 1673 | } |
1478 | } | 1674 | } |
1479 | else if (d->mode == feeds_SidebarMode && d->contextItem) { | 1675 | else if (d->mode == feeds_SidebarMode && d->contextItem) { |
1480 | iLabelWidget *menuItem = findMenuItem_Widget(d->menu, "feed.entry.toggleread"); | ||
1481 | const iBool isRead = d->contextItem->indent == 0; | 1676 | const iBool isRead = d->contextItem->indent == 0; |
1482 | setTextCStr_LabelWidget(menuItem, | 1677 | setMenuItemLabel_Widget(d->menu, |
1678 | "feed.entry.toggleread", | ||
1483 | isRead ? circle_Icon " ${feeds.entry.markunread}" | 1679 | isRead ? circle_Icon " ${feeds.entry.markunread}" |
1484 | : circleWhite_Icon " ${feeds.entry.markread}"); | 1680 | : circleWhite_Icon " ${feeds.entry.markread}"); |
1485 | checkIcon_LabelWidget(menuItem); | ||
1486 | } | 1681 | } |
1487 | else if (d->mode == identities_SidebarMode) { | 1682 | else if (d->mode == identities_SidebarMode) { |
1488 | const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d); | 1683 | const iGmIdentity *ident = constHoverIdentity_SidebarWidget_(d); |
@@ -1555,7 +1750,7 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1555 | const iRect bounds = bounds_Widget(w); | 1750 | const iRect bounds = bounds_Widget(w); |
1556 | iPaint p; | 1751 | iPaint p; |
1557 | init_Paint(&p); | 1752 | init_Paint(&p); |
1558 | if (deviceType_App() != phone_AppDeviceType) { | 1753 | if (!isPortraitPhone_App()) { /* this would erase page contents during transition on the phone */ |
1559 | if (flags_Widget(w) & visualOffset_WidgetFlag && | 1754 | if (flags_Widget(w) & visualOffset_WidgetFlag && |
1560 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { | 1755 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { |
1561 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | 1756 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); |
@@ -1576,12 +1771,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1576 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), | 1771 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), |
1577 | &Class_SidebarWidget); | 1772 | &Class_SidebarWidget); |
1578 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); | 1773 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); |
1579 | const iBool isPressing = isMouseDown_ListWidget(list); | 1774 | const iBool isDragging = constDragItem_ListWidget(list) == d; |
1775 | const iBool isPressing = isMouseDown_ListWidget(list) && !isDragging; | ||
1580 | const iBool isHover = | 1776 | const iBool isHover = |
1581 | (!isMenuVisible && | 1777 | (!isMenuVisible && |
1582 | isHover_Widget(constAs_Widget(list)) && | 1778 | isHover_Widget(constAs_Widget(list)) && |
1583 | constHoverItem_ListWidget(list) == d) || | 1779 | constHoverItem_ListWidget(list) == d) || |
1584 | (isMenuVisible && sidebar->contextItem == d); | 1780 | (isMenuVisible && sidebar->contextItem == d) || |
1781 | isDragging; | ||
1585 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); | 1782 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); |
1586 | #if defined (iPlatformApple) | 1783 | #if defined (iPlatformApple) |
1587 | const int blankWidth = 0; | 1784 | const int blankWidth = 0; |
@@ -1605,7 +1802,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1605 | fillRect_Paint(p, itemRect, bg); | 1802 | fillRect_Paint(p, itemRect, bg); |
1606 | } | 1803 | } |
1607 | else if (sidebar->mode == bookmarks_SidebarMode) { | 1804 | else if (sidebar->mode == bookmarks_SidebarMode) { |
1608 | if (d->icon == 0x2913) { /* TODO: Remote icon; meaning: is this in a folder? */ | 1805 | if (d->indent) /* remote icon */ { |
1609 | bg = uiBackgroundFolder_ColorId; | 1806 | bg = uiBackgroundFolder_ColorId; |
1610 | fillRect_Paint(p, itemRect, bg); | 1807 | fillRect_Paint(p, itemRect, bg); |
1611 | } | 1808 | } |
@@ -1707,11 +1904,13 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1707 | } | 1904 | } |
1708 | else if (sidebar->mode == bookmarks_SidebarMode) { | 1905 | else if (sidebar->mode == bookmarks_SidebarMode) { |
1709 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | 1906 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) |
1710 | : uiText_ColorId; | 1907 | : d->listItem.isDropTarget ? uiHeading_ColorId : uiText_ColorId; |
1908 | /* The icon. */ | ||
1711 | iString str; | 1909 | iString str; |
1712 | init_String(&str); | 1910 | init_String(&str); |
1713 | appendChar_String(&str, d->icon ? d->icon : 0x1f588); | 1911 | appendChar_String(&str, d->icon ? d->icon : 0x1f588); |
1714 | const iRect iconArea = { addX_I2(pos, gap_UI), | 1912 | const int leftIndent = d->indent * gap_UI * 4; |
1913 | const iRect iconArea = { addX_I2(pos, gap_UI + leftIndent), | ||
1715 | init_I2(1.75f * lineHeight_Text(font), itemHeight) }; | 1914 | init_I2(1.75f * lineHeight_Text(font), itemHeight) }; |
1716 | drawCentered_Text(font, | 1915 | drawCentered_Text(font, |
1717 | iconArea, | 1916 | iconArea, |
@@ -1732,12 +1931,14 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1732 | metaIconWidth | 1931 | metaIconWidth |
1733 | - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)), | 1932 | - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)), |
1734 | textPos.y); | 1933 | textPos.y); |
1735 | fillRect_Paint(p, | 1934 | if (!isDragging) { |
1736 | init_Rect(metaPos.x, | 1935 | fillRect_Paint(p, |
1737 | top_Rect(itemRect), | 1936 | init_Rect(metaPos.x, |
1738 | right_Rect(itemRect) - metaPos.x, | 1937 | top_Rect(itemRect), |
1739 | height_Rect(itemRect)), | 1938 | right_Rect(itemRect) - metaPos.x, |
1740 | bg); | 1939 | height_Rect(itemRect)), |
1940 | bg); | ||
1941 | } | ||
1741 | iInt2 mpos = metaPos; | 1942 | iInt2 mpos = metaPos; |
1742 | iStringConstIterator iter; | 1943 | iStringConstIterator iter; |
1743 | init_StringConstIterator(&iter, &d->meta); | 1944 | init_StringConstIterator(&iter, &d->meta); |
diff --git a/src/ui/sidebarwidget.h b/src/ui/sidebarwidget.h index 130242ab..638a1f2f 100644 --- a/src/ui/sidebarwidget.h +++ b/src/ui/sidebarwidget.h | |||
@@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
24 | 24 | ||
25 | #include "widget.h" | 25 | #include "widget.h" |
26 | 26 | ||
27 | #include <the_Foundation/intset.h> | ||
28 | |||
27 | enum iSidebarMode { | 29 | enum iSidebarMode { |
28 | bookmarks_SidebarMode, | 30 | bookmarks_SidebarMode, |
29 | feeds_SidebarMode, | 31 | feeds_SidebarMode, |
@@ -49,8 +51,10 @@ iDeclareWidgetClass(SidebarWidget) | |||
49 | iDeclareObjectConstructionArgs(SidebarWidget, enum iSidebarSide side) | 51 | iDeclareObjectConstructionArgs(SidebarWidget, enum iSidebarSide side) |
50 | 52 | ||
51 | iBool setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode); | 53 | iBool setMode_SidebarWidget (iSidebarWidget *, enum iSidebarMode mode); |
52 | void setButtonFont_SidebarWidget (iSidebarWidget *, int font); | 54 | void setWidth_SidebarWidget (iSidebarWidget *, float widthAsGaps); |
55 | iBool setButtonFont_SidebarWidget (iSidebarWidget *, int font); | ||
56 | void setClosedFolders_SidebarWidget (iSidebarWidget *, const iIntSet *closedFolders); | ||
53 | 57 | ||
54 | enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *); | 58 | enum iSidebarMode mode_SidebarWidget (const iSidebarWidget *); |
55 | float width_SidebarWidget (const iSidebarWidget *); | 59 | float width_SidebarWidget (const iSidebarWidget *); |
56 | void setWidth_SidebarWidget (iSidebarWidget *, float widthAsGaps); | 60 | const iIntSet * closedFolders_SidebarWidget (const iSidebarWidget *); |
diff --git a/src/ui/text.c b/src/ui/text.c index 006a4d0b..de8f10a4 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -25,6 +25,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
25 | #include "metrics.h" | 25 | #include "metrics.h" |
26 | #include "embedded.h" | 26 | #include "embedded.h" |
27 | #include "window.h" | 27 | #include "window.h" |
28 | #include "paint.h" | ||
28 | #include "app.h" | 29 | #include "app.h" |
29 | 30 | ||
30 | #define STB_TRUETYPE_IMPLEMENTATION | 31 | #define STB_TRUETYPE_IMPLEMENTATION |
@@ -120,6 +121,8 @@ iDefineTypeConstructionArgs(Glyph, (iChar ch), ch) | |||
120 | 121 | ||
121 | /*-----------------------------------------------------------------------------------------------*/ | 122 | /*-----------------------------------------------------------------------------------------------*/ |
122 | 123 | ||
124 | static iGlyph *glyph_Font_(iFont *d, iChar ch); | ||
125 | |||
123 | struct Impl_Font { | 126 | struct Impl_Font { |
124 | iBlock * data; | 127 | iBlock * data; |
125 | enum iTextFont family; | 128 | enum iTextFont family; |
@@ -130,6 +133,7 @@ struct Impl_Font { | |||
130 | int baseline; | 133 | int baseline; |
131 | iHash glyphs; /* key is glyph index in the font */ /* TODO: does not need to be a Hash */ | 134 | iHash glyphs; /* key is glyph index in the font */ /* TODO: does not need to be a Hash */ |
132 | iBool isMonospaced; | 135 | iBool isMonospaced; |
136 | float emAdvance; | ||
133 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ | 137 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ |
134 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ | 138 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ |
135 | #if defined (LAGRANGE_ENABLE_HARFBUZZ) | 139 | #if defined (LAGRANGE_ENABLE_HARFBUZZ) |
@@ -177,20 +181,20 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale, | |||
177 | d->height = height; | 181 | d->height = height; |
178 | iZap(d->font); | 182 | iZap(d->font); |
179 | stbtt_InitFont(&d->font, constData_Block(data), 0); | 183 | stbtt_InitFont(&d->font, constData_Block(data), 0); |
180 | int ascent, descent; | 184 | int ascent, descent, emAdv; |
181 | stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL); | 185 | stbtt_GetFontVMetrics(&d->font, &ascent, &descent, NULL); |
186 | stbtt_GetCodepointHMetrics(&d->font, 'M', &emAdv, NULL); | ||
182 | d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale; | 187 | d->xScale = d->yScale = stbtt_ScaleForPixelHeight(&d->font, height) * scale; |
183 | if (d->isMonospaced) { | 188 | if (d->isMonospaced) { |
184 | /* It is important that monospaced fonts align 1:1 with the pixel grid so that | 189 | /* It is important that monospaced fonts align 1:1 with the pixel grid so that |
185 | box-drawing characters don't have partially occupied edge pixels, leading to seams | 190 | box-drawing characters don't have partially occupied edge pixels, leading to seams |
186 | between adjacent glyphs. */ | 191 | between adjacent glyphs. */ |
187 | int adv; | 192 | const float advance = (float) emAdv * d->xScale; |
188 | stbtt_GetCodepointHMetrics(&d->font, 'M', &adv, NULL); | ||
189 | const float advance = (float) adv * d->xScale; | ||
190 | if (advance > 4) { /* not too tiny */ | 193 | if (advance > 4) { /* not too tiny */ |
191 | d->xScale *= floorf(advance) / advance; | 194 | d->xScale *= floorf(advance) / advance; |
192 | } | 195 | } |
193 | } | 196 | } |
197 | d->emAdvance = emAdv * d->xScale; | ||
194 | d->baseline = ascent * d->yScale; | 198 | d->baseline = ascent * d->yScale; |
195 | d->vertOffset = height * (1.0f - scale) / 2; | 199 | d->vertOffset = height * (1.0f - scale) / 2; |
196 | /* Custom tweaks. */ | 200 | /* Custom tweaks. */ |
@@ -289,7 +293,9 @@ struct Impl_Text { | |||
289 | iRegExp * ansiEscape; | 293 | iRegExp * ansiEscape; |
290 | }; | 294 | }; |
291 | 295 | ||
292 | static iText text_; | 296 | iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render) |
297 | |||
298 | static iText *activeText_; | ||
293 | static iBlock *userFont_; | 299 | static iBlock *userFont_; |
294 | 300 | ||
295 | static void initFonts_Text_(iText *d) { | 301 | static void initFonts_Text_(iText *d) { |
@@ -361,7 +367,7 @@ static void initFonts_Text_(iText *d) { | |||
361 | h12Font = &fontIosevkaTermExtended_Embedded; | 367 | h12Font = &fontIosevkaTermExtended_Embedded; |
362 | h3Font = &fontIosevkaTermExtended_Embedded; | 368 | h3Font = &fontIosevkaTermExtended_Embedded; |
363 | } | 369 | } |
364 | #if defined (iPlatformAppleMobile) | 370 | #if defined (iPlatformMobile) |
365 | const float uiSize = fontSize_UI * 1.1f; | 371 | const float uiSize = fontSize_UI * 1.1f; |
366 | #else | 372 | #else |
367 | const float uiSize = fontSize_UI; | 373 | const float uiSize = fontSize_UI; |
@@ -500,8 +506,7 @@ void loadUserFonts_Text(void) { | |||
500 | } | 506 | } |
501 | } | 507 | } |
502 | 508 | ||
503 | void init_Text(SDL_Renderer *render) { | 509 | void init_Text(iText *d, SDL_Renderer *render) { |
504 | iText *d = &text_; | ||
505 | loadUserFonts_Text(); | 510 | loadUserFonts_Text(); |
506 | d->contentFont = nunito_TextFont; | 511 | d->contentFont = nunito_TextFont; |
507 | d->headingFont = nunito_TextFont; | 512 | d->headingFont = nunito_TextFont; |
@@ -520,8 +525,7 @@ void init_Text(SDL_Renderer *render) { | |||
520 | initFonts_Text_(d); | 525 | initFonts_Text_(d); |
521 | } | 526 | } |
522 | 527 | ||
523 | void deinit_Text(void) { | 528 | void deinit_Text(iText *d) { |
524 | iText *d = &text_; | ||
525 | SDL_FreePalette(d->grayscale); | 529 | SDL_FreePalette(d->grayscale); |
526 | deinitFonts_Text_(d); | 530 | deinitFonts_Text_(d); |
527 | deinitCache_Text_(d); | 531 | deinitCache_Text_(d); |
@@ -529,30 +533,34 @@ void deinit_Text(void) { | |||
529 | iRelease(d->ansiEscape); | 533 | iRelease(d->ansiEscape); |
530 | } | 534 | } |
531 | 535 | ||
536 | void setCurrent_Text(iText *d) { | ||
537 | activeText_ = d; | ||
538 | } | ||
539 | |||
532 | void setOpacity_Text(float opacity) { | 540 | void setOpacity_Text(float opacity) { |
533 | SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); | 541 | SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); |
534 | } | 542 | } |
535 | 543 | ||
536 | void setContentFont_Text(enum iTextFont font) { | 544 | void setContentFont_Text(iText *d, enum iTextFont font) { |
537 | if (text_.contentFont != font) { | 545 | if (d->contentFont != font) { |
538 | text_.contentFont = font; | 546 | d->contentFont = font; |
539 | resetFonts_Text(); | 547 | resetFonts_Text(d); |
540 | } | 548 | } |
541 | } | 549 | } |
542 | 550 | ||
543 | void setHeadingFont_Text(enum iTextFont font) { | 551 | void setHeadingFont_Text(iText *d, enum iTextFont font) { |
544 | if (text_.headingFont != font) { | 552 | if (d->headingFont != font) { |
545 | text_.headingFont = font; | 553 | d->headingFont = font; |
546 | resetFonts_Text(); | 554 | resetFonts_Text(d); |
547 | } | 555 | } |
548 | } | 556 | } |
549 | 557 | ||
550 | void setContentFontSize_Text(float fontSizeFactor) { | 558 | void setContentFontSize_Text(iText *d, float fontSizeFactor) { |
551 | fontSizeFactor *= contentScale_Text_; | 559 | fontSizeFactor *= contentScale_Text_; |
552 | iAssert(fontSizeFactor > 0); | 560 | iAssert(fontSizeFactor > 0); |
553 | if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) { | 561 | if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) { |
554 | text_.contentFontSize = fontSizeFactor; | 562 | d->contentFontSize = fontSizeFactor; |
555 | resetFonts_Text(); | 563 | resetFonts_Text(d); |
556 | } | 564 | } |
557 | } | 565 | } |
558 | 566 | ||
@@ -564,8 +572,7 @@ static void resetCache_Text_(iText *d) { | |||
564 | initCache_Text_(d); | 572 | initCache_Text_(d); |
565 | } | 573 | } |
566 | 574 | ||
567 | void resetFonts_Text(void) { | 575 | void resetFonts_Text(iText *d) { |
568 | iText *d = &text_; | ||
569 | deinitFonts_Text_(d); | 576 | deinitFonts_Text_(d); |
570 | deinitCache_Text_(d); | 577 | deinitCache_Text_(d); |
571 | initCache_Text_(d); | 578 | initCache_Text_(d); |
@@ -573,7 +580,7 @@ void resetFonts_Text(void) { | |||
573 | } | 580 | } |
574 | 581 | ||
575 | iLocalDef iFont *font_Text_(enum iFontId id) { | 582 | iLocalDef iFont *font_Text_(enum iFontId id) { |
576 | return &text_.fonts[id & mask_FontId]; | 583 | return &activeText_->fonts[id & mask_FontId]; |
577 | } | 584 | } |
578 | 585 | ||
579 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { | 586 | static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { |
@@ -583,7 +590,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl | |||
583 | SDL_Surface *surface8 = | 590 | SDL_Surface *surface8 = |
584 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); | 591 | SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); |
585 | SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); | 592 | SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); |
586 | SDL_SetSurfacePalette(surface8, text_.grayscale); | 593 | SDL_SetSurfacePalette(surface8, activeText_->grayscale); |
587 | #if LAGRANGE_RASTER_DEPTH != 8 | 594 | #if LAGRANGE_RASTER_DEPTH != 8 |
588 | /* Convert to the cache format. */ | 595 | /* Convert to the cache format. */ |
589 | SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); | 596 | SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); |
@@ -630,7 +637,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
630 | &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); | 637 | &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); |
631 | glRect->size = init_I2(x1 - x0, y1 - y0); | 638 | glRect->size = init_I2(x1 - x0, y1 - y0); |
632 | /* Determine placement in the glyph cache texture, advancing in rows. */ | 639 | /* Determine placement in the glyph cache texture, advancing in rows. */ |
633 | glRect->pos = assignCachePos_Text_(&text_, glRect->size); | 640 | glRect->pos = assignCachePos_Text_(activeText_, glRect->size); |
634 | glyph->d[hoff] = init_I2(x0, y0); | 641 | glyph->d[hoff] = init_I2(x0, y0); |
635 | glyph->d[hoff].y += d->vertOffset; | 642 | glyph->d[hoff].y += d->vertOffset; |
636 | if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ | 643 | if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ |
@@ -736,11 +743,11 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { | |||
736 | } | 743 | } |
737 | else { | 744 | else { |
738 | /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ | 745 | /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ |
739 | if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { | 746 | if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) { |
740 | #if !defined (NDEBUG) | 747 | #if !defined (NDEBUG) |
741 | printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); | 748 | printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); |
742 | #endif | 749 | #endif |
743 | resetCache_Text_(&text_); | 750 | resetCache_Text_(activeText_); |
744 | } | 751 | } |
745 | glyph = new_Glyph(glyphIndex); | 752 | glyph = new_Glyph(glyphIndex); |
746 | glyph->font = d; | 753 | glyph->font = d; |
@@ -857,7 +864,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i | |||
857 | } | 864 | } |
858 | 865 | ||
859 | static enum iFontId fontId_Text_(const iFont *font) { | 866 | static enum iFontId fontId_Text_(const iFont *font) { |
860 | return (enum iFontId) (font - text_.fonts); | 867 | return (enum iFontId) (font - activeText_->fonts); |
861 | } | 868 | } |
862 | 869 | ||
863 | static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { | 870 | static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { |
@@ -889,6 +896,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
889 | resize_Array(&d->visualToLogical, length); | 896 | resize_Array(&d->visualToLogical, length); |
890 | d->bidiLevels = length ? malloc(length) : NULL; | 897 | d->bidiLevels = length ? malloc(length) : NULL; |
891 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; | 898 | FriBidiParType baseDir = (FriBidiParType) FRIBIDI_TYPE_ON; |
899 | /* TODO: If this returns zero (error occurred), act like everything is LTR. */ | ||
892 | fribidi_log2vis(constData_Array(&d->logical), | 900 | fribidi_log2vis(constData_Array(&d->logical), |
893 | length, | 901 | length, |
894 | &baseDir, | 902 | &baseDir, |
@@ -947,7 +955,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh | |||
947 | /* Do a regexp match in the source text. */ | 955 | /* Do a regexp match in the source text. */ |
948 | iRegExpMatch m; | 956 | iRegExpMatch m; |
949 | init_RegExpMatch(&m); | 957 | init_RegExpMatch(&m); |
950 | if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { | 958 | if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { |
951 | finishRun_AttributedText_(d, &run, pos - 1); | 959 | finishRun_AttributedText_(d, &run, pos - 1); |
952 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), | 960 | run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), |
953 | tmParagraph_ColorId); | 961 | tmParagraph_ColorId); |
@@ -1080,9 +1088,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1080 | while (index < size_Array(glyphIndices)) { | 1088 | while (index < size_Array(glyphIndices)) { |
1081 | for (; index < size_Array(glyphIndices); index++) { | 1089 | for (; index < size_Array(glyphIndices); index++) { |
1082 | const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); | 1090 | const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); |
1083 | const int lastCacheBottom = text_.cacheBottom; | 1091 | const int lastCacheBottom = activeText_->cacheBottom; |
1084 | iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); | 1092 | iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); |
1085 | if (text_.cacheBottom < lastCacheBottom) { | 1093 | if (activeText_->cacheBottom < lastCacheBottom) { |
1086 | /* The cache was reset due to running out of space. We need to restart from | 1094 | /* The cache was reset due to running out of space. We need to restart from |
1087 | the beginning! */ | 1095 | the beginning! */ |
1088 | bufX = 0; | 1096 | bufX = 0; |
@@ -1101,7 +1109,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1101 | LAGRANGE_RASTER_DEPTH, | 1109 | LAGRANGE_RASTER_DEPTH, |
1102 | LAGRANGE_RASTER_FORMAT); | 1110 | LAGRANGE_RASTER_FORMAT); |
1103 | SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); | 1111 | SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); |
1104 | SDL_SetSurfacePalette(buf, text_.grayscale); | 1112 | SDL_SetSurfacePalette(buf, activeText_->grayscale); |
1105 | } | 1113 | } |
1106 | SDL_Surface *surfaces[2] = { | 1114 | SDL_Surface *surfaces[2] = { |
1107 | !isRasterized_Glyph_(glyph, 0) ? | 1115 | !isRasterized_Glyph_(glyph, 0) ? |
@@ -1145,19 +1153,19 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1145 | } | 1153 | } |
1146 | /* Finished or the buffer is full, copy the glyphs to the cache texture. */ | 1154 | /* Finished or the buffer is full, copy the glyphs to the cache texture. */ |
1147 | if (!isEmpty_Array(rasters)) { | 1155 | if (!isEmpty_Array(rasters)) { |
1148 | SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); | 1156 | SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf); |
1149 | SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); | 1157 | SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); |
1150 | if (!isTargetChanged) { | 1158 | if (!isTargetChanged) { |
1151 | isTargetChanged = iTrue; | 1159 | isTargetChanged = iTrue; |
1152 | oldTarget = SDL_GetRenderTarget(text_.render); | 1160 | oldTarget = SDL_GetRenderTarget(activeText_->render); |
1153 | SDL_SetRenderTarget(text_.render, text_.cache); | 1161 | SDL_SetRenderTarget(activeText_->render, activeText_->cache); |
1154 | } | 1162 | } |
1155 | // printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout); | 1163 | // printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout); |
1156 | iConstForEach(Array, i, rasters) { | 1164 | iConstForEach(Array, i, rasters) { |
1157 | const iRasterGlyph *rg = i.value; | 1165 | const iRasterGlyph *rg = i.value; |
1158 | // iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); | 1166 | // iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); |
1159 | const iRect *glRect = &rg->glyph->rect[rg->hoff]; | 1167 | const iRect *glRect = &rg->glyph->rect[rg->hoff]; |
1160 | SDL_RenderCopy(text_.render, | 1168 | SDL_RenderCopy(activeText_->render, |
1161 | bufTex, | 1169 | bufTex, |
1162 | (const SDL_Rect *) &rg->rect, | 1170 | (const SDL_Rect *) &rg->rect, |
1163 | (const SDL_Rect *) glRect); | 1171 | (const SDL_Rect *) glRect); |
@@ -1177,7 +1185,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) { | |||
1177 | SDL_FreeSurface(buf); | 1185 | SDL_FreeSurface(buf); |
1178 | } | 1186 | } |
1179 | if (isTargetChanged) { | 1187 | if (isTargetChanged) { |
1180 | SDL_SetRenderTarget(text_.render, oldTarget); | 1188 | SDL_SetRenderTarget(activeText_->render, oldTarget); |
1181 | } | 1189 | } |
1182 | } | 1190 | } |
1183 | 1191 | ||
@@ -1330,6 +1338,11 @@ static void shape_GlyphBuffer_(iGlyphBuffer *d) { | |||
1330 | } | 1338 | } |
1331 | } | 1339 | } |
1332 | 1340 | ||
1341 | static float nextTabStop_Font_(const iFont *d, float x) { | ||
1342 | const float stop = 8 * d->emAdvance; | ||
1343 | return floorf(x / stop) * stop + stop; | ||
1344 | } | ||
1345 | |||
1333 | static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { | 1346 | static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { |
1334 | float x = 0.0f; | 1347 | float x = 0.0f; |
1335 | for (unsigned int i = 0; i < d->glyphCount; i++) { | 1348 | for (unsigned int i = 0; i < d->glyphCount; i++) { |
@@ -1338,6 +1351,9 @@ static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { | |||
1338 | continue; | 1351 | continue; |
1339 | } | 1352 | } |
1340 | x += d->font->xScale * d->glyphPos[i].x_advance; | 1353 | x += d->font->xScale * d->glyphPos[i].x_advance; |
1354 | if (d->logicalText[logPos] == '\t') { | ||
1355 | x = nextTabStop_Font_(d->font, x); | ||
1356 | } | ||
1341 | if (i + 1 < d->glyphCount) { | 1357 | if (i + 1 < d->glyphCount) { |
1342 | x += horizKern_Font_(d->font, | 1358 | x += horizKern_Font_(d->font, |
1343 | d->glyphInfo[i].codepoint, | 1359 | d->glyphInfo[i].codepoint, |
@@ -1349,7 +1365,7 @@ static float advance_GlyphBuffer_(const iGlyphBuffer *d, iRangei wrapPosRange) { | |||
1349 | 1365 | ||
1350 | static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont) { | 1366 | static void evenMonospaceAdvances_GlyphBuffer_(iGlyphBuffer *d, iFont *baseFont) { |
1351 | shape_GlyphBuffer_(d); | 1367 | shape_GlyphBuffer_(d); |
1352 | const float monoAdvance = glyph_Font_(baseFont, 'M')->advance; | 1368 | const float monoAdvance = baseFont->emAdvance; |
1353 | for (unsigned int i = 0; i < d->glyphCount; ++i) { | 1369 | for (unsigned int i = 0; i < d->glyphCount; ++i) { |
1354 | const hb_glyph_info_t *info = d->glyphInfo + i; | 1370 | const hb_glyph_info_t *info = d->glyphInfo + i; |
1355 | if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) { | 1371 | if (d->glyphPos[i].x_advance > 0 && d->font != baseFont) { |
@@ -1493,10 +1509,10 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1493 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); | 1509 | const int glyphFlags = hb_glyph_info_get_glyph_flags(info); |
1494 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; | 1510 | const float xOffset = run->font->xScale * buf->glyphPos[i].x_offset; |
1495 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1511 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
1512 | const iChar ch = logicalText[logPos]; | ||
1496 | iAssert(xAdvance >= 0); | 1513 | iAssert(xAdvance >= 0); |
1497 | if (args->wrap->mode == word_WrapTextMode) { | 1514 | if (args->wrap->mode == word_WrapTextMode) { |
1498 | /* When word wrapping, only consider certain places breakable. */ | 1515 | /* When word wrapping, only consider certain places breakable. */ |
1499 | const iChar ch = logicalText[logPos]; | ||
1500 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { | 1516 | if ((ch >= 128 || !ispunct(ch)) && (prevCh == '-' || prevCh == '/')) { |
1501 | safeBreakPos = logPos; | 1517 | safeBreakPos = logPos; |
1502 | breakAdvance = wrapAdvance; | 1518 | breakAdvance = wrapAdvance; |
@@ -1522,6 +1538,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1522 | wrap->hitGlyphNormX_out = (wrap->hitPoint.x - wrapAdvance) / xAdvance; | 1538 | wrap->hitGlyphNormX_out = (wrap->hitPoint.x - wrapAdvance) / xAdvance; |
1523 | } | 1539 | } |
1524 | } | 1540 | } |
1541 | if (ch == '\t') { | ||
1542 | wrapAdvance = nextTabStop_Font_(d, wrapAdvance) - xAdvance; | ||
1543 | } | ||
1525 | /* Out of room? */ | 1544 | /* Out of room? */ |
1526 | if (wrap->maxWidth > 0 && | 1545 | if (wrap->maxWidth > 0 && |
1527 | wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > | 1546 | wrapAdvance + xOffset + glyph->d[0].x + glyph->rect[0].size.x > |
@@ -1676,6 +1695,23 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1676 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; | 1695 | const float xAdvance = run->font->xScale * buf->glyphPos[i].x_advance; |
1677 | const float yAdvance = run->font->yScale * buf->glyphPos[i].y_advance; | 1696 | const float yAdvance = run->font->yScale * buf->glyphPos[i].y_advance; |
1678 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); | 1697 | const iGlyph *glyph = glyphByIndex_Font_(run->font, glyphId); |
1698 | if (logicalText[logPos] == '\t') { | ||
1699 | #if 0 | ||
1700 | if (mode & draw_RunMode) { | ||
1701 | /* Tab indicator. */ | ||
1702 | iColor tabColor = get_Color(uiTextAction_ColorId); | ||
1703 | SDL_SetRenderDrawColor(activeText_->render, tabColor.r, tabColor.g, tabColor.b, 255); | ||
1704 | const int pad = d->height / 6; | ||
1705 | SDL_RenderFillRect(activeText_->render, &(SDL_Rect){ | ||
1706 | orig.x + xCursor, | ||
1707 | orig.y + yCursor + d->height / 2 - pad / 2, | ||
1708 | pad, | ||
1709 | pad | ||
1710 | }); | ||
1711 | } | ||
1712 | #endif | ||
1713 | xCursor = nextTabStop_Font_(d, xCursor) - xAdvance; | ||
1714 | } | ||
1679 | const float xf = xCursor + xOffset; | 1715 | const float xf = xCursor + xOffset; |
1680 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; | 1716 | const int hoff = enableHalfPixelGlyphs_Text ? (xf - ((int) xf) > 0.5f ? 1 : 0) : 0; |
1681 | /* Output position for the glyph. */ | 1717 | /* Output position for the glyph. */ |
@@ -1704,20 +1740,22 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
1704 | } | 1740 | } |
1705 | if (~mode & permanentColorFlag_RunMode) { | 1741 | if (~mode & permanentColorFlag_RunMode) { |
1706 | const iColor clr = run->fgColor; | 1742 | const iColor clr = run->fgColor; |
1707 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); | 1743 | SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); |
1708 | if (args->mode & fillBackground_RunMode) { | 1744 | if (args->mode & fillBackground_RunMode) { |
1709 | SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); | 1745 | SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); |
1710 | } | 1746 | } |
1711 | } | 1747 | } |
1712 | SDL_Rect src; | 1748 | SDL_Rect src; |
1713 | memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect)); | 1749 | memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect)); |
1750 | dst.x += origin_Paint.x; | ||
1751 | dst.y += origin_Paint.y; | ||
1714 | if (args->mode & fillBackground_RunMode) { | 1752 | if (args->mode & fillBackground_RunMode) { |
1715 | /* Alpha blending looks much better if the RGB components don't change in | 1753 | /* Alpha blending looks much better if the RGB components don't change in |
1716 | the partially transparent pixels. */ | 1754 | the partially transparent pixels. */ |
1717 | /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ | 1755 | /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ |
1718 | SDL_RenderFillRect(text_.render, &dst); | 1756 | SDL_RenderFillRect(activeText_->render, &dst); |
1719 | } | 1757 | } |
1720 | SDL_RenderCopy(text_.render, text_.cache, &src, &dst); | 1758 | SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst); |
1721 | #if 0 | 1759 | #if 0 |
1722 | /* Show spaces and direction. */ | 1760 | /* Show spaces and direction. */ |
1723 | if (logicalText[logPos] == 0x20) { | 1761 | if (logicalText[logPos] == 0x20) { |
@@ -1859,7 +1897,7 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) { | |||
1859 | } | 1897 | } |
1860 | 1898 | ||
1861 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { | 1899 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { |
1862 | iText * d = &text_; | 1900 | iText * d = activeText_; |
1863 | iFont * font = font_Text_(fontId); | 1901 | iFont * font = font_Text_(fontId); |
1864 | const iColor clr = get_Color(color & mask_ColorId); | 1902 | const iColor clr = get_Color(color & mask_ColorId); |
1865 | SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); | 1903 | SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); |
@@ -2053,7 +2091,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) { | |||
2053 | } | 2091 | } |
2054 | 2092 | ||
2055 | SDL_Texture *glyphCache_Text(void) { | 2093 | SDL_Texture *glyphCache_Text(void) { |
2056 | return text_.cache; | 2094 | return activeText_->cache; |
2057 | } | 2095 | } |
2058 | 2096 | ||
2059 | static void freeBitmap_(void *ptr) { | 2097 | static void freeBitmap_(void *ptr) { |
@@ -2166,7 +2204,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo | |||
2166 | iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color) | 2204 | iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color) |
2167 | 2205 | ||
2168 | void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { | 2206 | void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { |
2169 | SDL_Renderer *render = text_.render; | 2207 | SDL_Renderer *render = activeText_->render; |
2170 | d->size = measure_WrapText(wrapText, font).bounds.size; | 2208 | d->size = measure_WrapText(wrapText, font).bounds.size; |
2171 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); | 2209 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); |
2172 | if (d->size.x * d->size.y) { | 2210 | if (d->size.x * d->size.y) { |
@@ -2181,14 +2219,17 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { | |||
2181 | } | 2219 | } |
2182 | if (d->texture) { | 2220 | if (d->texture) { |
2183 | SDL_Texture *oldTarget = SDL_GetRenderTarget(render); | 2221 | SDL_Texture *oldTarget = SDL_GetRenderTarget(render); |
2222 | const iInt2 oldOrigin = origin_Paint; | ||
2223 | origin_Paint = zero_I2(); | ||
2184 | SDL_SetRenderTarget(render, d->texture); | 2224 | SDL_SetRenderTarget(render, d->texture); |
2185 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); | 2225 | SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); |
2186 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); | 2226 | SDL_SetRenderDrawColor(render, 255, 255, 255, 0); |
2187 | SDL_RenderClear(render); | 2227 | SDL_RenderClear(render); |
2188 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ | 2228 | SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ |
2189 | draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); | 2229 | draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); |
2190 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); | 2230 | SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND); |
2191 | SDL_SetRenderTarget(render, oldTarget); | 2231 | SDL_SetRenderTarget(render, oldTarget); |
2232 | origin_Paint = oldOrigin; | ||
2192 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | 2233 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); |
2193 | } | 2234 | } |
2194 | } | 2235 | } |
@@ -2202,9 +2243,10 @@ iTextBuf *newRange_TextBuf(int font, int color, iRangecc text) { | |||
2202 | } | 2243 | } |
2203 | 2244 | ||
2204 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { | 2245 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { |
2246 | addv_I2(&pos, origin_Paint); | ||
2205 | const iColor clr = get_Color(color); | 2247 | const iColor clr = get_Color(color); |
2206 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); | 2248 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); |
2207 | SDL_RenderCopy(text_.render, | 2249 | SDL_RenderCopy(activeText_->render, |
2208 | d->texture, | 2250 | d->texture, |
2209 | &(SDL_Rect){ 0, 0, d->size.x, d->size.y }, | 2251 | &(SDL_Rect){ 0, 0, d->size.x, d->size.y }, |
2210 | &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y }); | 2252 | &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y }); |
diff --git a/src/ui/text.h b/src/ui/text.h index ac6cc1c1..1da43818 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -139,15 +139,20 @@ enum iTextFont { | |||
139 | 139 | ||
140 | extern int gap_Text; /* affected by content font size */ | 140 | extern int gap_Text; /* affected by content font size */ |
141 | 141 | ||
142 | void init_Text (SDL_Renderer *); | 142 | iDeclareType(Text) |
143 | void deinit_Text (void); | 143 | iDeclareTypeConstructionArgs(Text, SDL_Renderer *) |
144 | |||
145 | void init_Text (iText *, SDL_Renderer *); | ||
146 | void deinit_Text (iText *); | ||
147 | |||
148 | void setCurrent_Text (iText *); | ||
144 | 149 | ||
145 | void loadUserFonts_Text (void); /* based on Prefs */ | 150 | void loadUserFonts_Text (void); /* based on Prefs */ |
146 | 151 | ||
147 | void setContentFont_Text (enum iTextFont font); | 152 | void setContentFont_Text (iText *, enum iTextFont font); |
148 | void setHeadingFont_Text (enum iTextFont font); | 153 | void setHeadingFont_Text (iText *, enum iTextFont font); |
149 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ | 154 | void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */ |
150 | void resetFonts_Text (void); | 155 | void resetFonts_Text (iText *); |
151 | 156 | ||
152 | int lineHeight_Text (int fontId); | 157 | int lineHeight_Text (int fontId); |
153 | iRect visualBounds_Text (int fontId, iRangecc text); | 158 | iRect visualBounds_Text (int fontId, iRangecc text); |
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c index e88b09a8..8b1de64a 100644 --- a/src/ui/text_simple.c +++ b/src/ui/text_simple.c | |||
@@ -92,7 +92,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
92 | } | 92 | } |
93 | if (args->mode & fillBackground_RunMode) { | 93 | if (args->mode & fillBackground_RunMode) { |
94 | const iColor initial = get_Color(args->color); | 94 | const iColor initial = get_Color(args->color); |
95 | SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); | 95 | SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0); |
96 | } | 96 | } |
97 | /* Text rendering is not very straightforward! Let's dive in... */ | 97 | /* Text rendering is not very straightforward! Let's dive in... */ |
98 | iChar prevCh = 0; | 98 | iChar prevCh = 0; |
@@ -114,14 +114,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
114 | chPos++; | 114 | chPos++; |
115 | iRegExpMatch m; | 115 | iRegExpMatch m; |
116 | init_RegExpMatch(&m); | 116 | init_RegExpMatch(&m); |
117 | if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) { | 117 | if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) { |
118 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { | 118 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { |
119 | /* Change the color. */ | 119 | /* Change the color. */ |
120 | const iColor clr = | 120 | const iColor clr = |
121 | ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); | 121 | ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); |
122 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); | 122 | SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); |
123 | if (args->mode & fillBackground_RunMode) { | 123 | if (args->mode & fillBackground_RunMode) { |
124 | SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); | 124 | SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); |
125 | } | 125 | } |
126 | } | 126 | } |
127 | chPos = end_RegExpMatch(&m); | 127 | chPos = end_RegExpMatch(&m); |
@@ -205,9 +205,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
205 | } | 205 | } |
206 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { | 206 | if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { |
207 | const iColor clr = get_Color(colorNum); | 207 | const iColor clr = get_Color(colorNum); |
208 | SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); | 208 | SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b); |
209 | if (args->mode & fillBackground_RunMode) { | 209 | if (args->mode & fillBackground_RunMode) { |
210 | SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); | 210 | SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0); |
211 | } | 211 | } |
212 | } | 212 | } |
213 | prevCh = 0; | 213 | prevCh = 0; |
@@ -306,12 +306,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) { | |||
306 | src.y += over; | 306 | src.y += over; |
307 | src.h -= over; | 307 | src.h -= over; |
308 | } | 308 | } |
309 | dst.x += origin_Paint.x; | ||
310 | dst.y += origin_Paint.y; | ||
309 | if (args->mode & fillBackground_RunMode) { | 311 | if (args->mode & fillBackground_RunMode) { |
310 | /* Alpha blending looks much better if the RGB components don't change in | 312 | /* Alpha blending looks much better if the RGB components don't change in |
311 | the partially transparent pixels. */ | 313 | the partially transparent pixels. */ |
312 | SDL_RenderFillRect(text_.render, &dst); | 314 | SDL_RenderFillRect(activeText_->render, &dst); |
313 | } | 315 | } |
314 | SDL_RenderCopy(text_.render, text_.cache, &src, &dst); | 316 | SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst); |
315 | } | 317 | } |
316 | xpos += advance; | 318 | xpos += advance; |
317 | if (!isSpace_Char(ch)) { | 319 | if (!isSpace_Char(ch)) { |
diff --git a/src/ui/touch.c b/src/ui/touch.c index dac1152e..613f2c0d 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -59,7 +59,6 @@ enum iTouchAxis { | |||
59 | struct Impl_Touch { | 59 | struct Impl_Touch { |
60 | SDL_FingerID id; | 60 | SDL_FingerID id; |
61 | iWidget *affinity; /* widget on which the touch started */ | 61 | iWidget *affinity; /* widget on which the touch started */ |
62 | // iWidget *edgeDragging; | ||
63 | iBool hasMoved; | 62 | iBool hasMoved; |
64 | iBool isTapBegun; | 63 | iBool isTapBegun; |
65 | iBool isLeftDown; | 64 | iBool isLeftDown; |
@@ -254,6 +253,8 @@ static iFloat3 gestureVector_Touch_(const iTouch *d) { | |||
254 | } | 253 | } |
255 | 254 | ||
256 | static void update_TouchState_(void *ptr) { | 255 | static void update_TouchState_(void *ptr) { |
256 | iWindow *win = get_Window(); | ||
257 | const iWidget *oldHover = win->hover; | ||
257 | iTouchState *d = ptr; | 258 | iTouchState *d = ptr; |
258 | /* Check for long presses to simulate right clicks. */ | 259 | /* Check for long presses to simulate right clicks. */ |
259 | const uint32_t nowTime = SDL_GetTicks(); | 260 | const uint32_t nowTime = SDL_GetTicks(); |
@@ -291,8 +292,10 @@ static void update_TouchState_(void *ptr) { | |||
291 | } | 292 | } |
292 | if (elapsed > 50 && !touch->isTapBegun) { | 293 | if (elapsed > 50 && !touch->isTapBegun) { |
293 | /* Looks like a possible tap. */ | 294 | /* Looks like a possible tap. */ |
295 | touchState_()->currentTouchPos = initF3_I2(touch->pos[0]); | ||
294 | dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); | 296 | dispatchNotification_Touch_(touch, widgetTapBegins_UserEventCode); |
295 | dispatchMotion_Touch_(touch->pos[0], 0); | 297 | dispatchMotion_Touch_(touch->pos[0], 0); |
298 | refresh_Widget(touch->affinity); | ||
296 | touch->isTapBegun = iTrue; | 299 | touch->isTapBegun = iTrue; |
297 | } | 300 | } |
298 | if (!touch->isTapAndHold && nowTime - touch->startTime >= longPressSpanMs_ && | 301 | if (!touch->isTapAndHold && nowTime - touch->startTime >= longPressSpanMs_ && |
@@ -347,6 +350,7 @@ static void update_TouchState_(void *ptr) { | |||
347 | setCurrent_Root(mom->affinity->root); | 350 | setCurrent_Root(mom->affinity->root); |
348 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 351 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
349 | .type = SDL_MOUSEWHEEL, | 352 | .type = SDL_MOUSEWHEEL, |
353 | .which = SDL_TOUCH_MOUSEID, | ||
350 | .timestamp = nowTime, | 354 | .timestamp = nowTime, |
351 | .x = pixels.x, | 355 | .x = pixels.x, |
352 | .y = pixels.y, | 356 | .y = pixels.y, |
@@ -363,6 +367,10 @@ static void update_TouchState_(void *ptr) { | |||
363 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { | 367 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { |
364 | addTickerRoot_App(update_TouchState_, NULL, ptr); | 368 | addTickerRoot_App(update_TouchState_, NULL, ptr); |
365 | } | 369 | } |
370 | if (oldHover != win->hover) { | ||
371 | refresh_Widget(oldHover); | ||
372 | refresh_Widget(win->hover); | ||
373 | } | ||
366 | } | 374 | } |
367 | 375 | ||
368 | #if 0 | 376 | #if 0 |
@@ -464,13 +472,9 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
464 | } | 472 | } |
465 | iTouchState *d = touchState_(); | 473 | iTouchState *d = touchState_(); |
466 | iWindow *window = get_Window(); | 474 | iWindow *window = get_Window(); |
467 | if (!isFinished_Anim(&window->rootOffset)) { | ||
468 | return iFalse; | ||
469 | } | ||
470 | const iInt2 rootSize = size_Window(window); | 475 | const iInt2 rootSize = size_Window(window); |
471 | const SDL_TouchFingerEvent *fing = &ev->tfinger; | 476 | const SDL_TouchFingerEvent *fing = &ev->tfinger; |
472 | const iFloat3 pos = add_F3(init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0), /* pixels */ | 477 | const iFloat3 pos = init_F3(fing->x * rootSize.x, fing->y * rootSize.y, 0); /* pixels */ |
473 | init_F3(0, -value_Anim(&window->rootOffset), 0)); | ||
474 | const uint32_t nowTime = SDL_GetTicks(); | 478 | const uint32_t nowTime = SDL_GetTicks(); |
475 | if (ev->type == SDL_FINGERDOWN) { | 479 | if (ev->type == SDL_FINGERDOWN) { |
476 | /* Register the new touch. */ | 480 | /* Register the new touch. */ |
@@ -614,15 +618,16 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
614 | // pixels.y, y_F3(amount), y_F3(touch->accum), | 618 | // pixels.y, y_F3(amount), y_F3(touch->accum), |
615 | // touch->edge); | 619 | // touch->edge); |
616 | if (pixels.x || pixels.y) { | 620 | if (pixels.x || pixels.y) { |
617 | setFocus_Widget(NULL); | 621 | //setFocus_Widget(NULL); |
618 | dispatchMotion_Touch_(touch->pos[0], 0); | 622 | dispatchMotion_Touch_(touch->startPos /*pos[0]*/, 0); |
619 | setCurrent_Root(touch->affinity->root); | 623 | setCurrent_Root(touch->affinity->root); |
620 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 624 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
621 | .type = SDL_MOUSEWHEEL, | 625 | .type = SDL_MOUSEWHEEL, |
626 | .which = SDL_TOUCH_MOUSEID, | ||
622 | .timestamp = SDL_GetTicks(), | 627 | .timestamp = SDL_GetTicks(), |
623 | .x = pixels.x, | 628 | .x = pixels.x, |
624 | .y = pixels.y, | 629 | .y = pixels.y, |
625 | .direction = perPixel_MouseWheelFlag | 630 | .direction = perPixel_MouseWheelFlag, |
626 | }); | 631 | }); |
627 | /* TODO: Keep increasing movement if the direction is the same. */ | 632 | /* TODO: Keep increasing movement if the direction is the same. */ |
628 | clearWidgetMomentum_TouchState_(d, touch->affinity); | 633 | clearWidgetMomentum_TouchState_(d, touch->affinity); |
@@ -715,7 +720,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
715 | iMomentum mom = { | 720 | iMomentum mom = { |
716 | .affinity = touch->affinity, | 721 | .affinity = touch->affinity, |
717 | .releaseTime = nowTime, | 722 | .releaseTime = nowTime, |
718 | .pos = touch->pos[0], | 723 | .pos = touch->startPos, // pos[0], |
719 | .velocity = velocity | 724 | .velocity = velocity |
720 | }; | 725 | }; |
721 | if (isEmpty_Array(d->moms)) { | 726 | if (isEmpty_Array(d->moms)) { |
diff --git a/src/ui/translation.c b/src/ui/translation.c index 3ffa961b..b86e6e52 100644 --- a/src/ui/translation.c +++ b/src/ui/translation.c | |||
@@ -136,7 +136,8 @@ static void draw_TranslationProgressWidget_(const iTranslationProgressWidget *d) | |||
136 | get_Color(palette[palCur]), get_Color(palette[palNext]), palPos - (int) palPos); | 136 | get_Color(palette[palCur]), get_Color(palette[palNext]), palPos - (int) palPos); |
137 | SDL_SetRenderDrawColor(renderer_Window(get_Window()), back.r, back.g, back.b, p.alpha); | 137 | SDL_SetRenderDrawColor(renderer_Window(get_Window()), back.r, back.g, back.b, p.alpha); |
138 | SDL_RenderFillRect(renderer_Window(get_Window()), | 138 | SDL_RenderFillRect(renderer_Window(get_Window()), |
139 | &(SDL_Rect){ pos.x, pos.y, spr->size.x, spr->size.y }); | 139 | &(SDL_Rect){ pos.x + origin_Paint.x, pos.y + origin_Paint.y, |
140 | spr->size.x, spr->size.y }); | ||
140 | if (fg >= 0) { | 141 | if (fg >= 0) { |
141 | setOpacity_Text(opacity * 2); | 142 | setOpacity_Text(opacity * 2); |
142 | drawRange_Text(d->font, addX_I2(pos, spr->xoff), fg, range_String(&spr->text)); | 143 | drawRange_Text(d->font, addX_I2(pos, spr->xoff), fg, range_String(&spr->text)); |
@@ -424,19 +425,18 @@ static iBool processResult_Translation_(iTranslation *d) { | |||
424 | } | 425 | } |
425 | 426 | ||
426 | static iLabelWidget *acceptButton_Translation_(const iTranslation *d) { | 427 | static iLabelWidget *acceptButton_Translation_(const iTranslation *d) { |
427 | iWidget *buttonParent = findChild_Widget(d->dlg, "dialogbuttons"); | 428 | return dialogAcceptButton_Widget(d->dlg); |
428 | // if (!buttonParent) { | ||
429 | // buttonParent = findChild_Widget(d->dlg, "panel.back"); | ||
430 | // } | ||
431 | return (iLabelWidget *) lastChild_Widget(buttonParent); | ||
432 | } | 429 | } |
433 | 430 | ||
434 | iBool handleCommand_Translation(iTranslation *d, const char *cmd) { | 431 | iBool handleCommand_Translation(iTranslation *d, const char *cmd) { |
435 | iWidget *w = as_Widget(d->doc); | 432 | iWidget *w = as_Widget(d->doc); |
436 | if (equalWidget_Command(cmd, w, "translation.submit")) { | 433 | if (equalWidget_Command(cmd, w, "translation.submit")) { |
437 | if (status_TlsRequest(d->request) == initialized_TlsRequestStatus) { | 434 | if (status_TlsRequest(d->request) == initialized_TlsRequestStatus) { |
438 | iWidget *langs = findChild_Widget(d->dlg, "xlt.langs"); | 435 | iWidget *langs = findChild_Widget(d->dlg, "xlt.langs"); |
439 | setFlags_Widget(langs, hidden_WidgetFlag, iTrue); | 436 | // setFlags_Widget(langs, hidden_WidgetFlag, iTrue); |
437 | setFlags_Widget(findChild_Widget(d->dlg, "xlt.from"), hidden_WidgetFlag, iTrue); | ||
438 | setFlags_Widget(findChild_Widget(d->dlg, "xlt.to"), hidden_WidgetFlag, iTrue); | ||
439 | if (!langs) langs = d->dlg; | ||
440 | iLabelWidget *acceptButton = acceptButton_Translation_(d); | 440 | iLabelWidget *acceptButton = acceptButton_Translation_(d); |
441 | updateTextCStr_LabelWidget(acceptButton, "00:00"); | 441 | updateTextCStr_LabelWidget(acceptButton, "00:00"); |
442 | setFlags_Widget(as_Widget(acceptButton), disabled_WidgetFlag, iTrue); | 442 | setFlags_Widget(as_Widget(acceptButton), disabled_WidgetFlag, iTrue); |
diff --git a/src/ui/uploadwidget.c b/src/ui/uploadwidget.c index 5e1ee493..ba7545fd 100644 --- a/src/ui/uploadwidget.c +++ b/src/ui/uploadwidget.c | |||
@@ -29,12 +29,25 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
29 | #include "command.h" | 29 | #include "command.h" |
30 | #include "gmrequest.h" | 30 | #include "gmrequest.h" |
31 | #include "sitespec.h" | 31 | #include "sitespec.h" |
32 | #include "window.h" | ||
33 | #include "gmcerts.h" | ||
32 | #include "app.h" | 34 | #include "app.h" |
33 | 35 | ||
36 | #if defined (iPlatformAppleMobile) | ||
37 | # include "ios.h" | ||
38 | #endif | ||
39 | |||
34 | #include <the_Foundation/file.h> | 40 | #include <the_Foundation/file.h> |
35 | #include <the_Foundation/fileinfo.h> | 41 | #include <the_Foundation/fileinfo.h> |
42 | #include <the_Foundation/path.h> | ||
36 | 43 | ||
37 | iDefineObjectConstruction(UploadWidget) | 44 | iDefineObjectConstruction(UploadWidget) |
45 | |||
46 | enum iUploadIdentity { | ||
47 | none_UploadIdentity, | ||
48 | defaultForUrl_UploadIdentity, | ||
49 | dropdown_UploadIdentity, | ||
50 | }; | ||
38 | 51 | ||
39 | struct Impl_UploadWidget { | 52 | struct Impl_UploadWidget { |
40 | iWidget widget; | 53 | iWidget widget; |
@@ -51,9 +64,21 @@ struct Impl_UploadWidget { | |||
51 | iLabelWidget * counter; | 64 | iLabelWidget * counter; |
52 | iString filePath; | 65 | iString filePath; |
53 | size_t fileSize; | 66 | size_t fileSize; |
67 | enum iUploadIdentity idMode; | ||
68 | iBlock idFingerprint; | ||
54 | iAtomicInt isRequestUpdated; | 69 | iAtomicInt isRequestUpdated; |
55 | }; | 70 | }; |
56 | 71 | ||
72 | static void releaseFile_UploadWidget_(iUploadWidget *d) { | ||
73 | #if defined (iPlatformAppleMobile) | ||
74 | if (!isEmpty_String(&d->filePath)) { | ||
75 | /* Delete the temporary file that was copied for uploading. */ | ||
76 | remove(cstr_String(&d->filePath)); | ||
77 | } | ||
78 | #endif | ||
79 | clear_String(&d->filePath); | ||
80 | } | ||
81 | |||
57 | static void updateProgress_UploadWidget_(iGmRequest *request, size_t current, size_t total) { | 82 | static void updateProgress_UploadWidget_(iGmRequest *request, size_t current, size_t total) { |
58 | iUploadWidget *d = userData_Object(request); | 83 | iUploadWidget *d = userData_Object(request); |
59 | postCommand_Widget(d, | 84 | postCommand_Widget(d, |
@@ -67,116 +92,202 @@ static void updateInputMaxHeight_UploadWidget_(iUploadWidget *d) { | |||
67 | iWidget *w = as_Widget(d); | 92 | iWidget *w = as_Widget(d); |
68 | /* Calculate how many lines fits vertically in the view. */ | 93 | /* Calculate how many lines fits vertically in the view. */ |
69 | const iInt2 inputPos = topLeft_Rect(bounds_Widget(as_Widget(d->input))); | 94 | const iInt2 inputPos = topLeft_Rect(bounds_Widget(as_Widget(d->input))); |
70 | const int footerHeight = height_Widget(d->token) + | 95 | const int footerHeight = isUsingPanelLayout_Mobile() ? 0 : |
71 | height_Widget(findChild_Widget(w, "dialogbuttons")) + | 96 | (height_Widget(d->token) + |
72 | 6 * gap_UI; | 97 | height_Widget(findChild_Widget(w, "dialogbuttons")) + |
73 | const int avail = bottom_Rect(safeRect_Root(w->root)) - footerHeight; | 98 | 12 * gap_UI); |
99 | const int avail = bottom_Rect(safeRect_Root(w->root)) - footerHeight - | ||
100 | get_MainWindow()->keyboardHeight; | ||
74 | setLineLimits_InputWidget(d->input, | 101 | setLineLimits_InputWidget(d->input, |
75 | minLines_InputWidget(d->input), | 102 | minLines_InputWidget(d->input), |
76 | iMaxi(minLines_InputWidget(d->input), | 103 | iMaxi(minLines_InputWidget(d->input), |
77 | (avail - inputPos.y) / lineHeight_Text(monospace_FontId))); | 104 | (avail - inputPos.y) / lineHeight_Text(font_InputWidget(d->input)))); |
105 | } | ||
106 | |||
107 | static const iArray *makeIdentityItems_UploadWidget_(const iUploadWidget *d) { | ||
108 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
109 | const iGmIdentity *urlId = identityForUrl_GmCerts(certs_App(), &d->url); | ||
110 | pushBack_Array(items, | ||
111 | &(iMenuItem){ format_CStr("${dlg.upload.id.default} (%s)", | ||
112 | urlId ? cstr_String(name_GmIdentity(urlId)) | ||
113 | : "${dlg.upload.id.none}"), | ||
114 | 0, 0, "upload.setid arg:1" }); | ||
115 | pushBack_Array(items, &(iMenuItem){ "${dlg.upload.id.none}", 0, 0, "upload.setid arg:0" }); | ||
116 | pushBack_Array(items, &(iMenuItem){ "---" }); | ||
117 | iConstForEach(PtrArray, i, listIdentities_GmCerts(certs_App(), NULL, NULL)) { | ||
118 | const iGmIdentity *id = i.ptr; | ||
119 | pushBack_Array( | ||
120 | items, | ||
121 | &(iMenuItem){ cstr_String(name_GmIdentity(id)), 0, 0, | ||
122 | format_CStr("upload.setid fp:%s", | ||
123 | cstrCollect_String(hexEncode_Block(&id->fingerprint))) }); | ||
124 | } | ||
125 | pushBack_Array(items, &(iMenuItem){ NULL }); | ||
126 | return items; | ||
78 | } | 127 | } |
79 | 128 | ||
80 | void init_UploadWidget(iUploadWidget *d) { | 129 | void init_UploadWidget(iUploadWidget *d) { |
81 | iWidget *w = as_Widget(d); | 130 | iWidget *w = as_Widget(d); |
82 | init_Widget(w); | 131 | init_Widget(w); |
83 | setId_Widget(w, "upload"); | 132 | setId_Widget(w, "upload"); |
84 | useSheetStyle_Widget(w); | ||
85 | init_String(&d->originalUrl); | 133 | init_String(&d->originalUrl); |
86 | init_String(&d->url); | 134 | init_String(&d->url); |
87 | d->viewer = NULL; | 135 | d->viewer = NULL; |
88 | d->request = NULL; | 136 | d->request = NULL; |
89 | init_String(&d->filePath); | 137 | init_String(&d->filePath); |
90 | d->fileSize = 0; | 138 | d->fileSize = 0; |
91 | addChildFlags_Widget(w, | 139 | d->idMode = defaultForUrl_UploadIdentity; |
92 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), | 140 | init_Block(&d->idFingerprint, 0); |
93 | frameless_WidgetFlag); | 141 | const iMenuItem actions[] = { |
94 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), | 142 | { "${upload.port}", 0, 0, "upload.setport" }, |
95 | frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | | 143 | { "---" }, |
96 | fixedHeight_WidgetFlag); | 144 | { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, |
97 | setWrap_LabelWidget(d->info, iTrue); | 145 | { uiTextAction_ColorEscape "${dlg.upload.send}", SDLK_RETURN, KMOD_PRIMARY, "upload.accept" } |
98 | /* Tabs for input data. */ | 146 | }; |
99 | iWidget *tabs = makeTabs_Widget(w); | 147 | if (isUsingPanelLayout_Mobile()) { |
100 | /* Make the tabs support vertical expansion based on content. */ { | 148 | const iMenuItem textItems[] = { |
101 | setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); | 149 | { "title id:heading.upload.text" }, |
102 | setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); | 150 | { "input id:upload.text noheading:1" }, |
103 | iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); | 151 | { NULL } |
104 | setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); | 152 | }; |
105 | setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); | 153 | const iMenuItem fileItems[] = { |
106 | } | 154 | { "title id:heading.upload.file" }, |
107 | iWidget *headings, *values; | 155 | { "button text:" uiTextAction_ColorEscape "${dlg.upload.pickfile}", 0, 0, "upload.pickfile" }, |
108 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); | 156 | { "heading id:upload.file.name" }, |
109 | setId_Widget(tabs, "upload.tabs"); | 157 | { "label id:upload.filepathlabel text:\u2014" }, |
110 | // const int bigGap = lineHeight_Text(uiLabel_FontId) * 3 / 4; | 158 | { "heading id:upload.file.size" }, |
111 | /* Text input. */ { | 159 | { "label id:upload.filesizelabel text:\u2014" }, |
112 | //appendTwoColumnTabPage_Widget(tabs, "${heading.upload.text}", '1', &headings, &values); | 160 | { "padding" }, |
113 | iWidget *page = new_Widget(); | 161 | { "input id:upload.mime" }, |
114 | setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); | 162 | { "label id:upload.counter text:" }, |
115 | d->input = new_InputWidget(0); | 163 | { NULL } |
116 | setId_Widget(as_Widget(d->input), "upload.text"); | 164 | }; |
117 | setFont_InputWidget(d->input, monospace_FontId); | 165 | initPanels_Mobile(w, NULL, (iMenuItem[]){ |
118 | setLineLimits_InputWidget(d->input, 7, 20); | 166 | { "title id:heading.upload" }, |
119 | setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ | 167 | { "label id:upload.info" }, |
120 | setHint_InputWidget(d->input, "${hint.upload.text}"); | 168 | { "panel id:dlg.upload.text icon:0x1f5b9", 0, 0, (const void *) textItems }, |
121 | setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); | 169 | { "panel id:dlg.upload.file icon:0x1f4c1", 0, 0, (const void *) fileItems }, |
122 | addChild_Widget(page, iClob(d->input)); | 170 | { "padding" }, |
123 | appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); | 171 | { "dropdown id:upload.id icon:0x1f464", 0, 0, constData_Array(makeIdentityItems_UploadWidget_(d)) }, |
124 | } | 172 | { "input id:upload.token hint:hint.upload.token icon:0x1f511" }, |
125 | /* File content. */ { | 173 | { NULL } |
126 | appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); | 174 | }, actions, iElemCount(actions)); |
127 | // iWidget *pad = addChild_Widget(headings, iClob(makePadding_Widget(0))); | 175 | d->info = findChild_Widget(w, "upload.info"); |
128 | // iWidget *hint = addChild_Widget(values, iClob(new_LabelWidget("${upload.file.drophint}", NULL))); | 176 | d->input = findChild_Widget(w, "upload.text"); |
129 | // pad->sizeRef = hint; | 177 | d->filePathLabel = findChild_Widget(w, "upload.filepathlabel"); |
130 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); | 178 | d->fileSizeLabel = findChild_Widget(w, "upload.filesizelabel"); |
131 | d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); | 179 | d->mime = findChild_Widget(w, "upload.mime"); |
132 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); | 180 | d->token = findChild_Widget(w, "upload.token"); |
133 | d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); | 181 | d->counter = findChild_Widget(w, "upload.counter"); |
134 | d->mime = new_InputWidget(0); | ||
135 | setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); | ||
136 | addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); | ||
137 | } | ||
138 | /* Token. */ { | ||
139 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
140 | iWidget *page = makeTwoColumns_Widget(&headings, &values); | ||
141 | d->token = addTwoColumnDialogInputField_Widget( | ||
142 | headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); | ||
143 | setHint_InputWidget(d->token, "${hint.upload.token}"); | ||
144 | setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); | ||
145 | addChild_Widget(w, iClob(page)); | ||
146 | } | 182 | } |
147 | /* Buttons. */ { | 183 | else { |
148 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | 184 | useSheetStyle_Widget(w); |
149 | iWidget *buttons = | 185 | setFlags_Widget(w, overflowScrollable_WidgetFlag, iFalse); |
150 | makeDialogButtons_Widget((iMenuItem[]){ { "${upload.port}", 0, 0, "upload.setport" }, | 186 | addChildFlags_Widget(w, |
151 | { "---", 0, 0, NULL }, | 187 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.upload}", NULL)), |
152 | { "${close}", SDLK_ESCAPE, 0, "upload.cancel" }, | 188 | frameless_WidgetFlag); |
153 | { uiTextAction_ColorEscape "${dlg.upload.send}", | 189 | d->info = addChildFlags_Widget(w, iClob(new_LabelWidget("", NULL)), |
154 | SDLK_RETURN, | 190 | frameless_WidgetFlag | resizeToParentWidth_WidgetFlag | |
155 | KMOD_PRIMARY, | 191 | fixedHeight_WidgetFlag); |
156 | "upload.accept" } }, | 192 | setWrap_LabelWidget(d->info, iTrue); |
157 | 4); | 193 | /* Tabs for input data. */ |
158 | setId_Widget(insertChildAfterFlags_Widget(buttons, | 194 | iWidget *tabs = makeTabs_Widget(w); |
159 | iClob(d->counter = new_LabelWidget("", NULL)), | 195 | /* Make the tabs support vertical expansion based on content. */ { |
160 | 0, frameless_WidgetFlag), | 196 | setFlags_Widget(tabs, resizeHeightOfChildren_WidgetFlag, iFalse); |
161 | "upload.counter"); | 197 | setFlags_Widget(tabs, arrangeHeight_WidgetFlag, iTrue); |
162 | addChild_Widget(w, iClob(buttons)); | 198 | iWidget *tabPages = findChild_Widget(tabs, "tabs.pages"); |
199 | setFlags_Widget(tabPages, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
200 | setFlags_Widget(tabPages, arrangeHeight_WidgetFlag, iTrue); | ||
201 | } | ||
202 | iWidget *headings, *values; | ||
203 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); | ||
204 | setId_Widget(tabs, "upload.tabs"); | ||
205 | /* Text input. */ { | ||
206 | iWidget *page = new_Widget(); | ||
207 | setFlags_Widget(page, arrangeSize_WidgetFlag, iTrue); | ||
208 | d->input = new_InputWidget(0); | ||
209 | setId_Widget(as_Widget(d->input), "upload.text"); | ||
210 | setFixedSize_Widget(as_Widget(d->input), init_I2(120 * gap_UI, -1)); | ||
211 | addChild_Widget(page, iClob(d->input)); | ||
212 | appendFramelessTabPage_Widget(tabs, iClob(page), "${heading.upload.text}", '1', 0); | ||
213 | } | ||
214 | /* File content. */ { | ||
215 | appendTwoColumnTabPage_Widget(tabs, "${heading.upload.file}", '2', &headings, &values); | ||
216 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.name}", NULL)), frameless_WidgetFlag); | ||
217 | d->filePathLabel = addChildFlags_Widget(values, iClob(new_LabelWidget(uiTextAction_ColorEscape "${upload.file.drophere}", NULL)), frameless_WidgetFlag); | ||
218 | addChildFlags_Widget(headings, iClob(new_LabelWidget("${upload.file.size}", NULL)), frameless_WidgetFlag); | ||
219 | d->fileSizeLabel = addChildFlags_Widget(values, iClob(new_LabelWidget("\u2014", NULL)), frameless_WidgetFlag); | ||
220 | d->mime = new_InputWidget(0); | ||
221 | setFixedSize_Widget(as_Widget(d->mime), init_I2(70 * gap_UI, -1)); | ||
222 | addTwoColumnDialogInputField_Widget(headings, values, "${upload.mime}", "upload.mime", iClob(d->mime)); | ||
223 | } | ||
224 | /* Identity and Token. */ { | ||
225 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
226 | iWidget *page = makeTwoColumns_Widget(&headings, &values); | ||
227 | /* Token. */ | ||
228 | d->token = addTwoColumnDialogInputField_Widget( | ||
229 | headings, values, "${upload.token}", "upload.token", iClob(new_InputWidget(0))); | ||
230 | setHint_InputWidget(d->token, "${hint.upload.token}"); | ||
231 | setFixedSize_Widget(as_Widget(d->token), init_I2(50 * gap_UI, -1)); | ||
232 | /* Identity. */ | ||
233 | const iArray * identItems = makeIdentityItems_UploadWidget_(d); | ||
234 | const iMenuItem *items = constData_Array(identItems); | ||
235 | const size_t numItems = size_Array(identItems); | ||
236 | iLabelWidget * ident = makeMenuButton_LabelWidget("${upload.id}", items, numItems); | ||
237 | setTextCStr_LabelWidget(ident, items[findWidestLabel_MenuItem(items, numItems)].label); | ||
238 | addChild_Widget(headings, iClob(makeHeading_Widget("${upload.id}"))); | ||
239 | setId_Widget(addChildFlags_Widget(values, iClob(ident), alignLeft_WidgetFlag), "upload.id"); | ||
240 | addChild_Widget(w, iClob(page)); | ||
241 | } | ||
242 | /* Buttons. */ { | ||
243 | addChild_Widget(w, iClob(makePadding_Widget(gap_UI))); | ||
244 | iWidget *buttons = makeDialogButtons_Widget(actions, iElemCount(actions)); | ||
245 | setId_Widget(insertChildAfterFlags_Widget(buttons, | ||
246 | iClob(d->counter = new_LabelWidget("", NULL)), | ||
247 | 0, | ||
248 | frameless_WidgetFlag), | ||
249 | "upload.counter"); | ||
250 | addChild_Widget(w, iClob(buttons)); | ||
251 | } | ||
252 | resizeToLargestPage_Widget(tabs); | ||
253 | arrange_Widget(w); | ||
254 | setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); | ||
255 | setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); | ||
256 | setFocus_Widget(as_Widget(d->input)); | ||
163 | } | 257 | } |
164 | resizeToLargestPage_Widget(tabs); | 258 | setFont_InputWidget(d->input, iosevka_FontId); |
165 | arrange_Widget(w); | 259 | setUseReturnKeyBehavior_InputWidget(d->input, iFalse); /* traditional text editor */ |
166 | setFixedSize_Widget(as_Widget(d->token), init_I2(width_Widget(tabs) - left_Rect(parent_Widget(d->token)->rect), -1)); | 260 | setLineLimits_InputWidget(d->input, 7, 20); |
167 | setFlags_Widget(as_Widget(d->token), expand_WidgetFlag, iTrue); | 261 | setHint_InputWidget(d->input, "${hint.upload.text}"); |
168 | setFocus_Widget(as_Widget(d->input)); | ||
169 | setBackupFileName_InputWidget(d->input, "uploadbackup.txt"); | 262 | setBackupFileName_InputWidget(d->input, "uploadbackup.txt"); |
170 | updateInputMaxHeight_UploadWidget_(d); | 263 | updateInputMaxHeight_UploadWidget_(d); |
171 | } | 264 | } |
172 | 265 | ||
173 | void deinit_UploadWidget(iUploadWidget *d) { | 266 | void deinit_UploadWidget(iUploadWidget *d) { |
267 | releaseFile_UploadWidget_(d); | ||
268 | deinit_Block(&d->idFingerprint); | ||
174 | deinit_String(&d->filePath); | 269 | deinit_String(&d->filePath); |
175 | deinit_String(&d->url); | 270 | deinit_String(&d->url); |
176 | deinit_String(&d->originalUrl); | 271 | deinit_String(&d->originalUrl); |
177 | iRelease(d->request); | 272 | iRelease(d->request); |
178 | } | 273 | } |
179 | 274 | ||
275 | static void remakeIdentityItems_UploadWidget_(iUploadWidget *d) { | ||
276 | iWidget *dropMenu = findChild_Widget(findChild_Widget(as_Widget(d), "upload.id"), "menu"); | ||
277 | releaseChildren_Widget(dropMenu); | ||
278 | const iArray *items = makeIdentityItems_UploadWidget_(d); | ||
279 | makeMenuItems_Widget(dropMenu, constData_Array(items), size_Array(items)); | ||
280 | } | ||
281 | |||
282 | static void updateIdentityDropdown_UploadWidget_(iUploadWidget *d) { | ||
283 | updateDropdownSelection_LabelWidget( | ||
284 | findChild_Widget(as_Widget(d), "upload.id"), | ||
285 | d->idMode == none_UploadIdentity ? " arg:0" | ||
286 | : d->idMode == defaultForUrl_UploadIdentity | ||
287 | ? " arg:1" | ||
288 | : format_CStr(" fp:%s", cstrCollect_String(hexEncode_Block(&d->idFingerprint)))); | ||
289 | } | ||
290 | |||
180 | static uint16_t titanPortForUrl_(const iString *url) { | 291 | static uint16_t titanPortForUrl_(const iString *url) { |
181 | uint16_t port = 0; | 292 | uint16_t port = 0; |
182 | const iString *root = collectNewRange_String(urlRoot_String(url)); | 293 | const iString *root = collectNewRange_String(urlRoot_String(url)); |
@@ -201,10 +312,13 @@ static void setUrlPort_UploadWidget_(iUploadWidget *d, const iString *url, uint1 | |||
201 | appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); | 312 | appendFormat_String(&d->url, ":%u", overridePort ? overridePort : titanPortForUrl_(url)); |
202 | appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); | 313 | appendRange_String(&d->url, (iRangecc){ parts.path.start, constEnd_String(url) }); |
203 | setText_LabelWidget(d->info, &d->url); | 314 | setText_LabelWidget(d->info, &d->url); |
315 | arrange_Widget(as_Widget(d)); | ||
204 | } | 316 | } |
205 | 317 | ||
206 | void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { | 318 | void setUrl_UploadWidget(iUploadWidget *d, const iString *url) { |
207 | setUrlPort_UploadWidget_(d, url, 0); | 319 | setUrlPort_UploadWidget_(d, url, 0); |
320 | remakeIdentityItems_UploadWidget_(d); | ||
321 | updateIdentityDropdown_UploadWidget_(d); | ||
208 | } | 322 | } |
209 | 323 | ||
210 | void setResponseViewer_UploadWidget(iUploadWidget *d, iDocumentWidget *doc) { | 324 | void setResponseViewer_UploadWidget(iUploadWidget *d, iDocumentWidget *doc) { |
@@ -227,13 +341,33 @@ static void requestFinished_UploadWidget_(iUploadWidget *d, iGmRequest *req) { | |||
227 | postCommand_Widget(d, "upload.request.finished reqid:%u", id_GmRequest(req)); | 341 | postCommand_Widget(d, "upload.request.finished reqid:%u", id_GmRequest(req)); |
228 | } | 342 | } |
229 | 343 | ||
344 | static void updateFileInfo_UploadWidget_(iUploadWidget *d) { | ||
345 | iFileInfo *info = iClob(new_FileInfo(&d->filePath)); | ||
346 | if (isDirectory_FileInfo(info)) { | ||
347 | makeMessage_Widget("${heading.upload.error.file}", | ||
348 | "${upload.error.directory}", | ||
349 | (iMenuItem[]){ "${dlg.message.ok}", 0, 0, "message.ok" }, 1); | ||
350 | clear_String(&d->filePath); | ||
351 | d->fileSize = 0; | ||
352 | return; | ||
353 | } | ||
354 | d->fileSize = size_FileInfo(info); | ||
355 | #if defined (iPlatformMobile) | ||
356 | setTextCStr_LabelWidget(d->filePathLabel, cstr_Rangecc(baseName_Path(&d->filePath))); | ||
357 | #else | ||
358 | setText_LabelWidget(d->filePathLabel, &d->filePath); | ||
359 | #endif | ||
360 | setTextCStr_LabelWidget(d->fileSizeLabel, formatCStrs_Lang("num.bytes.n", d->fileSize)); | ||
361 | setTextCStr_InputWidget(d->mime, mediaType_Path(&d->filePath)); | ||
362 | } | ||
363 | |||
230 | static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | 364 | static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { |
231 | iWidget *w = as_Widget(d); | 365 | iWidget *w = as_Widget(d); |
232 | const char *cmd = command_UserEvent(ev); | 366 | const char *cmd = command_UserEvent(ev); |
233 | if (isResize_UserEvent(ev)) { | 367 | if (isResize_UserEvent(ev) || equal_Command(cmd, "keyboard.changed")) { |
234 | updateInputMaxHeight_UploadWidget_(d); | 368 | updateInputMaxHeight_UploadWidget_(d); |
235 | } | 369 | } |
236 | if (isCommand_Widget(w, ev, "upload.cancel")) { | 370 | if (equal_Command(cmd, "upload.cancel")) { |
237 | setupSheetTransition_Mobile(w, iFalse); | 371 | setupSheetTransition_Mobile(w, iFalse); |
238 | destroy_Widget(w); | 372 | destroy_Widget(w); |
239 | return iTrue; | 373 | return iTrue; |
@@ -254,9 +388,36 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
254 | } | 388 | } |
255 | return iTrue; | 389 | return iTrue; |
256 | } | 390 | } |
391 | if (isCommand_Widget(w, ev, "upload.setid")) { | ||
392 | if (hasLabel_Command(cmd, "fp")) { | ||
393 | set_Block(&d->idFingerprint, collect_Block(hexDecode_Rangecc(range_Command(cmd, "fp")))); | ||
394 | d->idMode = dropdown_UploadIdentity; | ||
395 | } | ||
396 | else if (arg_Command(cmd)) { | ||
397 | clear_Block(&d->idFingerprint); | ||
398 | d->idMode = defaultForUrl_UploadIdentity; | ||
399 | } | ||
400 | else { | ||
401 | clear_Block(&d->idFingerprint); | ||
402 | d->idMode = none_UploadIdentity; | ||
403 | } | ||
404 | updateIdentityDropdown_UploadWidget_(d); | ||
405 | return iTrue; | ||
406 | } | ||
257 | if (isCommand_Widget(w, ev, "upload.accept")) { | 407 | if (isCommand_Widget(w, ev, "upload.accept")) { |
258 | iWidget * tabs = findChild_Widget(w, "upload.tabs"); | 408 | iBool isText; |
259 | const int tabIndex = tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)); | 409 | iWidget *tabs = findChild_Widget(w, "upload.tabs"); |
410 | if (tabs) { | ||
411 | const size_t tabIndex = tabPageIndex_Widget(tabs, currentTabPage_Widget(tabs)); | ||
412 | isText = (tabIndex == 0); | ||
413 | } | ||
414 | else { | ||
415 | const size_t panelIndex = currentPanelIndex_Mobile(w); | ||
416 | if (panelIndex == iInvalidPos) { | ||
417 | return iTrue; | ||
418 | } | ||
419 | isText = (currentPanelIndex_Mobile(w) == 0); | ||
420 | } | ||
260 | /* Make a GmRequest and send the data. */ | 421 | /* Make a GmRequest and send the data. */ |
261 | iAssert(d->request == NULL); | 422 | iAssert(d->request == NULL); |
262 | iAssert(!isEmpty_String(&d->url)); | 423 | iAssert(!isEmpty_String(&d->url)); |
@@ -264,7 +425,21 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
264 | setSendProgressFunc_GmRequest(d->request, updateProgress_UploadWidget_); | 425 | setSendProgressFunc_GmRequest(d->request, updateProgress_UploadWidget_); |
265 | setUserData_Object(d->request, d); | 426 | setUserData_Object(d->request, d); |
266 | setUrl_GmRequest(d->request, &d->url); | 427 | setUrl_GmRequest(d->request, &d->url); |
267 | if (tabIndex == 0) { | 428 | switch (d->idMode) { |
429 | case defaultForUrl_UploadIdentity: | ||
430 | break; /* GmRequest handles it */ | ||
431 | case none_UploadIdentity: | ||
432 | setIdentity_GmRequest(d->request, NULL); | ||
433 | signOut_GmCerts(certs_App(), url_GmRequest(d->request)); | ||
434 | break; | ||
435 | case dropdown_UploadIdentity: { | ||
436 | iGmIdentity *ident = findIdentity_GmCerts(certs_App(), &d->idFingerprint); | ||
437 | setIdentity_GmRequest(d->request, ident); | ||
438 | signIn_GmCerts(certs_App(), ident, url_GmRequest(d->request)); | ||
439 | break; | ||
440 | } | ||
441 | } | ||
442 | if (isText) { | ||
268 | /* Uploading text. */ | 443 | /* Uploading text. */ |
269 | setTitanData_GmRequest(d->request, | 444 | setTitanData_GmRequest(d->request, |
270 | collectNewCStr_String("text/plain"), | 445 | collectNewCStr_String("text/plain"), |
@@ -312,6 +487,7 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
312 | d->request = NULL; /* DocumentWidget has it now. */ | 487 | d->request = NULL; /* DocumentWidget has it now. */ |
313 | } | 488 | } |
314 | setupSheetTransition_Mobile(w, iFalse); | 489 | setupSheetTransition_Mobile(w, iFalse); |
490 | releaseFile_UploadWidget_(d); | ||
315 | destroy_Widget(w); | 491 | destroy_Widget(w); |
316 | return iTrue; | 492 | return iTrue; |
317 | } | 493 | } |
@@ -321,34 +497,36 @@ static iBool processEvent_UploadWidget_(iUploadWidget *d, const SDL_Event *ev) { | |||
321 | refresh_Widget(w); | 497 | refresh_Widget(w); |
322 | return iTrue; | 498 | return iTrue; |
323 | } | 499 | } |
500 | else if (isCommand_Widget(w, ev, "upload.pickfile")) { | ||
501 | #if defined (iPlatformAppleMobile) | ||
502 | if (hasLabel_Command(cmd, "path")) { | ||
503 | releaseFile_UploadWidget_(d); | ||
504 | set_String(&d->filePath, collect_String(suffix_Command(cmd, "path"))); | ||
505 | updateFileInfo_UploadWidget_(d); | ||
506 | } | ||
507 | else { | ||
508 | pickFile_iOS(format_CStr("upload.pickfile ptr:%p", d)); | ||
509 | } | ||
510 | #endif | ||
511 | return iTrue; | ||
512 | } | ||
324 | if (ev->type == SDL_DROPFILE) { | 513 | if (ev->type == SDL_DROPFILE) { |
325 | /* Switch to File tab. */ | 514 | /* Switch to File tab. */ |
326 | iWidget *tabs = findChild_Widget(w, "upload.tabs"); | 515 | iWidget *tabs = findChild_Widget(w, "upload.tabs"); |
327 | showTabPage_Widget(tabs, tabPage_Widget(tabs, 1)); | 516 | showTabPage_Widget(tabs, tabPage_Widget(tabs, 1)); |
517 | releaseFile_UploadWidget_(d); | ||
328 | setCStr_String(&d->filePath, ev->drop.file); | 518 | setCStr_String(&d->filePath, ev->drop.file); |
329 | iFileInfo *info = iClob(new_FileInfo(&d->filePath)); | 519 | updateFileInfo_UploadWidget_(d); |
330 | if (isDirectory_FileInfo(info)) { | ||
331 | makeMessage_Widget("${heading.upload.error.file}", | ||
332 | "${upload.error.directory}", | ||
333 | (iMenuItem[]){ "${dlg.message.ok}", 0, 0, "message.ok" }, 1); | ||
334 | clear_String(&d->filePath); | ||
335 | d->fileSize = 0; | ||
336 | return iTrue; | ||
337 | } | ||
338 | d->fileSize = size_FileInfo(info); | ||
339 | setText_LabelWidget(d->filePathLabel, &d->filePath); | ||
340 | setTextCStr_LabelWidget(d->fileSizeLabel, formatCStrs_Lang("num.bytes.n", d->fileSize)); | ||
341 | setTextCStr_InputWidget(d->mime, mediaType_Path(&d->filePath)); | ||
342 | return iTrue; | 520 | return iTrue; |
343 | } | 521 | } |
344 | return processEvent_Widget(w, ev); | 522 | return processEvent_Widget(w, ev); |
345 | } | 523 | } |
346 | 524 | ||
347 | static void draw_UploadWidget_(const iUploadWidget *d) { | 525 | //static void draw_UploadWidget_(const iUploadWidget *d) { |
348 | draw_Widget(constAs_Widget(d)); | 526 | // draw_Widget(constAs_Widget(d)); |
349 | } | 527 | //} |
350 | 528 | ||
351 | iBeginDefineSubclass(UploadWidget, Widget) | 529 | iBeginDefineSubclass(UploadWidget, Widget) |
352 | .processEvent = (iAny *) processEvent_UploadWidget_, | 530 | .processEvent = (iAny *) processEvent_UploadWidget_, |
353 | .draw = (iAny *) draw_UploadWidget_, | 531 | .draw = draw_Widget, |
354 | iEndDefineSubclass(UploadWidget) | 532 | iEndDefineSubclass(UploadWidget) |
diff --git a/src/ui/util.c b/src/ui/util.c index b6ecc7d5..5b9f15a9 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -44,6 +44,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
44 | # include "../ios.h" | 44 | # include "../ios.h" |
45 | #endif | 45 | #endif |
46 | 46 | ||
47 | #if defined (iPlatformAppleDesktop) | ||
48 | # include "macos.h" | ||
49 | #endif | ||
50 | |||
47 | #include <the_Foundation/math.h> | 51 | #include <the_Foundation/math.h> |
48 | #include <the_Foundation/path.h> | 52 | #include <the_Foundation/path.h> |
49 | #include <SDL_timer.h> | 53 | #include <SDL_timer.h> |
@@ -333,7 +337,7 @@ void setValue_Anim(iAnim *d, float to, uint32_t span) { | |||
333 | } | 337 | } |
334 | 338 | ||
335 | void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { | 339 | void setValueSpeed_Anim(iAnim *d, float to, float unitsPerSecond) { |
336 | if (iAbs(d->to - to) > 0.0001f) { | 340 | if (iAbs(d->to - to) > 0.0001f || !isFinished_Anim(d)) { |
337 | const uint32_t now = SDL_GetTicks(); | 341 | const uint32_t now = SDL_GetTicks(); |
338 | const float from = valueAt_Anim_(d, now); | 342 | const float from = valueAt_Anim_(d, now); |
339 | const float delta = to - from; | 343 | const float delta = to - from; |
@@ -613,6 +617,8 @@ iBool isAction_Widget(const iWidget *d) { | |||
613 | /*-----------------------------------------------------------------------------------------------*/ | 617 | /*-----------------------------------------------------------------------------------------------*/ |
614 | 618 | ||
615 | static iBool isCommandIgnoredByMenus_(const char *cmd) { | 619 | static iBool isCommandIgnoredByMenus_(const char *cmd) { |
620 | if (equal_Command(cmd, "window.focus.lost") || | ||
621 | equal_Command(cmd, "window.focus.gained")) return iTrue; | ||
616 | /* TODO: Perhaps a common way of indicating which commands are notifications and should not | 622 | /* TODO: Perhaps a common way of indicating which commands are notifications and should not |
617 | be reacted to by menus? */ | 623 | be reacted to by menus? */ |
618 | return equal_Command(cmd, "media.updated") || | 624 | return equal_Command(cmd, "media.updated") || |
@@ -683,49 +689,55 @@ static iWidget *makeMenuSeparator_(void) { | |||
683 | return sep; | 689 | return sep; |
684 | } | 690 | } |
685 | 691 | ||
686 | iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | 692 | void makeMenuItems_Widget(iWidget *menu, const iMenuItem *items, size_t n) { |
687 | iWidget *menu = new_Widget(); | ||
688 | setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); | ||
689 | if (deviceType_App() != desktop_AppDeviceType) { | ||
690 | setPadding1_Widget(menu, 2 * gap_UI); | ||
691 | } | ||
692 | else { | ||
693 | setPadding1_Widget(menu, gap_UI / 2); | ||
694 | } | ||
695 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 693 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
696 | int64_t itemFlags = (deviceType_App() != desktop_AppDeviceType ? 0 : 0) | | 694 | int64_t itemFlags = (deviceType_App() != desktop_AppDeviceType ? 0 : 0) | |
697 | (isPortraitPhone ? extraPadding_WidgetFlag : 0); | 695 | (isPortraitPhone ? extraPadding_WidgetFlag : 0); |
698 | setFlags_Widget(menu, | 696 | iBool haveIcons = iFalse; |
699 | keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag | | 697 | iWidget *horizGroup = NULL; |
700 | arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag | | ||
701 | resizeChildrenToWidestChild_WidgetFlag | overflowScrollable_WidgetFlag | | ||
702 | (isPortraitPhone ? drawBackgroundToVerticalSafeArea_WidgetFlag : 0), | ||
703 | iTrue); | ||
704 | if (!isPortraitPhone) { | ||
705 | setFrameColor_Widget(menu, uiSeparator_ColorId); | ||
706 | } | ||
707 | iBool haveIcons = iFalse; | ||
708 | for (size_t i = 0; i < n; ++i) { | 698 | for (size_t i = 0; i < n; ++i) { |
709 | const iMenuItem *item = &items[i]; | 699 | const iMenuItem *item = &items[i]; |
710 | if (equal_CStr(item->label, "---")) { | 700 | if (!item->label) { |
701 | break; | ||
702 | } | ||
703 | const char *labelText = item->label; | ||
704 | if (!startsWith_CStr(labelText, ">>>")) { | ||
705 | horizGroup = NULL; | ||
706 | } | ||
707 | if (equal_CStr(labelText, "---")) { | ||
711 | addChild_Widget(menu, iClob(makeMenuSeparator_())); | 708 | addChild_Widget(menu, iClob(makeMenuSeparator_())); |
712 | } | 709 | } |
713 | else { | 710 | else { |
714 | iBool isInfo = iFalse; | 711 | iBool isInfo = iFalse; |
715 | const char *labelText = item->label; | 712 | iBool isDisabled = iFalse; |
713 | if (startsWith_CStr(labelText, ">>>")) { | ||
714 | labelText += 3; | ||
715 | if (!horizGroup) { | ||
716 | horizGroup = makeHDiv_Widget(); | ||
717 | setFlags_Widget(horizGroup, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
718 | setFlags_Widget(horizGroup, arrangeHeight_WidgetFlag, iTrue); | ||
719 | addChild_Widget(menu, iClob(horizGroup)); | ||
720 | } | ||
721 | } | ||
716 | if (startsWith_CStr(labelText, "```")) { | 722 | if (startsWith_CStr(labelText, "```")) { |
717 | labelText += 3; | 723 | labelText += 3; |
718 | isInfo = iTrue; | 724 | isInfo = iTrue; |
719 | } | 725 | } |
726 | if (startsWith_CStr(labelText, "///")) { | ||
727 | labelText += 3; | ||
728 | isDisabled = iTrue; | ||
729 | } | ||
720 | iLabelWidget *label = addChildFlags_Widget( | 730 | iLabelWidget *label = addChildFlags_Widget( |
721 | menu, | 731 | horizGroup ? horizGroup : menu, |
722 | iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), | 732 | iClob(newKeyMods_LabelWidget(labelText, item->key, item->kmods, item->command)), |
723 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | | 733 | noBackground_WidgetFlag | frameless_WidgetFlag | alignLeft_WidgetFlag | |
724 | drawKey_WidgetFlag | itemFlags); | 734 | drawKey_WidgetFlag | itemFlags); |
725 | setWrap_LabelWidget(label, isInfo); | 735 | setWrap_LabelWidget(label, isInfo); |
726 | haveIcons |= checkIcon_LabelWidget(label); | 736 | haveIcons |= checkIcon_LabelWidget(label); |
727 | updateSize_LabelWidget(label); /* drawKey was set */ | 737 | updateSize_LabelWidget(label); /* drawKey was set */ |
738 | setFlags_Widget(as_Widget(label), disabled_WidgetFlag, isDisabled); | ||
728 | if (isInfo) { | 739 | if (isInfo) { |
740 | setFlags_Widget(as_Widget(label), fixedHeight_WidgetFlag, iTrue); /* wrap changes height */ | ||
729 | setTextColor_LabelWidget(label, uiTextAction_ColorId); | 741 | setTextColor_LabelWidget(label, uiTextAction_ColorId); |
730 | } | 742 | } |
731 | } | 743 | } |
@@ -742,18 +754,103 @@ iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | |||
742 | iForEach(ObjectList, i, children_Widget(menu)) { | 754 | iForEach(ObjectList, i, children_Widget(menu)) { |
743 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | 755 | if (isInstance_Object(i.object, &Class_LabelWidget)) { |
744 | iLabelWidget *label = i.object; | 756 | iLabelWidget *label = i.object; |
745 | if (icon_LabelWidget(label) == 0) { | 757 | if (!isWrapped_LabelWidget(label) && icon_LabelWidget(label) == 0) { |
746 | setIcon_LabelWidget(label, ' '); | 758 | setIcon_LabelWidget(label, ' '); |
747 | } | 759 | } |
748 | } | 760 | } |
749 | } | 761 | } |
750 | } | 762 | } |
763 | } | ||
764 | |||
765 | static iArray *deepCopyMenuItems_(iWidget *menu, const iMenuItem *items, size_t n) { | ||
766 | iArray *array = new_Array(sizeof(iMenuItem)); | ||
767 | iString cmd; | ||
768 | init_String(&cmd); | ||
769 | for (size_t i = 0; i < n; i++) { | ||
770 | const iMenuItem *item = &items[i]; | ||
771 | const char *itemCommand = item->command; | ||
772 | #if 0 | ||
773 | if (itemCommand) { | ||
774 | /* Make it appear the command is coming from the right widget. */ | ||
775 | setCStr_String(&cmd, itemCommand); | ||
776 | if (!hasLabel_Command(itemCommand, "ptr")) { | ||
777 | size_t firstSpace = indexOf_String(&cmd, ' '); | ||
778 | iBlock ptr; | ||
779 | init_Block(&ptr, 0); | ||
780 | printf_Block(&ptr, " ptr:%p", menu); | ||
781 | if (firstSpace != iInvalidPos) { | ||
782 | insertData_Block(&cmd.chars, firstSpace, data_Block(&ptr), size_Block(&ptr)); | ||
783 | } | ||
784 | else { | ||
785 | append_Block(&cmd.chars, &ptr); | ||
786 | } | ||
787 | deinit_Block(&ptr); | ||
788 | } | ||
789 | itemCommand = cstr_String(&cmd); | ||
790 | } | ||
791 | #endif | ||
792 | pushBack_Array(array, &(iMenuItem){ | ||
793 | item->label ? iDupStr(item->label) : NULL, | ||
794 | item->key, | ||
795 | item->kmods, | ||
796 | itemCommand ? iDupStr(itemCommand) : NULL /* NOTE: Only works with string commands. */ | ||
797 | }); | ||
798 | } | ||
799 | deinit_String(&cmd); | ||
800 | return array; | ||
801 | } | ||
802 | |||
803 | static void deleteMenuItems_(iArray *items) { | ||
804 | iForEach(Array, i, items) { | ||
805 | iMenuItem *item = i.value; | ||
806 | free((void *) item->label); | ||
807 | free((void *) item->command); | ||
808 | } | ||
809 | delete_Array(items); | ||
810 | } | ||
811 | |||
812 | iWidget *makeMenu_Widget(iWidget *parent, const iMenuItem *items, size_t n) { | ||
813 | iWidget *menu = new_Widget(); | ||
814 | #if defined (iHaveNativeContextMenus) | ||
815 | setFlags_Widget(menu, hidden_WidgetFlag | nativeMenu_WidgetFlag, iTrue); | ||
816 | setUserData_Object(menu, deepCopyMenuItems_(menu, items, n)); | ||
817 | addChild_Widget(parent, menu); | ||
818 | iRelease(menu); /* owned by parent now */ | ||
819 | /* Keyboard shortcuts still need to triggerable via the menu, although the items don't exist. */ { | ||
820 | for (size_t i = 0; i < n; i++) { | ||
821 | const iMenuItem *item = &items[i]; | ||
822 | if (item->key) { | ||
823 | addAction_Widget(menu, item->key, item->kmods, item->command); | ||
824 | } | ||
825 | } | ||
826 | } | ||
827 | #else | ||
828 | /* Non-native custom popup menu. This may still be displayed inside a separate window. */ | ||
829 | setDrawBufferEnabled_Widget(menu, iTrue); | ||
830 | setBackgroundColor_Widget(menu, uiBackgroundMenu_ColorId); | ||
831 | if (deviceType_App() != desktop_AppDeviceType) { | ||
832 | setPadding1_Widget(menu, 2 * gap_UI); | ||
833 | } | ||
834 | else { | ||
835 | setPadding1_Widget(menu, gap_UI / 2); | ||
836 | } | ||
837 | setFlags_Widget(menu, | ||
838 | keepOnTop_WidgetFlag | collapse_WidgetFlag | hidden_WidgetFlag | | ||
839 | arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag | | ||
840 | resizeChildrenToWidestChild_WidgetFlag | overflowScrollable_WidgetFlag | | ||
841 | (isPortraitPhone_App() ? drawBackgroundToVerticalSafeArea_WidgetFlag : 0), | ||
842 | iTrue); | ||
843 | if (!isPortraitPhone_App()) { | ||
844 | setFrameColor_Widget(menu, uiSeparator_ColorId); | ||
845 | } | ||
846 | makeMenuItems_Widget(menu, items, n); | ||
751 | addChild_Widget(parent, menu); | 847 | addChild_Widget(parent, menu); |
752 | iRelease(menu); /* owned by parent now */ | 848 | iRelease(menu); /* owned by parent now */ |
753 | setCommandHandler_Widget(menu, menuHandler_); | 849 | setCommandHandler_Widget(menu, menuHandler_); |
754 | iWidget *cancel = addAction_Widget(menu, SDLK_ESCAPE, 0, "cancel"); | 850 | iWidget *cancel = addAction_Widget(menu, SDLK_ESCAPE, 0, "cancel"); |
755 | setId_Widget(cancel, "menu.cancel"); | 851 | setId_Widget(cancel, "menu.cancel"); |
756 | setFlags_Widget(cancel, disabled_WidgetFlag, iTrue); | 852 | setFlags_Widget(cancel, disabled_WidgetFlag, iTrue); |
853 | #endif | ||
757 | return menu; | 854 | return menu; |
758 | } | 855 | } |
759 | 856 | ||
@@ -761,7 +858,157 @@ void openMenu_Widget(iWidget *d, iInt2 windowCoord) { | |||
761 | openMenuFlags_Widget(d, windowCoord, iTrue); | 858 | openMenuFlags_Widget(d, windowCoord, iTrue); |
762 | } | 859 | } |
763 | 860 | ||
861 | static void updateMenuItemFonts_Widget_(iWidget *d) { | ||
862 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | ||
863 | const iBool isSlidePanel = (flags_Widget(d) & horizontalOffset_WidgetFlag) != 0; | ||
864 | iForEach(ObjectList, i, children_Widget(d)) { | ||
865 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
866 | iLabelWidget *label = i.object; | ||
867 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); | ||
868 | if (isWrapped_LabelWidget(label)) { | ||
869 | continue; | ||
870 | } | ||
871 | if (deviceType_App() == desktop_AppDeviceType) { | ||
872 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); | ||
873 | } | ||
874 | else if (isPortraitPhone) { | ||
875 | if (!isSlidePanel) { | ||
876 | setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); | ||
877 | } | ||
878 | } | ||
879 | else { | ||
880 | setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); | ||
881 | } | ||
882 | } | ||
883 | else if (childCount_Widget(i.object)) { | ||
884 | updateMenuItemFonts_Widget_(i.object); | ||
885 | } | ||
886 | } | ||
887 | } | ||
888 | |||
889 | iMenuItem *findNativeMenuItem_Widget(iWidget *menu, const char *commandSuffix) { | ||
890 | iAssert(flags_Widget(menu) & nativeMenu_WidgetFlag); | ||
891 | iForEach(Array, i, userData_Object(menu)) { | ||
892 | iMenuItem *item = i.value; | ||
893 | if (item->command && endsWith_Rangecc(range_CStr(item->command), commandSuffix)) { | ||
894 | return item; | ||
895 | } | ||
896 | } | ||
897 | return NULL; | ||
898 | } | ||
899 | |||
900 | void setPrefix_NativeMenuItem(iMenuItem *item, const char *prefix, iBool set) { | ||
901 | if (!item->label) { | ||
902 | return; | ||
903 | } | ||
904 | const iBool hasPrefix = startsWith_CStr(item->label, prefix); | ||
905 | if (hasPrefix && !set) { | ||
906 | char *label = iDupStr(item->label + 3); | ||
907 | free((char *) item->label); | ||
908 | item->label = label; | ||
909 | } | ||
910 | else if (!hasPrefix && set) { | ||
911 | char *label = malloc(strlen(item->label) + 4); | ||
912 | memcpy(label, prefix, 3); | ||
913 | strcpy(label + 3, item->label); | ||
914 | free((char *) item->label); | ||
915 | item->label = label; | ||
916 | } | ||
917 | } | ||
918 | |||
919 | void setSelected_NativeMenuItem(iMenuItem *item, iBool isSelected) { | ||
920 | if (item) { | ||
921 | setPrefix_NativeMenuItem(item, "///", iFalse); | ||
922 | setPrefix_NativeMenuItem(item, "###", isSelected); | ||
923 | } | ||
924 | } | ||
925 | |||
926 | void setDisabled_NativeMenuItem(iMenuItem *item, iBool isDisabled) { | ||
927 | if (item) { | ||
928 | setPrefix_NativeMenuItem(item, "###", iFalse); | ||
929 | setPrefix_NativeMenuItem(item, "///", isDisabled); | ||
930 | } | ||
931 | } | ||
932 | |||
933 | void setLabel_NativeMenuItem(iMenuItem *item, const char *label) { | ||
934 | free((char *) item->label); | ||
935 | item->label = iDupStr(label); | ||
936 | } | ||
937 | |||
938 | void setMenuItemLabel_Widget(iWidget *menu, const char *command, const char *newLabel) { | ||
939 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { | ||
940 | iArray *items = userData_Object(menu); | ||
941 | iAssert(items); | ||
942 | iForEach(Array, i, items) { | ||
943 | iMenuItem *item = i.value; | ||
944 | if (item->command && !iCmpStr(item->command, command)) { | ||
945 | setLabel_NativeMenuItem(item, newLabel); | ||
946 | break; | ||
947 | } | ||
948 | } | ||
949 | } | ||
950 | else { | ||
951 | iLabelWidget *menuItem = findMenuItem_Widget(menu, command); | ||
952 | if (menuItem) { | ||
953 | setTextCStr_LabelWidget(menuItem, newLabel); | ||
954 | checkIcon_LabelWidget(menuItem); | ||
955 | } | ||
956 | } | ||
957 | } | ||
958 | |||
959 | void setMenuItemLabelByIndex_Widget(iWidget *menu, size_t index, const char *newLabel) { | ||
960 | if (!menu) { | ||
961 | return; | ||
962 | } | ||
963 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { | ||
964 | iArray *items = userData_Object(menu); | ||
965 | iAssert(items); | ||
966 | iAssert(index < size_Array(items)); | ||
967 | setLabel_NativeMenuItem(at_Array(items, index), newLabel); | ||
968 | } | ||
969 | else { | ||
970 | iLabelWidget *menuItem = child_Widget(menu, index); | ||
971 | iAssert(isInstance_Object(menuItem, &Class_LabelWidget)); | ||
972 | setTextCStr_LabelWidget(menuItem, newLabel); | ||
973 | checkIcon_LabelWidget(menuItem); | ||
974 | } | ||
975 | } | ||
976 | |||
977 | void unselectAllNativeMenuItems_Widget(iWidget *menu) { | ||
978 | iArray *items = userData_Object(menu); | ||
979 | iAssert(items); | ||
980 | iForEach(Array, i, items) { | ||
981 | setSelected_NativeMenuItem(i.value, iFalse); | ||
982 | } | ||
983 | } | ||
984 | |||
985 | iLocalDef iBool isUsingMenuPopupWindows_(void) { | ||
986 | #if defined (LAGRANGE_ENABLE_POPUP_MENUS) | ||
987 | return deviceType_App() == desktop_AppDeviceType; | ||
988 | #else | ||
989 | return iFalse; | ||
990 | #endif | ||
991 | } | ||
992 | |||
993 | void releaseNativeMenu_Widget(iWidget *d) { | ||
994 | #if defined (iHaveNativeContextMenus) | ||
995 | iArray *items = userData_Object(d); | ||
996 | iAssert(flags_Widget(d) & nativeMenu_WidgetFlag); | ||
997 | iAssert(items); | ||
998 | deleteMenuItems_(items); | ||
999 | setUserData_Object(d, NULL); | ||
1000 | #else | ||
1001 | iUnused(d); | ||
1002 | #endif | ||
1003 | } | ||
1004 | |||
764 | void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | 1005 | void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { |
1006 | #if defined (iHaveNativeContextMenus) | ||
1007 | const iArray *items = userData_Object(d); | ||
1008 | iAssert(flags_Widget(d) & nativeMenu_WidgetFlag); | ||
1009 | iAssert(items); | ||
1010 | showPopupMenu_MacOS(d, windowCoord, constData_Array(items), size_Array(items)); | ||
1011 | #else | ||
765 | const iRect rootRect = rect_Root(d->root); | 1012 | const iRect rootRect = rect_Root(d->root); |
766 | const iInt2 rootSize = rootRect.size; | 1013 | const iInt2 rootSize = rootRect.size; |
767 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | 1014 | const iBool isPortraitPhone = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); |
@@ -773,6 +1020,36 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
773 | processEvents_App(postedEventsOnly_AppEventMode); | 1020 | processEvents_App(postedEventsOnly_AppEventMode); |
774 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); | 1021 | setFlags_Widget(d, hidden_WidgetFlag, iFalse); |
775 | setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); | 1022 | setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); |
1023 | if (isUsingMenuPopupWindows_()) { | ||
1024 | if (postCommands) { | ||
1025 | postCommand_Widget(d, "menu.opened"); | ||
1026 | } | ||
1027 | updateMenuItemFonts_Widget_(d); | ||
1028 | iRoot *oldRoot = current_Root(); | ||
1029 | setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse); | ||
1030 | setUserData_Object(d, parent_Widget(d)); | ||
1031 | removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */ | ||
1032 | const float pixelRatio = get_Window()->pixelRatio; | ||
1033 | iInt2 menuPos = add_I2(get_MainWindow()->place.normalRect.pos, | ||
1034 | divf_I2(sub_I2(windowCoord, divi_I2(gap2_UI, 2)), pixelRatio)); | ||
1035 | arrange_Widget(d); | ||
1036 | /* Check display bounds. */ { | ||
1037 | const iInt2 menuSize = divf_I2(d->rect.size, pixelRatio); | ||
1038 | SDL_Rect displayRect; | ||
1039 | SDL_GetDisplayBounds(SDL_GetWindowDisplayIndex(get_Window()->win), &displayRect); | ||
1040 | menuPos.x = iMin(menuPos.x, displayRect.x + displayRect.w - menuSize.x); | ||
1041 | menuPos.y = iMin(menuPos.y, displayRect.y + displayRect.h - menuSize.y); | ||
1042 | } | ||
1043 | // SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y); | ||
1044 | iWindow *win = newPopup_Window(menuPos, d); | ||
1045 | SDL_SetWindowTitle(win->win, "Menu"); | ||
1046 | addPopup_App(win); /* window takes the widget */ | ||
1047 | SDL_ShowWindow(win->win); | ||
1048 | draw_Window(win); | ||
1049 | setCurrent_Window(mainWindow_App()); | ||
1050 | setCurrent_Root(oldRoot); | ||
1051 | return; | ||
1052 | } | ||
776 | raise_Widget(d); | 1053 | raise_Widget(d); |
777 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); | 1054 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); |
778 | if (isPortraitPhone) { | 1055 | if (isPortraitPhone) { |
@@ -783,32 +1060,11 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
783 | } | 1060 | } |
784 | d->rect.size.x = rootSize.x; | 1061 | d->rect.size.x = rootSize.x; |
785 | } | 1062 | } |
786 | /* Update item fonts. */ { | 1063 | updateMenuItemFonts_Widget_(d); |
787 | iForEach(ObjectList, i, children_Widget(d)) { | ||
788 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
789 | iLabelWidget *label = i.object; | ||
790 | const iBool isCaution = startsWith_String(text_LabelWidget(label), uiTextCaution_ColorEscape); | ||
791 | if (isWrapped_LabelWidget(label)) { | ||
792 | continue; | ||
793 | } | ||
794 | if (deviceType_App() == desktop_AppDeviceType) { | ||
795 | setFont_LabelWidget(label, isCaution ? uiLabelBold_FontId : uiLabel_FontId); | ||
796 | } | ||
797 | else if (isPortraitPhone) { | ||
798 | if (!isSlidePanel) { | ||
799 | setFont_LabelWidget(label, isCaution ? defaultBigBold_FontId : defaultBig_FontId); | ||
800 | } | ||
801 | } | ||
802 | else { | ||
803 | setFont_LabelWidget(label, isCaution ? uiContentBold_FontId : uiContent_FontId); | ||
804 | } | ||
805 | } | ||
806 | } | ||
807 | } | ||
808 | arrange_Widget(d); | 1064 | arrange_Widget(d); |
809 | if (isPortraitPhone) { | 1065 | if (isPortraitPhone) { |
810 | if (isSlidePanel) { | 1066 | if (isSlidePanel) { |
811 | d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos); | 1067 | d->rect.pos = zero_I2(); |
812 | } | 1068 | } |
813 | else { | 1069 | else { |
814 | d->rect.pos = init_I2(0, rootSize.y); | 1070 | d->rect.pos = init_I2(0, rootSize.y); |
@@ -828,7 +1084,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
828 | float l, t, r, b; | 1084 | float l, t, r, b; |
829 | safeAreaInsets_iOS(&l, &t, &r, &b); | 1085 | safeAreaInsets_iOS(&l, &t, &r, &b); |
830 | topExcess += t; | 1086 | topExcess += t; |
831 | bottomExcess += iMax(b, get_Window()->keyboardHeight); | 1087 | bottomExcess += iMax(b, get_MainWindow()->keyboardHeight); |
832 | leftExcess += l; | 1088 | leftExcess += l; |
833 | rightExcess += r; | 1089 | rightExcess += r; |
834 | } | 1090 | } |
@@ -850,12 +1106,28 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
850 | postCommand_Widget(d, "menu.opened"); | 1106 | postCommand_Widget(d, "menu.opened"); |
851 | } | 1107 | } |
852 | setupMenuTransition_Mobile(d, iTrue); | 1108 | setupMenuTransition_Mobile(d, iTrue); |
1109 | #endif | ||
853 | } | 1110 | } |
854 | 1111 | ||
855 | void closeMenu_Widget(iWidget *d) { | 1112 | void closeMenu_Widget(iWidget *d) { |
1113 | if (flags_Widget(d) & nativeMenu_WidgetFlag) { | ||
1114 | return; /* Handled natively. */ | ||
1115 | } | ||
856 | if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) { | 1116 | if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) { |
857 | return; /* Already closed. */ | 1117 | return; /* Already closed. */ |
858 | } | 1118 | } |
1119 | if (isUsingMenuPopupWindows_()) { | ||
1120 | iWindow *win = window_Widget(d); | ||
1121 | iAssert(type_Window(win) == popup_WindowType); | ||
1122 | iWidget *originalParent = userData_Object(d); | ||
1123 | setUserData_Object(d, NULL); | ||
1124 | win->roots[0]->widget = NULL; | ||
1125 | setRoot_Widget(d, originalParent->root); | ||
1126 | addChild_Widget(originalParent, d); | ||
1127 | setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue); | ||
1128 | SDL_HideWindow(win->win); | ||
1129 | collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */ | ||
1130 | } | ||
859 | setFlags_Widget(d, hidden_WidgetFlag, iTrue); | 1131 | setFlags_Widget(d, hidden_WidgetFlag, iTrue); |
860 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); | 1132 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); |
861 | postRefresh_App(); | 1133 | postRefresh_App(); |
@@ -876,10 +1148,27 @@ iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { | |||
876 | } | 1148 | } |
877 | 1149 | ||
878 | void setMenuItemDisabled_Widget(iWidget *menu, const char *command, iBool disable) { | 1150 | void setMenuItemDisabled_Widget(iWidget *menu, const char *command, iBool disable) { |
879 | iLabelWidget *item = findMenuItem_Widget(menu, command); | 1151 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { |
880 | if (item) { | 1152 | setDisabled_NativeMenuItem(findNativeMenuItem_Widget(menu, command), disable); |
881 | setFlags_Widget(as_Widget(item), disabled_WidgetFlag, disable); | 1153 | } |
1154 | else { | ||
1155 | iLabelWidget *item = findMenuItem_Widget(menu, command); | ||
1156 | if (item) { | ||
1157 | setFlags_Widget(as_Widget(item), disabled_WidgetFlag, disable); | ||
1158 | } | ||
1159 | } | ||
1160 | } | ||
1161 | |||
1162 | void setMenuItemDisabledByIndex_Widget(iWidget *menu, size_t index, iBool disable) { | ||
1163 | if (!menu) { | ||
1164 | return; | ||
882 | } | 1165 | } |
1166 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { | ||
1167 | setDisabled_NativeMenuItem(at_Array(userData_Object(menu), index), disable); | ||
1168 | } | ||
1169 | else { | ||
1170 | setFlags_Widget(child_Widget(menu, index), disabled_WidgetFlag, disable); | ||
1171 | } | ||
883 | } | 1172 | } |
884 | 1173 | ||
885 | int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) { | 1174 | int checkContextMenu_Widget(iWidget *menu, const SDL_Event *ev) { |
@@ -904,6 +1193,50 @@ iLabelWidget *makeMenuButton_LabelWidget(const char *label, const iMenuItem *ite | |||
904 | return button; | 1193 | return button; |
905 | } | 1194 | } |
906 | 1195 | ||
1196 | const iString *removeMenuItemLabelPrefixes_String(const iString *d) { | ||
1197 | iString *str = copy_String(d); | ||
1198 | for (;;) { | ||
1199 | if (startsWith_String(str, "###")) { | ||
1200 | remove_Block(&str->chars, 0, 3); | ||
1201 | continue; | ||
1202 | } | ||
1203 | if (startsWith_String(str, "///")) { | ||
1204 | remove_Block(&str->chars, 0, 3); | ||
1205 | continue; | ||
1206 | } | ||
1207 | if (startsWith_String(str, "```")) { | ||
1208 | remove_Block(&str->chars, 0, 3); | ||
1209 | continue; | ||
1210 | } | ||
1211 | break; | ||
1212 | } | ||
1213 | return collect_String(str); | ||
1214 | } | ||
1215 | |||
1216 | void updateDropdownSelection_LabelWidget(iLabelWidget *dropButton, const char *selectedCommand) { | ||
1217 | iWidget *menu = findChild_Widget(as_Widget(dropButton), "menu"); | ||
1218 | if (flags_Widget(menu) & nativeMenu_WidgetFlag) { | ||
1219 | unselectAllNativeMenuItems_Widget(menu); | ||
1220 | iMenuItem *item = findNativeMenuItem_Widget(menu, selectedCommand); | ||
1221 | if (item) { | ||
1222 | setSelected_NativeMenuItem(item, iTrue); | ||
1223 | updateText_LabelWidget(dropButton, | ||
1224 | removeMenuItemLabelPrefixes_String(collectNewCStr_String(item->label))); | ||
1225 | } | ||
1226 | return; | ||
1227 | } | ||
1228 | iForEach(ObjectList, i, children_Widget(menu)) { | ||
1229 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
1230 | iLabelWidget *item = i.object; | ||
1231 | const iBool isSelected = endsWith_String(command_LabelWidget(item), selectedCommand); | ||
1232 | setFlags_Widget(as_Widget(item), selected_WidgetFlag, isSelected); | ||
1233 | if (isSelected) { | ||
1234 | updateText_LabelWidget(dropButton, sourceText_LabelWidget(item)); | ||
1235 | } | ||
1236 | } | ||
1237 | } | ||
1238 | } | ||
1239 | |||
907 | /*-----------------------------------------------------------------------------------------------*/ | 1240 | /*-----------------------------------------------------------------------------------------------*/ |
908 | 1241 | ||
909 | static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { | 1242 | static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { |
@@ -1043,6 +1376,7 @@ iWidget *removeTabPage_Widget(iWidget *tabs, size_t index) { | |||
1043 | } | 1376 | } |
1044 | 1377 | ||
1045 | void resizeToLargestPage_Widget(iWidget *tabs) { | 1378 | void resizeToLargestPage_Widget(iWidget *tabs) { |
1379 | if (!tabs) return; | ||
1046 | // puts("RESIZE TO LARGEST PAGE ..."); | 1380 | // puts("RESIZE TO LARGEST PAGE ..."); |
1047 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); | 1381 | iWidget *pages = findChild_Widget(tabs, "tabs.pages"); |
1048 | iForEach(ObjectList, i, children_Widget(pages)) { | 1382 | iForEach(ObjectList, i, children_Widget(pages)) { |
@@ -1178,11 +1512,23 @@ static void updateValueInputWidth_(iWidget *dlg) { | |||
1178 | dlg->rect.size.x = | 1512 | dlg->rect.size.x = |
1179 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); | 1513 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); |
1180 | } | 1514 | } |
1515 | /* Adjust the maximum number of visible lines. */ | ||
1516 | int footer = 6 * gap_UI + get_MainWindow()->keyboardHeight; | ||
1517 | iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); | ||
1518 | if (buttons) { | ||
1519 | footer += height_Widget(buttons); | ||
1520 | } | ||
1521 | iInputWidget *input = findChild_Widget(dlg, "input"); | ||
1522 | setLineLimits_InputWidget(input, | ||
1523 | 1, | ||
1524 | (bottom_Rect(safeRect_Root(dlg->root)) - footer - | ||
1525 | top_Rect(boundsWithoutVisualOffset_Widget(as_Widget(input)))) / | ||
1526 | lineHeight_Text(font_InputWidget(input))); | ||
1181 | } | 1527 | } |
1182 | 1528 | ||
1183 | iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | 1529 | iBool valueInputHandler_(iWidget *dlg, const char *cmd) { |
1184 | iWidget *ptr = as_Widget(pointer_Command(cmd)); | 1530 | iWidget *ptr = as_Widget(pointer_Command(cmd)); |
1185 | if (equal_Command(cmd, "window.resized")) { | 1531 | if (equal_Command(cmd, "window.resized") || equal_Command(cmd, "keyboard.changed")) { |
1186 | if (isVisible_Widget(dlg)) { | 1532 | if (isVisible_Widget(dlg)) { |
1187 | updateValueInputWidth_(dlg); | 1533 | updateValueInputWidth_(dlg); |
1188 | arrange_Widget(dlg); | 1534 | arrange_Widget(dlg); |
@@ -1198,7 +1544,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1198 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); | 1544 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); |
1199 | setId_Widget(dlg, ""); /* no further commands to emit */ | 1545 | setId_Widget(dlg, ""); /* no further commands to emit */ |
1200 | } | 1546 | } |
1201 | setupSheetTransition_Mobile(dlg, iFalse); | 1547 | setupSheetTransition_Mobile(dlg, top_TransitionDir); |
1202 | destroy_Widget(dlg); | 1548 | destroy_Widget(dlg); |
1203 | return iTrue; | 1549 | return iTrue; |
1204 | } | 1550 | } |
@@ -1207,13 +1553,13 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1207 | else if (equal_Command(cmd, "valueinput.cancel")) { | 1553 | else if (equal_Command(cmd, "valueinput.cancel")) { |
1208 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); | 1554 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); |
1209 | setId_Widget(dlg, ""); /* no further commands to emit */ | 1555 | setId_Widget(dlg, ""); /* no further commands to emit */ |
1210 | setupSheetTransition_Mobile(dlg, iFalse); | 1556 | setupSheetTransition_Mobile(dlg, top_TransitionDir); |
1211 | destroy_Widget(dlg); | 1557 | destroy_Widget(dlg); |
1212 | return iTrue; | 1558 | return iTrue; |
1213 | } | 1559 | } |
1214 | else if (equal_Command(cmd, "valueinput.accept")) { | 1560 | else if (equal_Command(cmd, "valueinput.accept")) { |
1215 | acceptValueInput_(dlg); | 1561 | acceptValueInput_(dlg); |
1216 | setupSheetTransition_Mobile(dlg, iFalse); | 1562 | setupSheetTransition_Mobile(dlg, top_TransitionDir); |
1217 | destroy_Widget(dlg); | 1563 | destroy_Widget(dlg); |
1218 | return iTrue; | 1564 | return iTrue; |
1219 | } | 1565 | } |
@@ -1317,7 +1663,6 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1317 | setText_InputWidget(input, initialValue); | 1663 | setText_InputWidget(input, initialValue); |
1318 | } | 1664 | } |
1319 | setId_Widget(as_Widget(input), "input"); | 1665 | setId_Widget(as_Widget(input), "input"); |
1320 | updateValueInputWidth_(dlg); | ||
1321 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 1666 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
1322 | addChild_Widget(dlg, | 1667 | addChild_Widget(dlg, |
1323 | iClob(makeDialogButtons_Widget( | 1668 | iClob(makeDialogButtons_Widget( |
@@ -1327,10 +1672,20 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1327 | acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), | 1672 | acceptKeyMod_ReturnKeyBehavior(prefs_App()->returnKey), |
1328 | "valueinput.accept" } }, | 1673 | "valueinput.accept" } }, |
1329 | 2))); | 1674 | 2))); |
1330 | finalizeSheet_Mobile(dlg); | 1675 | // finalizeSheet_Mobile(dlg); |
1676 | arrange_Widget(dlg); | ||
1331 | if (parent) { | 1677 | if (parent) { |
1332 | setFocus_Widget(as_Widget(input)); | 1678 | setFocus_Widget(as_Widget(input)); |
1333 | } | 1679 | } |
1680 | /* Check that the top is in the safe area. */ { | ||
1681 | int top = top_Rect(bounds_Widget(dlg)); | ||
1682 | int delta = top - top_Rect(safeRect_Root(dlg->root)); | ||
1683 | if (delta < 0) { | ||
1684 | dlg->rect.pos.y -= delta; | ||
1685 | } | ||
1686 | } | ||
1687 | updateValueInputWidth_(dlg); | ||
1688 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag | top_TransitionDir); | ||
1334 | return dlg; | 1689 | return dlg; |
1335 | } | 1690 | } |
1336 | 1691 | ||
@@ -1377,6 +1732,28 @@ iWidget *makeMessage_Widget(const char *title, const char *msg, const iMenuItem | |||
1377 | iWidget *makeQuestion_Widget(const char *title, const char *msg, | 1732 | iWidget *makeQuestion_Widget(const char *title, const char *msg, |
1378 | const iMenuItem *items, size_t numItems) { | 1733 | const iMenuItem *items, size_t numItems) { |
1379 | processEvents_App(postedEventsOnly_AppEventMode); | 1734 | processEvents_App(postedEventsOnly_AppEventMode); |
1735 | if (isUsingPanelLayout_Mobile()) { | ||
1736 | iArray *panelItems = collectNew_Array(sizeof(iMenuItem)); | ||
1737 | pushBackN_Array(panelItems, (iMenuItem[]){ | ||
1738 | { format_CStr("title text:%s", title) }, | ||
1739 | { format_CStr("label text:%s", msg) }, | ||
1740 | { NULL } | ||
1741 | }, 3); | ||
1742 | for (size_t i = 0; i < numItems; i++) { | ||
1743 | const iMenuItem *item = &items[i]; | ||
1744 | const char first = item->label[0]; | ||
1745 | if (first == '*' || first == '&') { | ||
1746 | insert_Array(panelItems, size_Array(panelItems) - 1, | ||
1747 | &(iMenuItem){ format_CStr("button selected:%d text:%s", | ||
1748 | first == '&' ? 1 : 0, item->label + 1), | ||
1749 | 0, 0, item->command }); | ||
1750 | } | ||
1751 | } | ||
1752 | iWidget *dlg = makePanels_Mobile("", data_Array(panelItems), items, numItems); | ||
1753 | setCommandHandler_Widget(dlg, messageHandler_); | ||
1754 | setupSheetTransition_Mobile(dlg, iTrue); | ||
1755 | return dlg; | ||
1756 | } | ||
1380 | iWidget *dlg = makeSheet_Widget(""); | 1757 | iWidget *dlg = makeSheet_Widget(""); |
1381 | setCommandHandler_Widget(dlg, messageHandler_); | 1758 | setCommandHandler_Widget(dlg, messageHandler_); |
1382 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag); | 1759 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag); |
@@ -1404,7 +1781,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
1404 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1781 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
1405 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't | 1782 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't |
1406 | be arranged correctly unless it's here. */ | 1783 | be arranged correctly unless it's here. */ |
1407 | finalizeSheet_Mobile(dlg); | 1784 | setupSheetTransition_Mobile(dlg, iTrue); |
1408 | return dlg; | 1785 | return dlg; |
1409 | } | 1786 | } |
1410 | 1787 | ||
@@ -1468,6 +1845,15 @@ iWidget *makeTwoColumns_Widget(iWidget **headings, iWidget **values) { | |||
1468 | return page; | 1845 | return page; |
1469 | } | 1846 | } |
1470 | 1847 | ||
1848 | iLabelWidget *dialogAcceptButton_Widget(const iWidget *d) { | ||
1849 | iWidget *buttonParent = findChild_Widget(d, "dialogbuttons"); | ||
1850 | if (!buttonParent) { | ||
1851 | iAssert(isUsingPanelLayout_Mobile()); | ||
1852 | buttonParent = findChild_Widget(d, "panel.back"); | ||
1853 | } | ||
1854 | return (iLabelWidget *) lastChild_Widget(buttonParent); | ||
1855 | } | ||
1856 | |||
1471 | iWidget *appendTwoColumnTabPage_Widget(iWidget *tabs, const char *title, int shortcut, iWidget **headings, | 1857 | iWidget *appendTwoColumnTabPage_Widget(iWidget *tabs, const char *title, int shortcut, iWidget **headings, |
1472 | iWidget **values) { | 1858 | iWidget **values) { |
1473 | /* TODO: Use `makeTwoColumnWidget_()`, see above. */ | 1859 | /* TODO: Use `makeTwoColumnWidget_()`, see above. */ |
@@ -1506,7 +1892,7 @@ static void addRadioButton_(iWidget *parent, const char *id, const char *label, | |||
1506 | id); | 1892 | id); |
1507 | } | 1893 | } |
1508 | 1894 | ||
1509 | static void addFontButtons_(iWidget *parent, const char *id) { | 1895 | static const iArray *makeFontItems_(const char *id) { |
1510 | const struct { | 1896 | const struct { |
1511 | const char * name; | 1897 | const char * name; |
1512 | enum iTextFont cfgId; | 1898 | enum iTextFont cfgId; |
@@ -1518,7 +1904,7 @@ static void addFontButtons_(iWidget *parent, const char *id) { | |||
1518 | { "Tinos", tinos_TextFont }, | 1904 | { "Tinos", tinos_TextFont }, |
1519 | { "---", -1 }, | 1905 | { "---", -1 }, |
1520 | { "Iosevka", iosevka_TextFont } }; | 1906 | { "Iosevka", iosevka_TextFont } }; |
1521 | iArray *items = new_Array(sizeof(iMenuItem)); | 1907 | iArray *items = collectNew_Array(sizeof(iMenuItem)); |
1522 | iForIndices(i, fonts) { | 1908 | iForIndices(i, fonts) { |
1523 | pushBack_Array(items, | 1909 | pushBack_Array(items, |
1524 | &(iMenuItem){ fonts[i].name, | 1910 | &(iMenuItem){ fonts[i].name, |
@@ -1528,11 +1914,18 @@ static void addFontButtons_(iWidget *parent, const char *id) { | |||
1528 | ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) | 1914 | ? format_CStr("!%s.set arg:%d", id, fonts[i].cfgId) |
1529 | : NULL }); | 1915 | : NULL }); |
1530 | } | 1916 | } |
1531 | iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", data_Array(items), size_Array(items)); | 1917 | pushBack_Array(items, &(iMenuItem){ NULL }); /* terminator */ |
1532 | setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); | 1918 | return items; |
1919 | } | ||
1920 | |||
1921 | static void addFontButtons_(iWidget *parent, const char *id) { | ||
1922 | const iArray *items = makeFontItems_(id); | ||
1923 | iLabelWidget *button = makeMenuButton_LabelWidget("Source Sans 3", | ||
1924 | constData_Array(items), size_Array(items)); | ||
1925 | setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), | ||
1926 | uiBackgroundMenu_ColorId); | ||
1533 | setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); | 1927 | setId_Widget(as_Widget(button), format_CStr("prefs.%s", id)); |
1534 | addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); | 1928 | addChildFlags_Widget(parent, iClob(button), alignLeft_WidgetFlag); |
1535 | delete_Array(items); | ||
1536 | } | 1929 | } |
1537 | 1930 | ||
1538 | #if 0 | 1931 | #if 0 |
@@ -1607,15 +2000,27 @@ iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *va | |||
1607 | return input; | 2000 | return input; |
1608 | } | 2001 | } |
1609 | 2002 | ||
2003 | static void addDialogPadding_(iWidget *headings, iWidget *values) { | ||
2004 | const int bigGap = lineHeight_Text(uiLabel_FontId) * 3 / 4; | ||
2005 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | ||
2006 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
2007 | } | ||
2008 | |||
1610 | static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values, | 2009 | static void addPrefsInputWithHeading_(iWidget *headings, iWidget *values, |
1611 | const char *id, iInputWidget *input) { | 2010 | const char *id, iInputWidget *input) { |
1612 | addDialogInputWithHeading_(headings, values, format_CStr("${%s}", id), id, input); | 2011 | addDialogInputWithHeading_(headings, values, format_CStr("${%s}", id), id, input); |
1613 | } | 2012 | } |
1614 | 2013 | ||
1615 | static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { | 2014 | static void addDialogToggle_(iWidget *headings, iWidget *values, |
2015 | const char *heading, const char *toggleId) { | ||
2016 | addChild_Widget(headings, iClob(makeHeading_Widget(heading))); | ||
2017 | addChild_Widget(values, iClob(makeToggle_Widget(toggleId))); | ||
2018 | } | ||
2019 | |||
2020 | size_t findWidestLabel_MenuItem(const iMenuItem *items, size_t num) { | ||
1616 | int widest = 0; | 2021 | int widest = 0; |
1617 | size_t widestPos = iInvalidPos; | 2022 | size_t widestPos = iInvalidPos; |
1618 | for (size_t i = 0; i < num; i++) { | 2023 | for (size_t i = 0; i < num && items[i].label; i++) { |
1619 | const int width = | 2024 | const int width = |
1620 | measure_Text(uiLabel_FontId, | 2025 | measure_Text(uiLabel_FontId, |
1621 | translateCStr_Lang(items[i].label)) | 2026 | translateCStr_Lang(items[i].label)) |
@@ -1628,7 +2033,260 @@ static size_t findWidestItemLabel_(const iMenuItem *items, size_t num) { | |||
1628 | return widestPos; | 2033 | return widestPos; |
1629 | } | 2034 | } |
1630 | 2035 | ||
2036 | iChar removeIconPrefix_String(iString *d) { | ||
2037 | if (isEmpty_String(d)) { | ||
2038 | return 0; | ||
2039 | } | ||
2040 | iStringConstIterator iter; | ||
2041 | init_StringConstIterator(&iter, d); | ||
2042 | iChar icon = iter.value; | ||
2043 | next_StringConstIterator(&iter); | ||
2044 | if (iter.value == ' ' && icon >= 0x100) { | ||
2045 | remove_Block(&d->chars, 0, iter.next - constBegin_String(d)); | ||
2046 | return icon; | ||
2047 | } | ||
2048 | return 0; | ||
2049 | } | ||
2050 | |||
2051 | iWidget *makeDialog_Widget(const char *id, | ||
2052 | const iMenuItem *itemsNullTerminated, | ||
2053 | const iMenuItem *actions, size_t numActions) { | ||
2054 | iWidget *dlg = makeSheet_Widget(id); | ||
2055 | /* TODO: Construct desktop dialogs using NULL-terminated item arrays, like mobile panels. */ | ||
2056 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | ||
2057 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, numActions))); | ||
2058 | addChild_Widget(dlg->root->widget, iClob(dlg)); | ||
2059 | arrange_Widget(dlg); | ||
2060 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2061 | return dlg; | ||
2062 | } | ||
2063 | |||
1631 | iWidget *makePreferences_Widget(void) { | 2064 | iWidget *makePreferences_Widget(void) { |
2065 | /* Common items. */ | ||
2066 | const iMenuItem langItems[] = { { "${lang.de} - de", 0, 0, "uilang id:de" }, | ||
2067 | { "${lang.en} - en", 0, 0, "uilang id:en" }, | ||
2068 | { "${lang.es} - es", 0, 0, "uilang id:es" }, | ||
2069 | { "${lang.fi} - fi", 0, 0, "uilang id:fi" }, | ||
2070 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, | ||
2071 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, | ||
2072 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, | ||
2073 | { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, | ||
2074 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, | ||
2075 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, | ||
2076 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, | ||
2077 | { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" }, | ||
2078 | { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" }, | ||
2079 | { NULL } }; | ||
2080 | const iMenuItem returnKeyBehaviors[] = { | ||
2081 | { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape shift_Icon return_Icon | ||
2082 | restore_ColorEscape | ||
2083 | " ${prefs.returnkey.accept} " uiTextAction_ColorEscape return_Icon, | ||
2084 | 0, | ||
2085 | 0, | ||
2086 | format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) }, | ||
2087 | { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape | ||
2088 | " ${prefs.returnkey.accept} " uiTextAction_ColorEscape shift_Icon return_Icon, | ||
2089 | 0, | ||
2090 | 0, | ||
2091 | format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) }, | ||
2092 | { "${prefs.returnkey.linebreak} " uiTextAction_ColorEscape return_Icon restore_ColorEscape | ||
2093 | " ${prefs.returnkey.accept} " uiTextAction_ColorEscape | ||
2094 | #if defined (iPlatformApple) | ||
2095 | "\u2318" return_Icon, | ||
2096 | #else | ||
2097 | "Ctrl" return_Icon, | ||
2098 | #endif | ||
2099 | 0, | ||
2100 | 0, | ||
2101 | format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, | ||
2102 | { NULL } | ||
2103 | }; | ||
2104 | iMenuItem docThemes[2][max_GmDocumentTheme + 1]; | ||
2105 | for (int i = 0; i < 2; ++i) { | ||
2106 | const iBool isDark = (i == 0); | ||
2107 | const char *mode = isDark ? "dark" : "light"; | ||
2108 | const iMenuItem items[max_GmDocumentTheme + 1] = { | ||
2109 | { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) }, | ||
2110 | { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) }, | ||
2111 | { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) }, | ||
2112 | { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) }, | ||
2113 | { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) }, | ||
2114 | { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) }, | ||
2115 | { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) }, | ||
2116 | { NULL } | ||
2117 | }; | ||
2118 | memcpy(docThemes[i], items, sizeof(items)); | ||
2119 | } | ||
2120 | const iMenuItem imgStyles[] = { | ||
2121 | { "${prefs.imagestyle.original}", 0, 0, format_CStr("imagestyle.set arg:%d", original_ImageStyle) }, | ||
2122 | { "${prefs.imagestyle.grayscale}", 0, 0, format_CStr("imagestyle.set arg:%d", grayscale_ImageStyle) }, | ||
2123 | { "${prefs.imagestyle.bgfg}", 0, 0, format_CStr("imagestyle.set arg:%d", bgFg_ImageStyle) }, | ||
2124 | { "${prefs.imagestyle.text}", 0, 0, format_CStr("imagestyle.set arg:%d", textColorized_ImageStyle) }, | ||
2125 | { "${prefs.imagestyle.preformat}", 0, 0, format_CStr("imagestyle.set arg:%d", preformatColorized_ImageStyle) }, | ||
2126 | { NULL } | ||
2127 | }; | ||
2128 | /* Create the Preferences UI. */ | ||
2129 | if (isUsingPanelLayout_Mobile()) { | ||
2130 | const iMenuItem pinSplitItems[] = { | ||
2131 | { "button id:prefs.pinsplit.0 label:prefs.pinsplit.none", 0, 0, "pinsplit.set arg:0" }, | ||
2132 | { "button id:prefs.pinsplit.1 label:prefs.pinsplit.left", 0, 0, "pinsplit.set arg:1" }, | ||
2133 | { "button id:prefs.pinsplit.2 label:prefs.pinsplit.right", 0, 0, "pinsplit.set arg:2" }, | ||
2134 | { NULL } | ||
2135 | }; | ||
2136 | const iMenuItem themeItems[] = { | ||
2137 | { "button id:prefs.theme.0 label:prefs.theme.black", 0, 0, "theme.set arg:0" }, | ||
2138 | { "button id:prefs.theme.1 label:prefs.theme.dark", 0, 0, "theme.set arg:1" }, | ||
2139 | { "button id:prefs.theme.2 label:prefs.theme.light", 0, 0, "theme.set arg:2" }, | ||
2140 | { "button id:prefs.theme.3 label:prefs.theme.white", 0, 0, "theme.set arg:3" }, | ||
2141 | { NULL } | ||
2142 | }; | ||
2143 | const iMenuItem accentItems[] = { | ||
2144 | { "button id:prefs.accent.0 label:prefs.accent.teal", 0, 0, "accent.set arg:0" }, | ||
2145 | { "button id:prefs.accent.1 label:prefs.accent.orange", 0, 0, "accent.set arg:1" }, | ||
2146 | { NULL } | ||
2147 | }; | ||
2148 | const iMenuItem satItems[] = { | ||
2149 | { "button id:prefs.saturation.3 text:100 %", 0, 0, "saturation.set arg:100" }, | ||
2150 | { "button id:prefs.saturation.2 text:66 %", 0, 0, "saturation.set arg:66" }, | ||
2151 | { "button id:prefs.saturation.1 text:33 %", 0, 0, "saturation.set arg:33" }, | ||
2152 | { "button id:prefs.saturation.0 text:0 %", 0, 0, "saturation.set arg:0" }, | ||
2153 | { NULL } | ||
2154 | }; | ||
2155 | const iMenuItem monoFontItems[] = { | ||
2156 | { "button id:prefs.mono.gemini" }, | ||
2157 | { "button id:prefs.mono.gopher" }, | ||
2158 | { NULL } | ||
2159 | }; | ||
2160 | const iMenuItem boldLinkItems[] = { | ||
2161 | { "button id:prefs.boldlink.dark" }, | ||
2162 | { "button id:prefs.boldlink.light" }, | ||
2163 | { NULL } | ||
2164 | }; | ||
2165 | const iMenuItem lineWidthItems[] = { | ||
2166 | { "button id:prefs.linewidth.30 text:\u20132", 0, 0, "linewidth.set arg:30" }, | ||
2167 | { "button id:prefs.linewidth.34 text:\u20131", 0, 0, "linewidth.set arg:34" }, | ||
2168 | { "button id:prefs.linewidth.38 label:prefs.linewidth.normal", 0, 0, "linewidth.set arg:38" }, | ||
2169 | { "button id:prefs.linewidth.43 text:+1", 0, 0, "linewidth.set arg:43" }, | ||
2170 | { "button id:prefs.linewidth.48 text:+2", 0, 0, "linewidth.set arg:48" }, | ||
2171 | { "button id:prefs.linewidth.1000 label:prefs.linewidth.fill", 0, 0, "linewidth.set arg:1000" }, | ||
2172 | { NULL } | ||
2173 | }; | ||
2174 | const iMenuItem quoteItems[] = { | ||
2175 | { "button id:prefs.quoteicon.1 label:prefs.quoteicon.icon", 0, 0, "quoteicon.set arg:1" }, | ||
2176 | { "button id:prefs.quoteicon.0 label:prefs.quoteicon.line", 0, 0, "quoteicon.set arg:0" }, | ||
2177 | { NULL } | ||
2178 | }; | ||
2179 | const iMenuItem generalPanelItems[] = { | ||
2180 | { "title id:heading.prefs.general" }, | ||
2181 | { "heading text:${prefs.searchurl}" }, | ||
2182 | { "input id:prefs.searchurl url:1 noheading:1" }, | ||
2183 | { "padding" }, | ||
2184 | { "toggle id:prefs.archive.openindex" }, | ||
2185 | { "radio device:1 id:prefs.pinsplit", 0, 0, (const void *) pinSplitItems }, | ||
2186 | { "padding" }, | ||
2187 | { "dropdown id:prefs.uilang", 0, 0, (const void *) langItems }, | ||
2188 | { NULL } | ||
2189 | }; | ||
2190 | const iMenuItem uiPanelItems[] = { | ||
2191 | { "title id:heading.prefs.interface" }, | ||
2192 | { "dropdown device:1 id:prefs.returnkey", 0, 0, (const void *) returnKeyBehaviors }, | ||
2193 | { "padding device:1" }, | ||
2194 | { "toggle id:prefs.hoverlink" }, | ||
2195 | { "toggle device:2 id:prefs.hidetoolbarscroll" }, | ||
2196 | { "heading id:heading.prefs.sizing" }, | ||
2197 | { "input id:prefs.uiscale maxlen:8" }, | ||
2198 | { NULL } | ||
2199 | }; | ||
2200 | const iMenuItem colorPanelItems[] = { | ||
2201 | { "title id:heading.prefs.colors" }, | ||
2202 | { "heading id:heading.prefs.uitheme" }, | ||
2203 | { "toggle id:prefs.ostheme" }, | ||
2204 | { "radio id:prefs.theme", 0, 0, (const void *) themeItems }, | ||
2205 | { "radio id:prefs.accent", 0, 0, (const void *) accentItems }, | ||
2206 | { "heading id:heading.prefs.pagecontent" }, | ||
2207 | { "dropdown id:prefs.doctheme.dark", 0, 0, (const void *) docThemes[0] }, | ||
2208 | { "dropdown id:prefs.doctheme.light", 0, 0, (const void *) docThemes[1] }, | ||
2209 | { "radio id:prefs.saturation", 0, 0, (const void *) satItems }, | ||
2210 | { "padding" }, | ||
2211 | { "dropdown id:prefs.imagestyle", 0, 0, (const void *) imgStyles }, | ||
2212 | { NULL } | ||
2213 | }; | ||
2214 | const iMenuItem fontPanelItems[] = { | ||
2215 | { "title id:heading.prefs.fonts" }, | ||
2216 | { "dropdown id:prefs.headingfont", 0, 0, (const void *) constData_Array(makeFontItems_("headingfont")) }, | ||
2217 | { "dropdown id:prefs.font", 0, 0, (const void *) constData_Array(makeFontItems_("font")) }, | ||
2218 | { "buttons id:prefs.mono", 0, 0, (const void *) monoFontItems }, | ||
2219 | { "buttons id:prefs.boldlink", 0, 0, (const void *) boldLinkItems }, | ||
2220 | { NULL } | ||
2221 | }; | ||
2222 | const iMenuItem stylePanelItems[] = { | ||
2223 | { "title id:heading.prefs.style" }, | ||
2224 | { "radio id:prefs.linewidth", 0, 0, (const void *) lineWidthItems }, | ||
2225 | { "padding" }, | ||
2226 | { "input id:prefs.linespacing maxlen:5" }, | ||
2227 | { "radio id:prefs.quoteicon", 0, 0, (const void *) quoteItems }, | ||
2228 | { "padding" }, | ||
2229 | { "toggle id:prefs.biglede" }, | ||
2230 | { "toggle id:prefs.plaintext.wrap" }, | ||
2231 | { "toggle id:prefs.collapsepreonload" }, | ||
2232 | { "toggle id:prefs.sideicon" }, | ||
2233 | { "toggle id:prefs.centershort" }, | ||
2234 | { NULL } | ||
2235 | }; | ||
2236 | const iMenuItem networkPanelItems[] = { | ||
2237 | { "title id:heading.prefs.network" }, | ||
2238 | { "toggle id:prefs.decodeurls" }, | ||
2239 | { "padding" }, | ||
2240 | { "input id:prefs.cachesize maxlen:4 selectall:1 unit:mb" }, | ||
2241 | { "input id:prefs.memorysize maxlen:4 selectall:1 unit:mb" }, | ||
2242 | { "heading text:${prefs.proxy.gemini}" }, | ||
2243 | { "input id:prefs.proxy.gemini noheading:1" }, | ||
2244 | { "heading text:${prefs.proxy.gopher}" }, | ||
2245 | { "input id:prefs.proxy.gopher noheading:1" }, | ||
2246 | { "heading text:${prefs.proxy.http}" }, | ||
2247 | { "input id:prefs.proxy.http noheading:1" }, | ||
2248 | { NULL } | ||
2249 | }; | ||
2250 | const iMenuItem identityPanelItems[] = { | ||
2251 | { "title id:sidebar.identities" }, | ||
2252 | { NULL } | ||
2253 | }; | ||
2254 | iString *aboutText = collectNew_String(); { | ||
2255 | setCStr_String(aboutText, "Lagrange " LAGRANGE_APP_VERSION); | ||
2256 | #if defined (iPlatformAppleMobile) | ||
2257 | appendFormat_String(aboutText, " (" LAGRANGE_IOS_VERSION ") %s" LAGRANGE_IOS_BUILD_DATE, | ||
2258 | escape_Color(uiTextDim_ColorId)); | ||
2259 | #endif | ||
2260 | } | ||
2261 | const iMenuItem aboutPanelItems[] = { | ||
2262 | { format_CStr("heading text:%s", cstr_String(aboutText)) }, | ||
2263 | { "button text:" clock_Icon " ${menu.releasenotes}", 0, 0, "!open url:about:version" }, | ||
2264 | { "button text:" globe_Icon " ${menu.website}", 0, 0, "!open url:https://gmi.skyjake.fi/lagrange" }, | ||
2265 | { "button text:" envelope_Icon " @jk@skyjake.fi", 0, 0, "!open url:https://skyjake.fi/@jk" }, | ||
2266 | { "padding" }, | ||
2267 | { "button text:" info_Icon " ${menu.aboutpages}", 0, 0, "!open url:about:about" }, | ||
2268 | { "button text:" bug_Icon " ${menu.debug}", 0, 0, "!open url:about:debug" }, | ||
2269 | { NULL } | ||
2270 | }; | ||
2271 | iWidget *dlg = makePanels_Mobile("prefs", (iMenuItem[]){ | ||
2272 | { "title id:heading.settings" }, | ||
2273 | { "panel text:" gear_Icon " ${heading.prefs.general}", 0, 0, (const void *) generalPanelItems }, | ||
2274 | { "panel icon:0x1f5a7 id:heading.prefs.network", 0, 0, (const void *) networkPanelItems }, | ||
2275 | { "panel text:" person_Icon " ${sidebar.identities}", 0, 0, (const void *) identityPanelItems }, | ||
2276 | { "padding" }, | ||
2277 | { "panel icon:0x1f4f1 id:heading.prefs.interface", 0, 0, (const void *) uiPanelItems }, | ||
2278 | { "panel icon:0x1f3a8 id:heading.prefs.colors", 0, 0, (const void *) colorPanelItems }, | ||
2279 | { "panel icon:0x1f5da id:heading.prefs.fonts", 0, 0, (const void *) fontPanelItems }, | ||
2280 | { "panel icon:0x1f660 id:heading.prefs.style", 0, 0, (const void *) stylePanelItems }, | ||
2281 | { "padding" }, | ||
2282 | { "button text:" info_Icon " ${menu.help}", 0, 0, "!open url:about:help" }, | ||
2283 | { "padding" }, | ||
2284 | { "panel text:" planet_Icon " ${menu.about}", 0, 0, (const void *) aboutPanelItems }, | ||
2285 | { NULL } | ||
2286 | }, NULL, 0); | ||
2287 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2288 | return dlg; | ||
2289 | } | ||
1632 | iWidget *dlg = makeSheet_Widget("prefs"); | 2290 | iWidget *dlg = makeSheet_Widget("prefs"); |
1633 | addChildFlags_Widget(dlg, | 2291 | addChildFlags_Widget(dlg, |
1634 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)), | 2292 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.prefs}", NULL)), |
@@ -1637,7 +2295,6 @@ iWidget *makePreferences_Widget(void) { | |||
1637 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); | 2295 | setBackgroundColor_Widget(findChild_Widget(tabs, "tabs.buttons"), uiBackgroundSidebar_ColorId); |
1638 | setId_Widget(tabs, "prefs.tabs"); | 2296 | setId_Widget(tabs, "prefs.tabs"); |
1639 | iWidget *headings, *values; | 2297 | iWidget *headings, *values; |
1640 | const int bigGap = lineHeight_Text(uiLabel_FontId) * 3 / 4; | ||
1641 | /* General preferences. */ { | 2298 | /* General preferences. */ { |
1642 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.general}", '1', &headings, &values); | 2299 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.general}", '1', &headings, &values); |
1643 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 2300 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
@@ -1646,12 +2303,9 @@ iWidget *makePreferences_Widget(void) { | |||
1646 | iInputWidget *searchUrl; | 2303 | iInputWidget *searchUrl; |
1647 | addPrefsInputWithHeading_(headings, values, "prefs.searchurl", iClob(searchUrl = new_InputWidget(0))); | 2304 | addPrefsInputWithHeading_(headings, values, "prefs.searchurl", iClob(searchUrl = new_InputWidget(0))); |
1648 | setUrlContent_InputWidget(searchUrl, iTrue); | 2305 | setUrlContent_InputWidget(searchUrl, iTrue); |
1649 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 2306 | addDialogPadding_(headings, values); |
1650 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 2307 | addDialogToggle_(headings, values, "${prefs.hoverlink}", "prefs.hoverlink"); |
1651 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hoverlink}"))); | 2308 | addDialogToggle_(headings, values, "${prefs.archive.openindex}", "prefs.archive.openindex"); |
1652 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); | ||
1653 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); | ||
1654 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.archive.openindex"))); | ||
1655 | if (deviceType_App() != phone_AppDeviceType) { | 2309 | if (deviceType_App() != phone_AppDeviceType) { |
1656 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); | 2310 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); |
1657 | iWidget *pinSplit = new_Widget(); | 2311 | iWidget *pinSplit = new_Widget(); |
@@ -1662,28 +2316,12 @@ iWidget *makePreferences_Widget(void) { | |||
1662 | } | 2316 | } |
1663 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2317 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1664 | } | 2318 | } |
1665 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 2319 | addDialogPadding_(headings, values); |
1666 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
1667 | /* UI languages. */ { | 2320 | /* UI languages. */ { |
1668 | iArray *uiLangs = collectNew_Array(sizeof(iMenuItem)); | 2321 | iArray *uiLangs = collectNew_Array(sizeof(iMenuItem)); |
1669 | const iMenuItem langItems[] = { | 2322 | pushBackN_Array(uiLangs, langItems, iElemCount(langItems) - 1); |
1670 | { "${lang.de} - de", 0, 0, "uilang id:de" }, | ||
1671 | { "${lang.en} - en", 0, 0, "uilang id:en" }, | ||
1672 | { "${lang.es} - es", 0, 0, "uilang id:es" }, | ||
1673 | { "${lang.fi} - fi", 0, 0, "uilang id:fi" }, | ||
1674 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, | ||
1675 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, | ||
1676 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, | ||
1677 | { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, | ||
1678 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, | ||
1679 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, | ||
1680 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, | ||
1681 | { "${lang.zh.hans} - zh", 0, 0, "uilang id:zh_Hans" }, | ||
1682 | { "${lang.zh.hant} - zh", 0, 0, "uilang id:zh_Hant" }, | ||
1683 | }; | ||
1684 | pushBackN_Array(uiLangs, langItems, iElemCount(langItems)); | ||
1685 | /* TODO: Add an arrange flag for resizing parent to widest child. */ | 2323 | /* TODO: Add an arrange flag for resizing parent to widest child. */ |
1686 | size_t widestPos = findWidestItemLabel_(data_Array(uiLangs), size_Array(uiLangs)); | 2324 | size_t widestPos = findWidestLabel_MenuItem(data_Array(uiLangs), size_Array(uiLangs)); |
1687 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}"))); | 2325 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.uilang}"))); |
1688 | setId_Widget(addChildFlags_Widget(values, | 2326 | setId_Widget(addChildFlags_Widget(values, |
1689 | iClob(makeMenuButton_LabelWidget( | 2327 | iClob(makeMenuButton_LabelWidget( |
@@ -1697,54 +2335,24 @@ iWidget *makePreferences_Widget(void) { | |||
1697 | /* User Interface. */ { | 2335 | /* User Interface. */ { |
1698 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.interface}", '2', &headings, &values); | 2336 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.interface}", '2', &headings, &values); |
1699 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 2337 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
1700 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); | 2338 | addDialogToggle_(headings, values, "${prefs.customframe}", "prefs.customframe"); |
1701 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); | ||
1702 | #endif | 2339 | #endif |
1703 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); | 2340 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.returnkey}"))); |
1704 | /* Return key behaviors. */ { | 2341 | /* Return key behaviors. */ { |
1705 | const iMenuItem returnKeyBehaviors[] = { | ||
1706 | { "${prefs.returnkey.linebreak} " | ||
1707 | uiTextAction_ColorEscape shift_Icon return_Icon restore_ColorEscape | ||
1708 | " ${prefs.returnkey.accept} " | ||
1709 | uiTextAction_ColorEscape return_Icon, | ||
1710 | 0, | ||
1711 | 0, | ||
1712 | format_CStr("returnkey.set arg:%d", default_ReturnKeyBehavior) }, | ||
1713 | { "${prefs.returnkey.linebreak} " | ||
1714 | uiTextAction_ColorEscape return_Icon restore_ColorEscape | ||
1715 | " ${prefs.returnkey.accept} " | ||
1716 | uiTextAction_ColorEscape shift_Icon return_Icon, | ||
1717 | 0, | ||
1718 | 0, | ||
1719 | format_CStr("returnkey.set arg:%d", acceptWithShift_ReturnKeyBehavior) }, | ||
1720 | { "${prefs.returnkey.linebreak} " | ||
1721 | uiTextAction_ColorEscape return_Icon restore_ColorEscape | ||
1722 | " ${prefs.returnkey.accept} " uiTextAction_ColorEscape | ||
1723 | #if defined (iPlatformApple) | ||
1724 | "\u2318" return_Icon, | ||
1725 | #else | ||
1726 | "Ctrl" return_Icon, | ||
1727 | #endif | ||
1728 | 0, | ||
1729 | 0, | ||
1730 | format_CStr("returnkey.set arg:%d", acceptWithPrimaryMod_ReturnKeyBehavior) }, | ||
1731 | }; | ||
1732 | iLabelWidget *returnKey = makeMenuButton_LabelWidget( | 2342 | iLabelWidget *returnKey = makeMenuButton_LabelWidget( |
1733 | returnKeyBehaviors[findWidestItemLabel_(returnKeyBehaviors, | 2343 | returnKeyBehaviors[findWidestLabel_MenuItem(returnKeyBehaviors, |
1734 | iElemCount(returnKeyBehaviors))] | 2344 | iElemCount(returnKeyBehaviors) - 1)] |
1735 | .label, | 2345 | .label, |
1736 | returnKeyBehaviors, | 2346 | returnKeyBehaviors, |
1737 | iElemCount(returnKeyBehaviors)); | 2347 | iElemCount(returnKeyBehaviors) - 1); |
1738 | setBackgroundColor_Widget(findChild_Widget(as_Widget(returnKey), "menu"), | 2348 | setBackgroundColor_Widget(findChild_Widget(as_Widget(returnKey), "menu"), |
1739 | uiBackgroundMenu_ColorId); | 2349 | uiBackgroundMenu_ColorId); |
1740 | setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), | 2350 | setId_Widget(addChildFlags_Widget(values, iClob(returnKey), alignLeft_WidgetFlag), |
1741 | "prefs.returnkey"); | 2351 | "prefs.returnkey"); |
1742 | } | 2352 | } |
1743 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.animate}"))); | 2353 | addDialogToggle_(headings, values, "${prefs.animate}", "prefs.animate"); |
1744 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.animate"))); | ||
1745 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); | 2354 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); |
1746 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.smoothscroll}"))); | 2355 | addDialogToggle_(headings, values, "${prefs.smoothscroll}", "prefs.smoothscroll"); |
1747 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll"))); | ||
1748 | /* Scroll speeds. */ { | 2356 | /* Scroll speeds. */ { |
1749 | for (int type = 0; type < max_ScrollType; type++) { | 2357 | for (int type = 0; type < max_ScrollType; type++) { |
1750 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); | 2358 | const char *typeStr = (type == mouse_ScrollType ? "mouse" : "keyboard"); |
@@ -1764,25 +2372,21 @@ iWidget *makePreferences_Widget(void) { | |||
1764 | values, iClob(scrollSpeed), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2372 | values, iClob(scrollSpeed), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1765 | } | 2373 | } |
1766 | } | 2374 | } |
1767 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imageloadscroll}"))); | 2375 | addDialogToggle_(headings, values, "${prefs.imageloadscroll}", "prefs.imageloadscroll"); |
1768 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.imageloadscroll"))); | ||
1769 | if (deviceType_App() == phone_AppDeviceType) { | 2376 | if (deviceType_App() == phone_AppDeviceType) { |
1770 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.hidetoolbarscroll}"))); | 2377 | addDialogToggle_(headings, values, "${prefs.hidetoolbarscroll}", "prefs.hidetoolbarscroll"); |
1771 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hidetoolbarscroll"))); | ||
1772 | } | 2378 | } |
1773 | makeTwoColumnHeading_("${heading.prefs.sizing}", headings, values); | 2379 | makeTwoColumnHeading_("${heading.prefs.sizing}", headings, values); |
1774 | addPrefsInputWithHeading_(headings, values, "prefs.uiscale", iClob(new_InputWidget(8))); | 2380 | addPrefsInputWithHeading_(headings, values, "prefs.uiscale", iClob(new_InputWidget(8))); |
1775 | if (deviceType_App() == desktop_AppDeviceType) { | 2381 | if (deviceType_App() == desktop_AppDeviceType) { |
1776 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.retainwindow}"))); | 2382 | addDialogToggle_(headings, values, "${prefs.retainwindow}", "prefs.retainwindow"); |
1777 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.retainwindow"))); | ||
1778 | } | 2383 | } |
1779 | } | 2384 | } |
1780 | /* Colors. */ { | 2385 | /* Colors. */ { |
1781 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.colors}", '3', &headings, &values); | 2386 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.colors}", '3', &headings, &values); |
1782 | makeTwoColumnHeading_("${heading.prefs.uitheme}", headings, values); | 2387 | makeTwoColumnHeading_("${heading.prefs.uitheme}", headings, values); |
1783 | #if defined (iPlatformApple) || defined (iPlatformMSys) | 2388 | #if defined (iPlatformApple) || defined (iPlatformMSys) |
1784 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.ostheme}"))); | 2389 | addDialogToggle_(headings, values, "${prefs.ostheme}", "prefs.ostheme"); |
1785 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); | ||
1786 | #endif | 2390 | #endif |
1787 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}"))); | 2391 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.theme}"))); |
1788 | iWidget *themes = new_Widget(); | 2392 | iWidget *themes = new_Widget(); |
@@ -1804,20 +2408,13 @@ iWidget *makePreferences_Widget(void) { | |||
1804 | for (int i = 0; i < 2; ++i) { | 2408 | for (int i = 0; i < 2; ++i) { |
1805 | const iBool isDark = (i == 0); | 2409 | const iBool isDark = (i == 0); |
1806 | const char *mode = isDark ? "dark" : "light"; | 2410 | const char *mode = isDark ? "dark" : "light"; |
1807 | const iMenuItem themes[] = { | ||
1808 | { "${prefs.doctheme.name.colorfuldark}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulDark_GmDocumentTheme) }, | ||
1809 | { "${prefs.doctheme.name.colorfullight}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, colorfulLight_GmDocumentTheme) }, | ||
1810 | { "${prefs.doctheme.name.black}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, black_GmDocumentTheme) }, | ||
1811 | { "${prefs.doctheme.name.gray}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, gray_GmDocumentTheme) }, | ||
1812 | { "${prefs.doctheme.name.white}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, white_GmDocumentTheme) }, | ||
1813 | { "${prefs.doctheme.name.sepia}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, sepia_GmDocumentTheme) }, | ||
1814 | { "${prefs.doctheme.name.highcontrast}", 0, 0, format_CStr("doctheme.%s.set arg:%d", mode, highContrast_GmDocumentTheme) }, | ||
1815 | }; | ||
1816 | addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "${prefs.doctheme.dark}" : "${prefs.doctheme.light}"))); | 2411 | addChild_Widget(headings, iClob(makeHeading_Widget(isDark ? "${prefs.doctheme.dark}" : "${prefs.doctheme.light}"))); |
1817 | iLabelWidget *button = | 2412 | iLabelWidget *button = makeMenuButton_LabelWidget( |
1818 | makeMenuButton_LabelWidget(themes[1].label, themes, iElemCount(themes)); | 2413 | docThemes[i][findWidestLabel_MenuItem(docThemes[i], max_GmDocumentTheme)].label, |
1819 | // setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), | 2414 | docThemes[i], |
1820 | // uiBackgroundSelected_ColorId); | 2415 | max_GmDocumentTheme); |
2416 | // setFrameColor_Widget(findChild_Widget(as_Widget(button), "menu"), | ||
2417 | // uiBackgroundSelected_ColorId); | ||
1821 | setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); | 2418 | setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), uiBackgroundMenu_ColorId); |
1822 | setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), | 2419 | setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), |
1823 | format_CStr("prefs.doctheme.%s", mode)); | 2420 | format_CStr("prefs.doctheme.%s", mode)); |
@@ -1831,6 +2428,17 @@ iWidget *makePreferences_Widget(void) { | |||
1831 | addRadioButton_(sats, "prefs.saturation.0", "0 %", "saturation.set arg:0"); | 2428 | addRadioButton_(sats, "prefs.saturation.0", "0 %", "saturation.set arg:0"); |
1832 | } | 2429 | } |
1833 | addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2430 | addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
2431 | /* Colorize images. */ { | ||
2432 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.imagestyle}"))); | ||
2433 | iLabelWidget *button = makeMenuButton_LabelWidget( | ||
2434 | imgStyles[findWidestLabel_MenuItem(imgStyles, iElemCount(imgStyles) - 1)].label, | ||
2435 | imgStyles, | ||
2436 | iElemCount(imgStyles) - 1); | ||
2437 | setBackgroundColor_Widget(findChild_Widget(as_Widget(button), "menu"), | ||
2438 | uiBackgroundMenu_ColorId); | ||
2439 | setId_Widget(addChildFlags_Widget(values, iClob(button), alignLeft_WidgetFlag), | ||
2440 | "prefs.imagestyle"); | ||
2441 | } | ||
1834 | } | 2442 | } |
1835 | /* Fonts. */ { | 2443 | /* Fonts. */ { |
1836 | setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); | 2444 | setId_Widget(appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.fonts}", '4', &headings, &values), "prefs.page.fonts"); |
@@ -1839,8 +2447,7 @@ iWidget *makePreferences_Widget(void) { | |||
1839 | addFontButtons_(values, "headingfont"); | 2447 | addFontButtons_(values, "headingfont"); |
1840 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); | 2448 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.font}"))); |
1841 | addFontButtons_(values, "font"); | 2449 | addFontButtons_(values, "font"); |
1842 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 2450 | addDialogPadding_(headings, values); |
1843 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
1844 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); | 2451 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.mono}"))); |
1845 | iWidget *mono = new_Widget(); { | 2452 | iWidget *mono = new_Widget(); { |
1846 | iWidget *tog; | 2453 | iWidget *tog; |
@@ -1872,8 +2479,7 @@ iWidget *makePreferences_Widget(void) { | |||
1872 | updateSize_LabelWidget((iLabelWidget *) tog); | 2479 | updateSize_LabelWidget((iLabelWidget *) tog); |
1873 | } | 2480 | } |
1874 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2481 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1875 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 2482 | addDialogPadding_(headings, values); |
1876 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
1877 | /* Custom font. */ { | 2483 | /* Custom font. */ { |
1878 | iInputWidget *customFont = new_InputWidget(0); | 2484 | iInputWidget *customFont = new_InputWidget(0); |
1879 | setHint_InputWidget(customFont, "${hint.prefs.userfont}"); | 2485 | setHint_InputWidget(customFont, "${hint.prefs.userfont}"); |
@@ -1902,19 +2508,12 @@ iWidget *makePreferences_Widget(void) { | |||
1902 | addRadioButton_(quote, "prefs.quoteicon.0", "${prefs.quoteicon.line}", "quoteicon.set arg:0"); | 2508 | addRadioButton_(quote, "prefs.quoteicon.0", "${prefs.quoteicon.line}", "quoteicon.set arg:0"); |
1903 | } | 2509 | } |
1904 | addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2510 | addChildFlags_Widget(values, iClob(quote), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1905 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.biglede}"))); | 2511 | addDialogToggle_(headings, values, "${prefs.biglede}", "prefs.biglede"); |
1906 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede"))); | 2512 | addDialogToggle_(headings, values, "${prefs.plaintext.wrap}", "prefs.plaintext.wrap"); |
1907 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.plaintext.wrap}"))); | 2513 | addDialogToggle_(headings, values, "${prefs.collapsepreonload}", "prefs.collapsepreonload"); |
1908 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.plaintext.wrap"))); | 2514 | addDialogPadding_(headings, values); |
1909 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.collapsepreonload}"))); | 2515 | addDialogToggle_(headings, values, "${prefs.sideicon}", "prefs.sideicon"); |
1910 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.collapsepreonload"))); | 2516 | addDialogToggle_(headings, values, "${prefs.centershort}", "prefs.centershort"); |
1911 | // makeTwoColumnHeading_("${heading.prefs.widelayout}", headings, values); | ||
1912 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | ||
1913 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | ||
1914 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.sideicon}"))); | ||
1915 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.sideicon"))); | ||
1916 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.centershort}"))); | ||
1917 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.centershort"))); | ||
1918 | } | 2517 | } |
1919 | /* Network. */ { | 2518 | /* Network. */ { |
1920 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.network}", '6', &headings, &values); | 2519 | appendTwoColumnTabPage_Widget(tabs, "${heading.prefs.network}", '6', &headings, &values); |
@@ -1961,13 +2560,37 @@ iWidget *makePreferences_Widget(void) { | |||
1961 | iClob(makeDialogButtons_Widget( | 2560 | iClob(makeDialogButtons_Widget( |
1962 | (iMenuItem[]){ { "${close}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); | 2561 | (iMenuItem[]){ { "${close}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); |
1963 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 2562 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
1964 | finalizeSheet_Mobile(dlg); | 2563 | // finalizeSheet_Mobile(dlg); |
2564 | // arrange_Widget(dlg); | ||
1965 | setupSheetTransition_Mobile(dlg, iTrue); | 2565 | setupSheetTransition_Mobile(dlg, iTrue); |
1966 | // printTree_Widget(dlg); | 2566 | // printTree_Widget(dlg); |
1967 | return dlg; | 2567 | return dlg; |
1968 | } | 2568 | } |
1969 | 2569 | ||
1970 | iWidget *makeBookmarkEditor_Widget(void) { | 2570 | iWidget *makeBookmarkEditor_Widget(void) { |
2571 | const iMenuItem actions[] = { | ||
2572 | { "${cancel}" }, | ||
2573 | { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" } | ||
2574 | }; | ||
2575 | if (isUsingPanelLayout_Mobile()) { | ||
2576 | const iMenuItem items[] = { | ||
2577 | { "title id:bmed.heading text:${heading.bookmark.edit}" }, | ||
2578 | { "heading id:dlg.bookmark.url" }, | ||
2579 | { "input id:bmed.url url:1 noheading:1" }, | ||
2580 | { "padding" }, | ||
2581 | { "input id:bmed.title text:${dlg.bookmark.title}" }, | ||
2582 | { "input id:bmed.tags text:${dlg.bookmark.tags}" }, | ||
2583 | { "input id:bmed.icon maxlen:1 text:${dlg.bookmark.icon}" }, | ||
2584 | { "heading text:${heading.bookmark.tags}" }, | ||
2585 | { "toggle id:bmed.tag.home text:${bookmark.tag.home}" }, | ||
2586 | { "toggle id:bmed.tag.remote text:${bookmark.tag.remote}" }, | ||
2587 | { "toggle id:bmed.tag.linksplit text:${bookmark.tag.linksplit}" }, | ||
2588 | { NULL } | ||
2589 | }; | ||
2590 | iWidget *dlg = makePanels_Mobile("bmed", items, actions, iElemCount(actions)); | ||
2591 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2592 | return dlg; | ||
2593 | } | ||
1971 | iWidget *dlg = makeSheet_Widget("bmed"); | 2594 | iWidget *dlg = makeSheet_Widget("bmed"); |
1972 | setId_Widget(addChildFlags_Widget( | 2595 | setId_Widget(addChildFlags_Widget( |
1973 | dlg, | 2596 | dlg, |
@@ -1985,28 +2608,18 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
1985 | /* Buttons for special tags. */ | 2608 | /* Buttons for special tags. */ |
1986 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 2609 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
1987 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); | 2610 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); |
1988 | makeTwoColumnHeading_("SPECIAL TAGS", headings, values); | 2611 | makeTwoColumnHeading_("${heading.bookmark.tags}", headings, values); |
1989 | addChild_Widget(headings, iClob(makeHeading_Widget("${bookmark.tag.home}"))); | 2612 | addDialogToggle_(headings, values, "${bookmark.tag.home}", "bmed.tag.home"); |
1990 | addChild_Widget(values, iClob(makeToggle_Widget("bmed.tag.home"))); | 2613 | addDialogToggle_(headings, values, "${bookmark.tag.remote}", "bmed.tag.remote"); |
1991 | addChild_Widget(headings, iClob(makeHeading_Widget("${bookmark.tag.remote}"))); | 2614 | addDialogToggle_(headings, values, "${bookmark.tag.linksplit}", "bmed.tag.linksplit"); |
1992 | addChild_Widget(values, iClob(makeToggle_Widget("bmed.tag.remote"))); | ||
1993 | addChild_Widget(headings, iClob(makeHeading_Widget("${bookmark.tag.linksplit}"))); | ||
1994 | addChild_Widget(values, iClob(makeToggle_Widget("bmed.tag.linksplit"))); | ||
1995 | arrange_Widget(dlg); | 2615 | arrange_Widget(dlg); |
1996 | for (int i = 0; i < 3; ++i) { | 2616 | for (int i = 0; i < 3; ++i) { |
1997 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; | 2617 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; |
1998 | } | 2618 | } |
1999 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 2619 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
2000 | addChild_Widget( | 2620 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); |
2001 | dlg, | ||
2002 | iClob(makeDialogButtons_Widget((iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | ||
2003 | { uiTextCaution_ColorEscape "${dlg.bookmark.save}", | ||
2004 | SDLK_RETURN, | ||
2005 | KMOD_PRIMARY, | ||
2006 | "bmed.accept" } }, | ||
2007 | 2))); | ||
2008 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2621 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2009 | finalizeSheet_Mobile(dlg); | 2622 | setupSheetTransition_Mobile(dlg, iTrue); |
2010 | return dlg; | 2623 | return dlg; |
2011 | } | 2624 | } |
2012 | 2625 | ||
@@ -2060,7 +2673,6 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i | |||
2060 | return dlg; | 2673 | return dlg; |
2061 | } | 2674 | } |
2062 | 2675 | ||
2063 | |||
2064 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | 2676 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { |
2065 | if (equal_Command(cmd, "cancel")) { | 2677 | if (equal_Command(cmd, "cancel")) { |
2066 | setupSheetTransition_Mobile(dlg, iFalse); | 2678 | setupSheetTransition_Mobile(dlg, iFalse); |
@@ -2106,132 +2718,174 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | |||
2106 | } | 2718 | } |
2107 | 2719 | ||
2108 | iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | 2720 | iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { |
2109 | iWidget *dlg = makeSheet_Widget("feedcfg"); | 2721 | const char *headingText = bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" |
2110 | setId_Widget(addChildFlags_Widget( | 2722 | : uiHeading_ColorEscape "${heading.subscribe}"; |
2111 | dlg, | 2723 | const iMenuItem actions[] = { { "${cancel}" }, |
2112 | iClob(new_LabelWidget(bookmarkId ? uiHeading_ColorEscape "${heading.feedcfg}" | 2724 | { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" |
2113 | : uiHeading_ColorEscape "${heading.subscribe}", | 2725 | : uiTextCaution_ColorEscape "${dlg.feed.sub}", |
2114 | NULL)), | 2726 | SDLK_RETURN, |
2115 | frameless_WidgetFlag), | 2727 | KMOD_PRIMARY, |
2116 | "feedcfg.heading"); | 2728 | format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }; |
2117 | iWidget *headings, *values; | 2729 | iWidget *dlg; |
2118 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); | 2730 | if (isUsingPanelLayout_Mobile()) { |
2119 | iInputWidget *input = new_InputWidget(0); | 2731 | const iMenuItem typeItems[] = { |
2120 | addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input)); | 2732 | { "button id:feedcfg.type.gemini label:dlg.feed.type.gemini", 0, 0, "feedcfg.type arg:0" }, |
2121 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}"))); | 2733 | { "button id:feedcfg.type.headings label:dlg.feed.type.headings", 0, 0, "feedcfg.type arg:1" }, |
2122 | iWidget *types = new_Widget(); { | 2734 | { NULL } |
2123 | addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0"); | 2735 | }; |
2124 | addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1"); | 2736 | dlg = makePanels_Mobile("feedcfg", (iMenuItem[]){ |
2125 | } | 2737 | { format_CStr("title id:feedcfg.heading text:%s", headingText) }, |
2126 | addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 2738 | { "input id:feedcfg.title text:${dlg.feed.title}" }, |
2127 | iWidget *buttons = | 2739 | { "radio id:dlg.feed.entrytype", 0, 0, (const void *) typeItems }, |
2128 | addChild_Widget(dlg, | 2740 | { NULL } |
2129 | iClob(makeDialogButtons_Widget( | 2741 | }, actions, iElemCount(actions)); |
2130 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | 2742 | } |
2131 | { bookmarkId ? uiTextCaution_ColorEscape "${dlg.feed.save}" | 2743 | else { |
2132 | : uiTextCaution_ColorEscape "${dlg.feed.sub}", | 2744 | dlg = makeSheet_Widget("feedcfg"); |
2133 | SDLK_RETURN, | 2745 | setId_Widget( |
2134 | KMOD_PRIMARY, | 2746 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(headingText, NULL)), frameless_WidgetFlag), |
2135 | format_CStr("feedcfg.accept bmid:%d", bookmarkId) } }, | 2747 | "feedcfg.heading"); |
2136 | 2))); | 2748 | iWidget *headings, *values; |
2137 | setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); | 2749 | addChild_Widget(dlg, iClob(makeTwoColumns_Widget(&headings, &values))); |
2138 | arrange_Widget(dlg); | 2750 | iInputWidget *input = new_InputWidget(0); |
2139 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; | 2751 | addDialogInputWithHeading_(headings, values, "${dlg.feed.title}", "feedcfg.title", iClob(input)); |
2140 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2752 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.feed.entrytype}"))); |
2141 | finalizeSheet_Mobile(dlg); | 2753 | iWidget *types = new_Widget(); { |
2754 | addRadioButton_(types, "feedcfg.type.gemini", "${dlg.feed.type.gemini}", "feedcfg.type arg:0"); | ||
2755 | addRadioButton_(types, "feedcfg.type.headings", "${dlg.feed.type.headings}", "feedcfg.type arg:1"); | ||
2756 | } | ||
2757 | addChildFlags_Widget(values, iClob(types), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
2758 | iWidget *buttons = | ||
2759 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); | ||
2760 | setId_Widget(child_Widget(buttons, childCount_Widget(buttons) - 1), "feedcfg.save"); | ||
2761 | arrange_Widget(dlg); | ||
2762 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; | ||
2763 | addChild_Widget(get_Root()->widget, iClob(dlg)); | ||
2764 | // finalizeSheet_Mobile(dlg); | ||
2765 | } | ||
2142 | /* Initialize. */ { | 2766 | /* Initialize. */ { |
2143 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; | 2767 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; |
2144 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), | 2768 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), |
2145 | bm ? &bm->title : feedTitle_DocumentWidget(document_App())); | 2769 | bm ? &bm->title : feedTitle_DocumentWidget(document_App())); |
2146 | setFlags_Widget(findChild_Widget(dlg, | 2770 | setFlags_Widget(findChild_Widget(dlg, |
2147 | hasTag_Bookmark(bm, headings_BookmarkTag) ? "feedcfg.type.headings" | 2771 | hasTag_Bookmark(bm, headings_BookmarkTag) |
2148 | : "feedcfg.type.gemini"), | 2772 | ? "feedcfg.type.headings" |
2773 | : "feedcfg.type.gemini"), | ||
2149 | selected_WidgetFlag, | 2774 | selected_WidgetFlag, |
2150 | iTrue); | 2775 | iTrue); |
2151 | setCommandHandler_Widget(dlg, handleFeedSettingCommands_); | 2776 | setCommandHandler_Widget(dlg, handleFeedSettingCommands_); |
2152 | } | 2777 | } |
2778 | setupSheetTransition_Mobile(dlg, incoming_TransitionFlag); | ||
2153 | return dlg; | 2779 | return dlg; |
2154 | } | 2780 | } |
2155 | 2781 | ||
2156 | iWidget *makeIdentityCreation_Widget(void) { | 2782 | iWidget *makeIdentityCreation_Widget(void) { |
2157 | iWidget *dlg = makeSheet_Widget("ident"); | 2783 | const iMenuItem actions[] = { { "${dlg.newident.more}", 0, 0, "ident.showmore" }, |
2158 | setId_Widget(addChildFlags_Widget( | 2784 | { "---" }, |
2159 | dlg, | 2785 | { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" }, |
2160 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)), | 2786 | { uiTextAction_ColorEscape "${dlg.newident.create}", |
2161 | frameless_WidgetFlag), | 2787 | SDLK_RETURN, |
2162 | "ident.heading"); | 2788 | KMOD_PRIMARY, |
2163 | iWidget *page = new_Widget(); | 2789 | "ident.accept" } }; |
2164 | addChildFlags_Widget( | 2790 | iUrl url; |
2165 | dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); | 2791 | init_Url(&url, url_DocumentWidget(document_App())); |
2166 | /* TODO: Use makeTwoColumnWidget_? */ | 2792 | const iMenuItem scopeItems[] = { |
2167 | addChild_Widget(dlg, iClob(page)); | 2793 | { format_CStr("${dlg.newident.scope.domain}:\n%s", cstr_Rangecc(url.host)), 0, 0, "ident.scope arg:0" }, |
2168 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | 2794 | { format_CStr("${dlg.newident.scope.page}:\n%s", cstr_Rangecc(url.path)), 0, 0, "ident.scope arg:1" }, |
2169 | iWidget *headings = addChildFlags_Widget( | 2795 | { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" }, |
2170 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 2796 | { NULL } |
2171 | iWidget *values = addChildFlags_Widget( | 2797 | }; |
2172 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 2798 | iWidget *dlg; |
2173 | setId_Widget(headings, "headings"); | 2799 | if (isUsingPanelLayout_Mobile()) { |
2174 | setId_Widget(values, "values"); | 2800 | dlg = makePanels_Mobile("ident", (iMenuItem[]){ |
2175 | iInputWidget *inputs[6]; | 2801 | { "title id:ident.heading text:${heading.newident}" }, |
2176 | /* Where will the new identity be active on? */ { | 2802 | { "label text:${dlg.newident.rsa.selfsign}" }, |
2177 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}"))); | 2803 | { "dropdown id:ident.scope text:${dlg.newident.scope}", 0, 0, |
2178 | const iMenuItem items[] = { | 2804 | (const void *) scopeItems }, |
2179 | { "${dlg.newident.scope.domain}", 0, 0, "ident.scope arg:0" }, | 2805 | { "input id:ident.until hint:hint.newident.date maxlen:19 text:${dlg.newident.until}" }, |
2180 | { "${dlg.newident.scope.page}", 0, 0, "ident.scope arg:1" }, | 2806 | //{ "padding" }, |
2181 | { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" }, | 2807 | //{ "toggle id:ident.temp text:${dlg.newident.temp}" }, |
2182 | }; | 2808 | //{ "label text:${help.ident.temp}" }, |
2183 | setId_Widget(addChild_Widget(values, | 2809 | { "heading id:dlg.newident.commonname" }, |
2184 | iClob(makeMenuButton_LabelWidget( | 2810 | { "input id:ident.common noheading:1" }, |
2185 | items[0].label, items, iElemCount(items)))), | 2811 | { "padding collapse:1" }, |
2186 | "ident.scope"); | 2812 | { "input collapse:1 id:ident.email hint:hint.newident.optional text:${dlg.newident.email}" }, |
2187 | } | 2813 | { "input collapse:1 id:ident.userid hint:hint.newident.optional text:${dlg.newident.userid}" }, |
2188 | addDialogInputWithHeading_(headings, | 2814 | { "input collapse:1 id:ident.domain hint:hint.newident.optional text:${dlg.newident.domain}" }, |
2189 | values, | 2815 | { "input collapse:1 id:ident.org hint:hint.newident.optional text:${dlg.newident.org}" }, |
2190 | "${dlg.newident.until}", | 2816 | { "input collapse:1 id:ident.country hint:hint.newident.optional text:${dlg.newident.country}" }, |
2191 | "ident.until", | 2817 | { NULL } |
2192 | iClob(newHint_InputWidget(19, "${hint.newident.date}"))); | 2818 | }, actions, iElemCount(actions)); |
2193 | addDialogInputWithHeading_(headings, | ||
2194 | values, | ||
2195 | "${dlg.newident.commonname}", | ||
2196 | "ident.common", | ||
2197 | iClob(inputs[0] = new_InputWidget(0))); | ||
2198 | /* Temporary? */ { | ||
2199 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}"))); | ||
2200 | iWidget *tmpGroup = new_Widget(); | ||
2201 | setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | ||
2202 | addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); | ||
2203 | setId_Widget( | ||
2204 | addChildFlags_Widget(tmpGroup, | ||
2205 | iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon | ||
2206 | " ${dlg.newident.notsaved}", | ||
2207 | NULL)), | ||
2208 | hidden_WidgetFlag | frameless_WidgetFlag), | ||
2209 | "ident.temp.note"); | ||
2210 | addChild_Widget(values, iClob(tmpGroup)); | ||
2211 | } | ||
2212 | addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2213 | addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2214 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2215 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2216 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2217 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2218 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2219 | arrange_Widget(dlg); | ||
2220 | for (size_t i = 0; i < iElemCount(inputs); ++i) { | ||
2221 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; | ||
2222 | } | 2819 | } |
2223 | addChild_Widget(dlg, | 2820 | else { |
2224 | iClob(makeDialogButtons_Widget( | 2821 | dlg = makeSheet_Widget("ident"); |
2225 | (iMenuItem[]){ { "${dlg.newident.more}", 0, 0, "ident.showmore" }, | 2822 | setId_Widget(addChildFlags_Widget( |
2226 | { "---", 0, 0, NULL }, | 2823 | dlg, |
2227 | { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" }, | 2824 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.newident}", NULL)), |
2228 | { uiTextAction_ColorEscape "${dlg.newident.create}", | 2825 | frameless_WidgetFlag), |
2229 | SDLK_RETURN, | 2826 | "ident.heading"); |
2230 | KMOD_PRIMARY, | 2827 | iWidget *page = new_Widget(); |
2231 | "ident.accept" } }, | 2828 | addChildFlags_Widget( |
2232 | 4))); | 2829 | dlg, iClob(new_LabelWidget("${dlg.newident.rsa.selfsign}", NULL)), frameless_WidgetFlag); |
2233 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2830 | /* TODO: Use makeTwoColumnWidget_? */ |
2234 | finalizeSheet_Mobile(dlg); | 2831 | addChild_Widget(dlg, iClob(page)); |
2832 | setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
2833 | iWidget *headings = addChildFlags_Widget( | ||
2834 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
2835 | iWidget *values = addChildFlags_Widget( | ||
2836 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | ||
2837 | setId_Widget(headings, "headings"); | ||
2838 | setId_Widget(values, "values"); | ||
2839 | iInputWidget *inputs[6]; | ||
2840 | /* Where will the new identity be active on? */ { | ||
2841 | iWidget *head = addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}"))); | ||
2842 | iWidget *val; | ||
2843 | setId_Widget( | ||
2844 | addChild_Widget(values, | ||
2845 | val = iClob(makeMenuButton_LabelWidget( | ||
2846 | scopeItems[0].label, scopeItems, iElemCount(scopeItems)))), | ||
2847 | "ident.scope"); | ||
2848 | head->sizeRef = val; | ||
2849 | } | ||
2850 | addDialogInputWithHeading_(headings, | ||
2851 | values, | ||
2852 | "${dlg.newident.until}", | ||
2853 | "ident.until", | ||
2854 | iClob(newHint_InputWidget(19, "${hint.newident.date}"))); | ||
2855 | addDialogInputWithHeading_(headings, | ||
2856 | values, | ||
2857 | "${dlg.newident.commonname}", | ||
2858 | "ident.common", | ||
2859 | iClob(inputs[0] = new_InputWidget(0))); | ||
2860 | /* Temporary? */ { | ||
2861 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.temp}"))); | ||
2862 | iWidget *tmpGroup = new_Widget(); | ||
2863 | setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | ||
2864 | addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); | ||
2865 | setId_Widget( | ||
2866 | addChildFlags_Widget(tmpGroup, | ||
2867 | iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon | ||
2868 | " ${dlg.newident.notsaved}", | ||
2869 | NULL)), | ||
2870 | hidden_WidgetFlag | frameless_WidgetFlag), | ||
2871 | "ident.temp.note"); | ||
2872 | addChild_Widget(values, iClob(tmpGroup)); | ||
2873 | } | ||
2874 | addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2875 | addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2876 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2877 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2878 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2879 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2880 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); | ||
2881 | arrange_Widget(dlg); | ||
2882 | for (size_t i = 0; i < iElemCount(inputs); ++i) { | ||
2883 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; | ||
2884 | } | ||
2885 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); | ||
2886 | addChild_Widget(get_Root()->widget, iClob(dlg)); | ||
2887 | } | ||
2888 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2235 | return dlg; | 2889 | return dlg; |
2236 | } | 2890 | } |
2237 | 2891 | ||
@@ -2247,15 +2901,23 @@ static const iMenuItem languages[] = { | |||
2247 | { "${lang.pt}", 0, 0, "xlt.lang id:pt" }, | 2901 | { "${lang.pt}", 0, 0, "xlt.lang id:pt" }, |
2248 | { "${lang.ru}", 0, 0, "xlt.lang id:ru" }, | 2902 | { "${lang.ru}", 0, 0, "xlt.lang id:ru" }, |
2249 | { "${lang.es}", 0, 0, "xlt.lang id:es" }, | 2903 | { "${lang.es}", 0, 0, "xlt.lang id:es" }, |
2904 | { NULL } | ||
2250 | }; | 2905 | }; |
2251 | 2906 | ||
2252 | static iBool translationHandler_(iWidget *dlg, const char *cmd) { | 2907 | static iBool translationHandler_(iWidget *dlg, const char *cmd) { |
2253 | iUnused(dlg); | 2908 | iUnused(dlg); |
2254 | if (equal_Command(cmd, "xlt.lang")) { | 2909 | if (equal_Command(cmd, "xlt.lang")) { |
2255 | iLabelWidget *menuItem = pointer_Command(cmd); | 2910 | const iMenuItem *langItem = &languages[languageIndex_CStr(cstr_Rangecc(range_Command(cmd, "id")))]; |
2256 | iWidget *button = parent_Widget(parent_Widget(menuItem)); | 2911 | iWidget *widget = pointer_Command(cmd); |
2257 | iAssert(isInstance_Object(button, &Class_LabelWidget)); | 2912 | iLabelWidget *drop; |
2258 | updateText_LabelWidget((iLabelWidget *) button, text_LabelWidget(menuItem)); | 2913 | if (flags_Widget(widget) & nativeMenu_WidgetFlag) { |
2914 | drop = (iLabelWidget *) parent_Widget(widget); | ||
2915 | } | ||
2916 | else { | ||
2917 | drop = (iLabelWidget *) parent_Widget(parent_Widget(widget)); | ||
2918 | } | ||
2919 | iAssert(isInstance_Object(drop, &Class_LabelWidget)); | ||
2920 | updateDropdownSelection_LabelWidget(drop, langItem->command); | ||
2259 | return iTrue; | 2921 | return iTrue; |
2260 | } | 2922 | } |
2261 | return iFalse; | 2923 | return iFalse; |
@@ -2263,6 +2925,7 @@ static iBool translationHandler_(iWidget *dlg, const char *cmd) { | |||
2263 | 2925 | ||
2264 | const char *languageId_String(const iString *menuItemLabel) { | 2926 | const char *languageId_String(const iString *menuItemLabel) { |
2265 | iForIndices(i, languages) { | 2927 | iForIndices(i, languages) { |
2928 | if (!languages[i].label) break; | ||
2266 | if (!cmp_String(menuItemLabel, translateCStr_Lang(languages[i].label))) { | 2929 | if (!cmp_String(menuItemLabel, translateCStr_Lang(languages[i].label))) { |
2267 | return cstr_Rangecc(range_Command(languages[i].command, "id")); | 2930 | return cstr_Rangecc(range_Command(languages[i].command, "id")); |
2268 | } | 2931 | } |
@@ -2272,6 +2935,7 @@ const char *languageId_String(const iString *menuItemLabel) { | |||
2272 | 2935 | ||
2273 | int languageIndex_CStr(const char *langId) { | 2936 | int languageIndex_CStr(const char *langId) { |
2274 | iForIndices(i, languages) { | 2937 | iForIndices(i, languages) { |
2938 | if (!languages[i].label) break; | ||
2275 | if (equal_Rangecc(range_Command(languages[i].command, "id"), langId)) { | 2939 | if (equal_Rangecc(range_Command(languages[i].command, "id"), langId)) { |
2276 | return (int) i; | 2940 | return (int) i; |
2277 | } | 2941 | } |
@@ -2280,54 +2944,74 @@ int languageIndex_CStr(const char *langId) { | |||
2280 | } | 2944 | } |
2281 | 2945 | ||
2282 | iWidget *makeTranslation_Widget(iWidget *parent) { | 2946 | iWidget *makeTranslation_Widget(iWidget *parent) { |
2283 | iWidget *dlg = makeSheet_Widget("xlt"); | 2947 | const iMenuItem actions[] = { |
2284 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); | 2948 | { "${cancel}", SDLK_ESCAPE, 0, "translation.cancel" }, |
2285 | dlg->minSize.x = 70 * gap_UI; | 2949 | { uiTextAction_ColorEscape "${dlg.translate}", SDLK_RETURN, 0, "translation.submit" } |
2950 | }; | ||
2951 | iWidget *dlg; | ||
2952 | if (isUsingPanelLayout_Mobile()) { | ||
2953 | dlg = makePanelsParent_Mobile(parent, "xlt", (iMenuItem[]){ | ||
2954 | { "title id:heading.translate" }, | ||
2955 | { "dropdown id:xlt.from text:${dlg.translate.from}", 0, 0, (const void *) languages }, | ||
2956 | { "dropdown id:xlt.to text:${dlg.translate.to}", 0, 0, (const void *) languages }, | ||
2957 | { NULL } | ||
2958 | }, actions, iElemCount(actions)); | ||
2959 | } | ||
2960 | else { | ||
2961 | dlg = makeSheet_Widget("xlt"); | ||
2962 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); | ||
2963 | dlg->minSize.x = 70 * gap_UI; | ||
2964 | addChildFlags_Widget( | ||
2965 | dlg, | ||
2966 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), | ||
2967 | frameless_WidgetFlag); | ||
2968 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); | ||
2969 | iWidget *headings, *values; | ||
2970 | iWidget *page; | ||
2971 | addChild_Widget(dlg, iClob(page = makeTwoColumns_Widget(&headings, &values))); | ||
2972 | setId_Widget(page, "xlt.langs"); | ||
2973 | iLabelWidget *fromLang, *toLang; | ||
2974 | const size_t numLangs = iElemCount(languages) - 1; | ||
2975 | const char *widestLabel = languages[findWidestLabel_MenuItem(languages, numLangs)].label; | ||
2976 | /* Source language. */ { | ||
2977 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.translate.from}"))); | ||
2978 | setId_Widget(addChildFlags_Widget(values, | ||
2979 | iClob(fromLang = makeMenuButton_LabelWidget( | ||
2980 | widestLabel, languages, numLangs)), | ||
2981 | alignLeft_WidgetFlag), | ||
2982 | "xlt.from"); | ||
2983 | setBackgroundColor_Widget(findChild_Widget(as_Widget(fromLang), "menu"), | ||
2984 | uiBackgroundMenu_ColorId); | ||
2985 | } | ||
2986 | /* Target language. */ { | ||
2987 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.translate.to}"))); | ||
2988 | setId_Widget(addChildFlags_Widget(values, | ||
2989 | iClob(toLang = makeMenuButton_LabelWidget( | ||
2990 | widestLabel, languages, numLangs)), | ||
2991 | alignLeft_WidgetFlag), | ||
2992 | "xlt.to"); | ||
2993 | setBackgroundColor_Widget(findChild_Widget(as_Widget(toLang), "menu"), | ||
2994 | uiBackgroundMenu_ColorId); | ||
2995 | } | ||
2996 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); | ||
2997 | addChild_Widget(dlg, iClob(makeDialogButtons_Widget(actions, iElemCount(actions)))); | ||
2998 | addChild_Widget(parent, iClob(dlg)); | ||
2999 | arrange_Widget(dlg); | ||
3000 | } | ||
3001 | /* Update choices. */ | ||
3002 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.from"), | ||
3003 | languages[prefs_App()->langFrom].command); | ||
3004 | updateDropdownSelection_LabelWidget(findChild_Widget(dlg, "xlt.to"), | ||
3005 | languages[prefs_App()->langTo].command); | ||
3006 | // updateText_LabelWidget( | ||
3007 | // findChild_Widget(dlg, "xlt.from"), | ||
3008 | // text_LabelWidget(child_Widget(findChild_Widget(findChild_Widget(dlg, "xlt.from"), "menu"), | ||
3009 | // prefs_App()->langFrom))); | ||
3010 | // updateText_LabelWidget( | ||
3011 | // findChild_Widget(dlg, "xlt.to"), | ||
3012 | // text_LabelWidget(child_Widget(findChild_Widget(findChild_Widget(dlg, "xlt.to"), "menu"), | ||
3013 | // prefs_App()->langTo))); | ||
2286 | setCommandHandler_Widget(dlg, translationHandler_); | 3014 | setCommandHandler_Widget(dlg, translationHandler_); |
2287 | addChildFlags_Widget(dlg, | 3015 | setupSheetTransition_Mobile(dlg, iTrue); |
2288 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), | ||
2289 | frameless_WidgetFlag); | ||
2290 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); | ||
2291 | iWidget *headings, *values; | ||
2292 | iWidget *page; | ||
2293 | addChild_Widget(dlg, iClob(page = makeTwoColumns_Widget(&headings, &values))); | ||
2294 | setId_Widget(page, "xlt.langs"); | ||
2295 | iLabelWidget *fromLang, *toLang; | ||
2296 | /* Source language. */ { | ||
2297 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.translate.from}"))); | ||
2298 | setId_Widget( | ||
2299 | addChildFlags_Widget(values, | ||
2300 | iClob(fromLang = makeMenuButton_LabelWidget( | ||
2301 | "${lang.pt}", languages, iElemCount(languages))), | ||
2302 | alignLeft_WidgetFlag), | ||
2303 | "xlt.from"); | ||
2304 | iWidget *langMenu = findChild_Widget(as_Widget(fromLang), "menu"); | ||
2305 | updateText_LabelWidget(fromLang, | ||
2306 | text_LabelWidget(child_Widget(langMenu, prefs_App()->langFrom))); | ||
2307 | setBackgroundColor_Widget(langMenu, uiBackgroundMenu_ColorId); | ||
2308 | } | ||
2309 | /* Target language. */ { | ||
2310 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.translate.to}"))); | ||
2311 | setId_Widget(addChildFlags_Widget(values, | ||
2312 | iClob(toLang = makeMenuButton_LabelWidget( | ||
2313 | "${lang.pt}", languages, iElemCount(languages))), | ||
2314 | alignLeft_WidgetFlag), | ||
2315 | "xlt.to"); | ||
2316 | iWidget *langMenu = findChild_Widget(as_Widget(toLang), "menu"); | ||
2317 | setBackgroundColor_Widget(langMenu, uiBackgroundMenu_ColorId); | ||
2318 | updateText_LabelWidget(toLang, | ||
2319 | text_LabelWidget(child_Widget(langMenu, prefs_App()->langTo))); | ||
2320 | } | ||
2321 | addChild_Widget(dlg, iClob(makePadding_Widget(lineHeight_Text(uiLabel_FontId)))); | ||
2322 | addChild_Widget( | ||
2323 | dlg, | ||
2324 | iClob(makeDialogButtons_Widget( | ||
2325 | (iMenuItem[]){ | ||
2326 | { "${cancel}", SDLK_ESCAPE, 0, "translation.cancel" }, | ||
2327 | { uiTextAction_ColorEscape "${dlg.translate}", SDLK_RETURN, 0, "translation.submit" } }, | ||
2328 | 2))); | ||
2329 | addChild_Widget(parent, iClob(dlg)); | ||
2330 | arrange_Widget(dlg); | ||
2331 | finalizeSheet_Mobile(dlg); | ||
2332 | return dlg; | 3016 | return dlg; |
2333 | } | 3017 | } |
diff --git a/src/ui/util.h b/src/ui/util.h index 2423f834..5d092700 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -220,18 +220,32 @@ struct Impl_MenuItem { | |||
220 | const char *label; | 220 | const char *label; |
221 | int key; | 221 | int key; |
222 | int kmods; | 222 | int kmods; |
223 | const char *command; | 223 | union { |
224 | const char *command; | ||
225 | const void *data; | ||
226 | }; | ||
224 | }; | 227 | }; |
225 | 228 | ||
226 | iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */ | 229 | iWidget * makeMenu_Widget (iWidget *parent, const iMenuItem *items, size_t n); /* returns no ref */ |
227 | void openMenu_Widget (iWidget *, iInt2 windowCoord); | 230 | void makeMenuItems_Widget (iWidget *menu, const iMenuItem *items, size_t n); |
228 | void openMenuFlags_Widget(iWidget *, iInt2 windowCoord, iBool postCommands); | 231 | void openMenu_Widget (iWidget *, iInt2 windowCoord); |
229 | void closeMenu_Widget (iWidget *); | 232 | void openMenuFlags_Widget (iWidget *, iInt2 windowCoord, iBool postCommands); |
233 | void closeMenu_Widget (iWidget *); | ||
234 | void releaseNativeMenu_Widget (iWidget *); | ||
230 | 235 | ||
231 | iLabelWidget * findMenuItem_Widget (iWidget *menu, const char *command); | 236 | size_t findWidestLabel_MenuItem (const iMenuItem *items, size_t num); |
232 | void setMenuItemDisabled_Widget (iWidget *menu, const char *command, iBool disable); | 237 | void setSelected_NativeMenuItem (iMenuItem *item, iBool isSelected); |
233 | 238 | ||
234 | int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see macro below */ | 239 | iChar removeIconPrefix_String (iString *); |
240 | |||
241 | iLabelWidget * findMenuItem_Widget (iWidget *menu, const char *command); | ||
242 | iMenuItem * findNativeMenuItem_Widget (iWidget *menu, const char *commandSuffix); | ||
243 | void setMenuItemDisabled_Widget (iWidget *menu, const char *command, iBool disable); | ||
244 | void setMenuItemDisabledByIndex_Widget(iWidget *menu, size_t index, iBool disable); | ||
245 | void setMenuItemLabel_Widget (iWidget *menu, const char *command, const char *newLabel); | ||
246 | void setMenuItemLabelByIndex_Widget (iWidget *menu, size_t index, const char *newLabel); | ||
247 | |||
248 | int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see macro below */ | ||
235 | 249 | ||
236 | #define processContextMenuEvent_Widget(menu, sdlEvent, stmtEaten) \ | 250 | #define processContextMenuEvent_Widget(menu, sdlEvent, stmtEaten) \ |
237 | for (const int result = checkContextMenu_Widget((menu), (sdlEvent));;) { \ | 251 | for (const int result = checkContextMenu_Widget((menu), (sdlEvent));;) { \ |
@@ -239,7 +253,8 @@ int checkContextMenu_Widget (iWidget *, const SDL_Event *ev); /* see mac | |||
239 | break; \ | 253 | break; \ |
240 | } | 254 | } |
241 | 255 | ||
242 | iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n); | 256 | iLabelWidget * makeMenuButton_LabelWidget (const char *label, const iMenuItem *items, size_t n); |
257 | void updateDropdownSelection_LabelWidget (iLabelWidget *dropButton, const char *selectedCommand); | ||
243 | 258 | ||
244 | /*-----------------------------------------------------------------------------------------------*/ | 259 | /*-----------------------------------------------------------------------------------------------*/ |
245 | 260 | ||
@@ -268,6 +283,8 @@ void useSheetStyle_Widget (iWidget *); | |||
268 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); | 283 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); |
269 | iWidget * makeTwoColumns_Widget (iWidget **headings, iWidget **values); | 284 | iWidget * makeTwoColumns_Widget (iWidget **headings, iWidget **values); |
270 | 285 | ||
286 | iLabelWidget *dialogAcceptButton_Widget (const iWidget *); | ||
287 | |||
271 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 288 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
272 | const char *labelText, const char *inputId, | 289 | const char *labelText, const char *inputId, |
273 | iInputWidget *input); | 290 | iInputWidget *input); |
diff --git a/src/ui/widget.c b/src/ui/widget.c index 4f567989..6b9ee11d 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -40,6 +40,67 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
40 | # include "../ios.h" | 40 | # include "../ios.h" |
41 | #endif | 41 | #endif |
42 | 42 | ||
43 | struct Impl_WidgetDrawBuffer { | ||
44 | SDL_Texture *texture; | ||
45 | iInt2 size; | ||
46 | iBool isValid; | ||
47 | SDL_Texture *oldTarget; | ||
48 | iInt2 oldOrigin; | ||
49 | }; | ||
50 | |||
51 | static void init_WidgetDrawBuffer(iWidgetDrawBuffer *d) { | ||
52 | d->texture = NULL; | ||
53 | d->size = zero_I2(); | ||
54 | d->isValid = iFalse; | ||
55 | d->oldTarget = NULL; | ||
56 | } | ||
57 | |||
58 | static void deinit_WidgetDrawBuffer(iWidgetDrawBuffer *d) { | ||
59 | SDL_DestroyTexture(d->texture); | ||
60 | } | ||
61 | |||
62 | iDefineTypeConstruction(WidgetDrawBuffer) | ||
63 | |||
64 | static void realloc_WidgetDrawBuffer(iWidgetDrawBuffer *d, SDL_Renderer *render, iInt2 size) { | ||
65 | if (!isEqual_I2(d->size, size)) { | ||
66 | d->size = size; | ||
67 | if (d->texture) { | ||
68 | SDL_DestroyTexture(d->texture); | ||
69 | } | ||
70 | d->texture = SDL_CreateTexture(render, | ||
71 | SDL_PIXELFORMAT_RGBA8888, | ||
72 | SDL_TEXTUREACCESS_STATIC | SDL_TEXTUREACCESS_TARGET, | ||
73 | size.x, | ||
74 | size.y); | ||
75 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | ||
76 | d->isValid = iFalse; | ||
77 | } | ||
78 | } | ||
79 | |||
80 | static void release_WidgetDrawBuffer(iWidgetDrawBuffer *d) { | ||
81 | if (d->texture) { | ||
82 | SDL_DestroyTexture(d->texture); | ||
83 | d->texture = NULL; | ||
84 | } | ||
85 | d->size = zero_I2(); | ||
86 | d->isValid = iFalse; | ||
87 | } | ||
88 | |||
89 | static iRect boundsForDraw_Widget_(const iWidget *d) { | ||
90 | iRect bounds = bounds_Widget(d); | ||
91 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | ||
92 | bounds.size.y = iMax(bounds.size.y, size_Root(d->root).y); | ||
93 | } | ||
94 | return bounds; | ||
95 | } | ||
96 | |||
97 | static iBool checkDrawBuffer_Widget_(const iWidget *d) { | ||
98 | return d->drawBuf && d->drawBuf->isValid && | ||
99 | isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size); | ||
100 | } | ||
101 | |||
102 | /*----------------------------------------------------------------------------------------------*/ | ||
103 | |||
43 | static void printInfo_Widget_(const iWidget *); | 104 | static void printInfo_Widget_(const iWidget *); |
44 | 105 | ||
45 | void releaseChildren_Widget(iWidget *d) { | 106 | void releaseChildren_Widget(iWidget *d) { |
@@ -66,6 +127,7 @@ void init_Widget(iWidget *d) { | |||
66 | d->children = NULL; | 127 | d->children = NULL; |
67 | d->parent = NULL; | 128 | d->parent = NULL; |
68 | d->commandHandler = NULL; | 129 | d->commandHandler = NULL; |
130 | d->drawBuf = NULL; | ||
69 | iZap(d->padding); | 131 | iZap(d->padding); |
70 | } | 132 | } |
71 | 133 | ||
@@ -82,6 +144,7 @@ static void visualOffsetAnimation_Widget_(void *ptr) { | |||
82 | 144 | ||
83 | void deinit_Widget(iWidget *d) { | 145 | void deinit_Widget(iWidget *d) { |
84 | releaseChildren_Widget(d); | 146 | releaseChildren_Widget(d); |
147 | delete_WidgetDrawBuffer(d->drawBuf); | ||
85 | #if 0 && !defined (NDEBUG) | 148 | #if 0 && !defined (NDEBUG) |
86 | printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), | 149 | printf("widget %p (%s) deleted (on top:%d)\n", d, cstr_String(&d->id), |
87 | d->flags & keepOnTop_WidgetFlag ? 1 : 0); | 150 | d->flags & keepOnTop_WidgetFlag ? 1 : 0); |
@@ -93,6 +156,16 @@ void deinit_Widget(iWidget *d) { | |||
93 | if (d->flags & visualOffset_WidgetFlag) { | 156 | if (d->flags & visualOffset_WidgetFlag) { |
94 | removeTicker_App(visualOffsetAnimation_Widget_, d); | 157 | removeTicker_App(visualOffsetAnimation_Widget_, d); |
95 | } | 158 | } |
159 | iWindow *win = get_Window(); | ||
160 | if (win->lastHover == d) { | ||
161 | win->lastHover = NULL; | ||
162 | } | ||
163 | if (win->hover == d) { | ||
164 | win->hover = NULL; | ||
165 | } | ||
166 | if (d->flags & nativeMenu_WidgetFlag) { | ||
167 | releaseNativeMenu_Widget(d); | ||
168 | } | ||
96 | widgetDestroyed_Touch(d); | 169 | widgetDestroyed_Touch(d); |
97 | } | 170 | } |
98 | 171 | ||
@@ -100,11 +173,15 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) { | |||
100 | d->flags |= destroyPending_WidgetFlag; | 173 | d->flags |= destroyPending_WidgetFlag; |
101 | if (isFocused_Widget(d)) { | 174 | if (isFocused_Widget(d)) { |
102 | setFocus_Widget(NULL); | 175 | setFocus_Widget(NULL); |
103 | return; | 176 | //return; /* TODO: Why?! */ |
104 | } | 177 | } |
105 | remove_Periodic(periodic_App(), d); | 178 | remove_Periodic(periodic_App(), d); |
179 | iWindow *win = get_Window(); | ||
106 | if (isHover_Widget(d)) { | 180 | if (isHover_Widget(d)) { |
107 | get_Window()->hover = NULL; | 181 | win->hover = NULL; |
182 | } | ||
183 | if (win->lastHover == d) { | ||
184 | win->lastHover = NULL; | ||
108 | } | 185 | } |
109 | iForEach(ObjectList, i, d->children) { | 186 | iForEach(ObjectList, i, d->children) { |
110 | aboutToBeDestroyed_Widget_(as_Widget(i.object)); | 187 | aboutToBeDestroyed_Widget_(as_Widget(i.object)); |
@@ -151,6 +228,7 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) { | |||
151 | } | 228 | } |
152 | else { | 229 | else { |
153 | removeOne_PtrArray(onTop, d); | 230 | removeOne_PtrArray(onTop, d); |
231 | iAssert(indexOf_PtrArray(onTop, d) == iInvalidPos); | ||
154 | } | 232 | } |
155 | } | 233 | } |
156 | if (d->flags & arrangeWidth_WidgetFlag && | 234 | if (d->flags & arrangeWidth_WidgetFlag && |
@@ -196,6 +274,10 @@ iWidget *root_Widget(const iWidget *d) { | |||
196 | return d ? d->root->widget : NULL; | 274 | return d ? d->root->widget : NULL; |
197 | } | 275 | } |
198 | 276 | ||
277 | iWindow *window_Widget(const iAnyObject *d) { | ||
278 | return constAs_Widget(d)->root->window; | ||
279 | } | ||
280 | |||
199 | void showCollapsed_Widget(iWidget *d, iBool show) { | 281 | void showCollapsed_Widget(iWidget *d, iBool show) { |
200 | const iBool isVisible = !(d->flags & hidden_WidgetFlag); | 282 | const iBool isVisible = !(d->flags & hidden_WidgetFlag); |
201 | if ((isVisible && !show) || (!isVisible && show)) { | 283 | if ((isVisible && !show) || (!isVisible && show)) { |
@@ -452,7 +534,7 @@ static void arrange_Widget_(iWidget *d) { | |||
452 | else if (d->flags & centerHorizontal_WidgetFlag) { | 534 | else if (d->flags & centerHorizontal_WidgetFlag) { |
453 | centerHorizontal_Widget_(d); | 535 | centerHorizontal_Widget_(d); |
454 | } | 536 | } |
455 | if (d->flags & resizeToParentWidth_WidgetFlag) { | 537 | if (d->flags & resizeToParentWidth_WidgetFlag && d->parent) { |
456 | iRect childBounds = zero_Rect(); | 538 | iRect childBounds = zero_Rect(); |
457 | if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) { | 539 | if (flags_Widget(d->parent) & arrangeWidth_WidgetFlag) { |
458 | /* Can't go narrower than what the children require, though. */ | 540 | /* Can't go narrower than what the children require, though. */ |
@@ -462,7 +544,7 @@ static void arrange_Widget_(iWidget *d) { | |||
462 | setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)), | 544 | setWidth_Widget_(d, iMaxi(width_Rect(innerRect_Widget_(d->parent)), |
463 | width_Rect(childBounds))); | 545 | width_Rect(childBounds))); |
464 | } | 546 | } |
465 | if (d->flags & resizeToParentHeight_WidgetFlag) { | 547 | if (d->flags & resizeToParentHeight_WidgetFlag && d->parent) { |
466 | TRACE(d, "resize to parent height"); | 548 | TRACE(d, "resize to parent height"); |
467 | setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent))); | 549 | setHeight_Widget_(d, height_Rect(innerRect_Widget_(d->parent))); |
468 | } | 550 | } |
@@ -817,9 +899,6 @@ iInt2 localToWindow_Widget(const iWidget *d, iInt2 localCoord) { | |||
817 | applyVisualOffset_Widget_(w, &pos); | 899 | applyVisualOffset_Widget_(w, &pos); |
818 | addv_I2(&window, pos); | 900 | addv_I2(&window, pos); |
819 | } | 901 | } |
820 | #if defined (iPlatformMobile) | ||
821 | window.y += value_Anim(&get_Window()->rootOffset); | ||
822 | #endif | ||
823 | return window; | 902 | return window; |
824 | } | 903 | } |
825 | 904 | ||
@@ -899,15 +978,18 @@ static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) { | |||
899 | } | 978 | } |
900 | 979 | ||
901 | void unhover_Widget(void) { | 980 | void unhover_Widget(void) { |
902 | get_Window()->hover = NULL; | 981 | iWidget **hover = &get_Window()->hover; |
982 | if (*hover) { | ||
983 | refresh_Widget(*hover); | ||
984 | } | ||
985 | *hover = NULL; | ||
903 | } | 986 | } |
904 | 987 | ||
905 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | 988 | iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { |
906 | //iAssert(d->root == get_Root()); | ||
907 | if (!d->parent) { | 989 | if (!d->parent) { |
908 | if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) { | 990 | if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) { |
909 | /* Root dispatches keyboard events directly to the focused widget. */ | 991 | /* Root dispatches keyboard events directly to the focused widget. */ |
910 | if (dispatchEvent_Widget(get_Window()->focus, ev)) { | 992 | if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) { |
911 | return iTrue; | 993 | return iTrue; |
912 | } | 994 | } |
913 | } | 995 | } |
@@ -936,7 +1018,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
936 | } | 1018 | } |
937 | } | 1019 | } |
938 | else if (ev->type == SDL_MOUSEMOTION && | 1020 | else if (ev->type == SDL_MOUSEMOTION && |
939 | (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) && | 1021 | ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) && |
1022 | (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) && | ||
940 | flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && | 1023 | flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && |
941 | ~flags_Widget(d) & disabled_WidgetFlag) { | 1024 | ~flags_Widget(d) & disabled_WidgetFlag) { |
942 | if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { | 1025 | if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { |
@@ -955,11 +1038,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
955 | iReverseForEach(ObjectList, i, d->children) { | 1038 | iReverseForEach(ObjectList, i, d->children) { |
956 | iWidget *child = as_Widget(i.object); | 1039 | iWidget *child = as_Widget(i.object); |
957 | //iAssert(child->root == d->root); | 1040 | //iAssert(child->root == d->root); |
958 | if (child == get_Window()->focus && isKeyboardEvent_(ev)) { | 1041 | if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) { |
959 | continue; /* Already dispatched. */ | 1042 | continue; /* Already dispatched. */ |
960 | } | 1043 | } |
961 | if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { | 1044 | if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { |
962 | /* Already dispatched. */ | 1045 | /* Already dispatched. */ |
963 | continue; | 1046 | continue; |
964 | } | 1047 | } |
965 | if (dispatchEvent_Widget(child, ev)) { | 1048 | if (dispatchEvent_Widget(child, ev)) { |
@@ -974,7 +1057,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
974 | #endif | 1057 | #endif |
975 | #if 0 | 1058 | #if 0 |
976 | if (ev->type == SDL_MOUSEMOTION) { | 1059 | if (ev->type == SDL_MOUSEMOTION) { |
977 | printf("[%p] %s:'%s' (on top) ate the motion\n", | 1060 | printf("[%p] %s:'%s' ate the motion\n", |
978 | child, class_Widget(child)->name, | 1061 | child, class_Widget(child)->name, |
979 | cstr_String(id_Widget(child))); | 1062 | cstr_String(id_Widget(child))); |
980 | fflush(stdout); | 1063 | fflush(stdout); |
@@ -1008,24 +1091,60 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1008 | return iFalse; | 1091 | return iFalse; |
1009 | } | 1092 | } |
1010 | 1093 | ||
1094 | void scrollInfo_Widget(const iWidget *d, iWidgetScrollInfo *info) { | ||
1095 | iRect bounds = boundsWithoutVisualOffset_Widget(d); | ||
1096 | const iRect winRect = adjusted_Rect(safeRect_Root(d->root), | ||
1097 | zero_I2(), | ||
1098 | init_I2(0, -get_MainWindow()->keyboardHeight)); | ||
1099 | info->height = bounds.size.y; | ||
1100 | info->avail = height_Rect(winRect); | ||
1101 | if (info->avail >= info->height) { | ||
1102 | info->normScroll = 0.0f; | ||
1103 | info->thumbY = 0; | ||
1104 | info->thumbHeight = 0; | ||
1105 | } | ||
1106 | else { | ||
1107 | int scroll = top_Rect(winRect) - top_Rect(bounds); | ||
1108 | info->normScroll = scroll / (float) (info->height - info->avail); | ||
1109 | info->normScroll = iClamp(info->normScroll, 0.0f, 1.0f); | ||
1110 | info->thumbHeight = iMin(info->avail / 2, info->avail * info->avail / info->height); | ||
1111 | info->thumbY = top_Rect(winRect) + (info->avail - info->thumbHeight) * info->normScroll; | ||
1112 | } | ||
1113 | } | ||
1114 | |||
1011 | iBool scrollOverflow_Widget(iWidget *d, int delta) { | 1115 | iBool scrollOverflow_Widget(iWidget *d, int delta) { |
1012 | iRect bounds = boundsWithoutVisualOffset_Widget(d); | 1116 | iRect bounds = boundsWithoutVisualOffset_Widget(d); |
1013 | const iInt2 rootSize = size_Root(d->root); | 1117 | const iRect winRect = adjusted_Rect(safeRect_Root(d->root), |
1014 | const iRect winRect = safeRect_Root(d->root); | 1118 | zero_I2(), |
1015 | const int yTop = top_Rect(winRect); | 1119 | init_I2(0, -get_MainWindow()->keyboardHeight)); |
1016 | const int yBottom = bottom_Rect(winRect); | 1120 | const int yTop = top_Rect(winRect); |
1121 | const int yBottom = bottom_Rect(winRect); | ||
1017 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { | 1122 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { |
1018 | return iFalse; /* fits inside just fine */ | 1123 | return iFalse; /* fits inside just fine */ |
1019 | } | 1124 | } |
1020 | //const int safeBottom = rootSize.y - yBottom; | 1125 | //const int safeBottom = rootSize.y - yBottom; |
1021 | bounds.pos.y += delta; | 1126 | iRangei validPosRange = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; |
1022 | const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; | 1127 | if (validPosRange.start > validPosRange.end) { |
1128 | validPosRange.start = validPosRange.end; /* no room to scroll */ | ||
1129 | } | ||
1130 | if (delta) { | ||
1131 | if (delta < 0 && bounds.pos.y < validPosRange.start) { | ||
1132 | delta = 0; | ||
1133 | } | ||
1134 | if (delta > 0 && bounds.pos.y > validPosRange.end) { | ||
1135 | delta = 0; | ||
1136 | } | ||
1137 | bounds.pos.y += delta; | ||
1138 | if (delta < 0) { | ||
1139 | bounds.pos.y = iMax(bounds.pos.y, validPosRange.start); | ||
1140 | } | ||
1141 | else if (delta > 0) { | ||
1142 | bounds.pos.y = iMin(bounds.pos.y, validPosRange.end); | ||
1143 | } | ||
1023 | // printf("range: %d ... %d\n", range.start, range.end); | 1144 | // printf("range: %d ... %d\n", range.start, range.end); |
1024 | if (range.start >= range.end) { | ||
1025 | bounds.pos.y = range.end; | ||
1026 | } | 1145 | } |
1027 | else { | 1146 | else { |
1028 | bounds.pos.y = iClamp(bounds.pos.y, range.start, range.end); | 1147 | bounds.pos.y = iClamp(bounds.pos.y, validPosRange.start, validPosRange.end); |
1029 | } | 1148 | } |
1030 | // if (delta >= 0) { | 1149 | // if (delta >= 0) { |
1031 | // bounds.pos.y = iMin(bounds.pos.y, yTop); | 1150 | // bounds.pos.y = iMin(bounds.pos.y, yTop); |
@@ -1036,7 +1155,8 @@ iBool scrollOverflow_Widget(iWidget *d, int delta) { | |||
1036 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); | 1155 | const iInt2 newPos = windowToInner_Widget(d->parent, bounds.pos); |
1037 | if (!isEqual_I2(newPos, d->rect.pos)) { | 1156 | if (!isEqual_I2(newPos, d->rect.pos)) { |
1038 | d->rect.pos = newPos; | 1157 | d->rect.pos = newPos; |
1039 | refresh_Widget(d); | 1158 | // refresh_Widget(d); |
1159 | postRefresh_App(); | ||
1040 | } | 1160 | } |
1041 | return height_Rect(bounds) > height_Rect(winRect); | 1161 | return height_Rect(bounds) > height_Rect(winRect); |
1042 | } | 1162 | } |
@@ -1077,6 +1197,9 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1077 | } | 1197 | } |
1078 | if (ev->user.code == command_UserEventCode) { | 1198 | if (ev->user.code == command_UserEventCode) { |
1079 | const char *cmd = command_UserEvent(ev); | 1199 | const char *cmd = command_UserEvent(ev); |
1200 | if (d->drawBuf && equal_Command(cmd, "theme.changed")) { | ||
1201 | d->drawBuf->isValid = iFalse; | ||
1202 | } | ||
1080 | if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && | 1203 | if (d->flags & (leftEdgeDraggable_WidgetFlag | rightEdgeDraggable_WidgetFlag) && |
1081 | isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && | 1204 | isVisible_Widget(d) && ~d->flags & disabled_WidgetFlag && |
1082 | equal_Command(cmd, "edgeswipe.moved")) { | 1205 | equal_Command(cmd, "edgeswipe.moved")) { |
@@ -1130,7 +1253,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
1130 | ev->button.x, | 1253 | ev->button.x, |
1131 | ev->button.y); | 1254 | ev->button.y); |
1132 | } | 1255 | } |
1133 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | 1256 | setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW); |
1134 | return iTrue; | 1257 | return iTrue; |
1135 | } | 1258 | } |
1136 | return iFalse; | 1259 | return iFalse; |
@@ -1147,14 +1270,14 @@ int backgroundFadeColor_Widget(void) { | |||
1147 | } | 1270 | } |
1148 | } | 1271 | } |
1149 | 1272 | ||
1150 | void drawBackground_Widget(const iWidget *d) { | 1273 | iLocalDef iBool isDrawn_Widget_(const iWidget *d) { |
1151 | if (d->flags & noBackground_WidgetFlag) { | 1274 | return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; |
1152 | return; | 1275 | } |
1153 | } | 1276 | |
1154 | if (d->flags & hidden_WidgetFlag && ~d->flags & visualOffset_WidgetFlag) { | 1277 | void drawLayerEffects_Widget(const iWidget *d) { |
1155 | return; | 1278 | /* Layered effects are not buffered, so they are drawn here separately. */ |
1156 | } | 1279 | iAssert(isDrawn_Widget_(d)); |
1157 | /* Popup menus have a shadowed border. */ | 1280 | iAssert(window_Widget(d) == get_Window()); |
1158 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; | 1281 | iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; |
1159 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; | 1282 | iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; |
1160 | if (deviceType_App() == phone_AppDeviceType) { | 1283 | if (deviceType_App() == phone_AppDeviceType) { |
@@ -1163,13 +1286,12 @@ void drawBackground_Widget(const iWidget *d) { | |||
1163 | shadowBorder = iFalse; | 1286 | shadowBorder = iFalse; |
1164 | } | 1287 | } |
1165 | } | 1288 | } |
1289 | const iBool isFaded = fadeBackground && ~d->flags & noFadeBackground_WidgetFlag; | ||
1166 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { | 1290 | if (shadowBorder && ~d->flags & noShadowBorder_WidgetFlag) { |
1167 | iPaint p; | 1291 | iPaint p; |
1168 | init_Paint(&p); | 1292 | init_Paint(&p); |
1169 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1293 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1170 | } | 1294 | } |
1171 | const iBool isFaded = fadeBackground && | ||
1172 | ~d->flags & noFadeBackground_WidgetFlag; | ||
1173 | if (isFaded) { | 1295 | if (isFaded) { |
1174 | iPaint p; | 1296 | iPaint p; |
1175 | init_Paint(&p); | 1297 | init_Paint(&p); |
@@ -1183,15 +1305,68 @@ void drawBackground_Widget(const iWidget *d) { | |||
1183 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); | 1305 | fillRect_Paint(&p, rect_Root(d->root), backgroundFadeColor_Widget()); |
1184 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 1306 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
1185 | } | 1307 | } |
1308 | #if defined (iPlatformAppleMobile) | ||
1309 | if (d->bgColor >= 0 && d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag | | ||
1310 | drawBackgroundToVerticalSafeArea_WidgetFlag)) { | ||
1311 | iPaint p; | ||
1312 | init_Paint(&p); | ||
1313 | const iRect rect = bounds_Widget(d); | ||
1314 | const iInt2 rootSize = size_Root(d->root); | ||
1315 | const iInt2 center = divi_I2(rootSize, 2); | ||
1316 | int top = 0, right = 0, bottom = 0, left = 0; | ||
1317 | if (d->flags & drawBackgroundToHorizontalSafeArea_WidgetFlag) { | ||
1318 | const iBool isWide = width_Rect(rect) > rootSize.x * 9 / 10; | ||
1319 | if (isWide || mid_Rect(rect).x < center.x) { | ||
1320 | left = -left_Rect(rect); | ||
1321 | } | ||
1322 | if (isWide || mid_Rect(rect).x > center.x) { | ||
1323 | right = rootSize.x - right_Rect(rect); | ||
1324 | } | ||
1325 | } | ||
1326 | if (d->flags & drawBackgroundToVerticalSafeArea_WidgetFlag) { | ||
1327 | if (top_Rect(rect) > center.y) { | ||
1328 | bottom = rootSize.y - bottom_Rect(rect); | ||
1329 | } | ||
1330 | if (bottom_Rect(rect) < center.y) { | ||
1331 | top = -top_Rect(rect); | ||
1332 | } | ||
1333 | } | ||
1334 | if (top < 0) { | ||
1335 | fillRect_Paint(&p, (iRect){ init_I2(left_Rect(rect), 0), | ||
1336 | init_I2(width_Rect(rect), top_Rect(rect)) }, | ||
1337 | d->bgColor); | ||
1338 | } | ||
1339 | if (left < 0) { | ||
1340 | fillRect_Paint(&p, (iRect){ init_I2(0, top_Rect(rect)), | ||
1341 | init_I2(left_Rect(rect), height_Rect(rect)) }, d->bgColor); | ||
1342 | } | ||
1343 | if (right > 0) { | ||
1344 | fillRect_Paint(&p, (iRect){ init_I2(right_Rect(rect), top_Rect(rect)), | ||
1345 | init_I2(right, height_Rect(rect)) }, d->bgColor); | ||
1346 | } | ||
1347 | // adjustEdges_Rect(&rect, iMin(0, top), iMax(0, right), iMax(0, bottom), iMin(0, left)); | ||
1348 | } | ||
1349 | #endif | ||
1350 | } | ||
1351 | |||
1352 | void drawBackground_Widget(const iWidget *d) { | ||
1353 | if (d->flags & noBackground_WidgetFlag) { | ||
1354 | return; | ||
1355 | } | ||
1356 | if (!isDrawn_Widget_(d)) { | ||
1357 | return; | ||
1358 | } | ||
1359 | /* Popup menus have a shadowed border. */ | ||
1186 | if (d->bgColor >= 0 || d->frameColor >= 0) { | 1360 | if (d->bgColor >= 0 || d->frameColor >= 0) { |
1187 | iRect rect = bounds_Widget(d); | 1361 | iRect rect = bounds_Widget(d); |
1188 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | 1362 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { |
1189 | rect.size.y = size_Root(d->root).y - top_Rect(rect); | 1363 | rect.size.y += size_Root(d->root).y; // = iMax(rect.size.y, size_Root(d->root).y - top_Rect(rect)); |
1190 | } | 1364 | } |
1191 | iPaint p; | 1365 | iPaint p; |
1192 | init_Paint(&p); | 1366 | init_Paint(&p); |
1193 | if (d->bgColor >= 0) { | 1367 | if (d->bgColor >= 0) { |
1194 | #if defined (iPlatformAppleMobile) | 1368 | #if 0 && defined (iPlatformAppleMobile) |
1369 | /* TODO: This is part of the unbuffered draw (layer effects). */ | ||
1195 | if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag | | 1370 | if (d->flags & (drawBackgroundToHorizontalSafeArea_WidgetFlag | |
1196 | drawBackgroundToVerticalSafeArea_WidgetFlag)) { | 1371 | drawBackgroundToVerticalSafeArea_WidgetFlag)) { |
1197 | const iInt2 rootSize = size_Root(d->root); | 1372 | const iInt2 rootSize = size_Root(d->root); |
@@ -1214,7 +1389,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1214 | top = -top_Rect(rect); | 1389 | top = -top_Rect(rect); |
1215 | } | 1390 | } |
1216 | } | 1391 | } |
1217 | adjustEdges_Rect(&rect, top, right, bottom, left); | 1392 | adjustEdges_Rect(&rect, iMin(0, top), iMax(0, right), iMax(0, bottom), iMin(0, left)); |
1218 | } | 1393 | } |
1219 | #endif | 1394 | #endif |
1220 | fillRect_Paint(&p, rect, d->bgColor); | 1395 | fillRect_Paint(&p, rect, d->bgColor); |
@@ -1242,8 +1417,62 @@ void drawBackground_Widget(const iWidget *d) { | |||
1242 | } | 1417 | } |
1243 | } | 1418 | } |
1244 | 1419 | ||
1245 | iLocalDef iBool isDrawn_Widget_(const iWidget *d) { | 1420 | int drawCount_; |
1246 | return ~d->flags & hidden_WidgetFlag || d->flags & visualOffset_WidgetFlag; | 1421 | |
1422 | static iBool isRoot_Widget_(const iWidget *d) { | ||
1423 | return d == d->root->widget; | ||
1424 | } | ||
1425 | |||
1426 | iLocalDef iBool isFullyContainedByOther_Rect(const iRect d, const iRect other) { | ||
1427 | if (isEmpty_Rect(other)) { | ||
1428 | /* Nothing is contained by empty. */ | ||
1429 | return iFalse; | ||
1430 | } | ||
1431 | if (isEmpty_Rect(d)) { | ||
1432 | /* Empty is fully contained by anything. */ | ||
1433 | return iTrue; | ||
1434 | } | ||
1435 | return equal_Rect(intersect_Rect(d, other), d); | ||
1436 | } | ||
1437 | |||
1438 | static void addToPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs, iRect *fullyMasked) { | ||
1439 | if (isDrawn_Widget_(d)) { | ||
1440 | iRect bounds = bounds_Widget(d); | ||
1441 | if (d->flags & drawBackgroundToBottom_WidgetFlag) { | ||
1442 | bounds.size.y += size_Root(d->root).y; // iMax(bounds.size.y, size_Root(d->root).y - top_Rect(bounds)); | ||
1443 | } | ||
1444 | if (isFullyContainedByOther_Rect(bounds, *fullyMasked)) { | ||
1445 | return; /* can't be seen */ | ||
1446 | } | ||
1447 | pushBack_PtrArray(pvs, d); | ||
1448 | if (d->bgColor >= 0 && ~d->flags & noBackground_WidgetFlag && | ||
1449 | isFullyContainedByOther_Rect(*fullyMasked, bounds)) { | ||
1450 | *fullyMasked = bounds; | ||
1451 | } | ||
1452 | } | ||
1453 | } | ||
1454 | |||
1455 | static void findPotentiallyVisible_Widget_(const iWidget *d, iPtrArray *pvs) { | ||
1456 | iRect fullyMasked = zero_Rect(); | ||
1457 | if (isRoot_Widget_(d)) { | ||
1458 | iReverseConstForEach(PtrArray, i, onTop_Root(d->root)) { | ||
1459 | const iWidget *top = i.ptr; | ||
1460 | iAssert(top->parent); | ||
1461 | addToPotentiallyVisible_Widget_(top, pvs, &fullyMasked); | ||
1462 | } | ||
1463 | } | ||
1464 | iReverseConstForEach(ObjectList, i, d->children) { | ||
1465 | const iWidget *child = i.object; | ||
1466 | if (~child->flags & keepOnTop_WidgetFlag) { | ||
1467 | addToPotentiallyVisible_Widget_(child, pvs, &fullyMasked); | ||
1468 | } | ||
1469 | } | ||
1470 | } | ||
1471 | |||
1472 | iLocalDef void incrementDrawCount_(const iWidget *d) { | ||
1473 | if (class_Widget(d) != &Class_Widget || d->bgColor >= 0 || d->frameColor >= 0) { | ||
1474 | drawCount_++; | ||
1475 | } | ||
1247 | } | 1476 | } |
1248 | 1477 | ||
1249 | void drawChildren_Widget(const iWidget *d) { | 1478 | void drawChildren_Widget(const iWidget *d) { |
@@ -1253,21 +1482,108 @@ void drawChildren_Widget(const iWidget *d) { | |||
1253 | iConstForEach(ObjectList, i, d->children) { | 1482 | iConstForEach(ObjectList, i, d->children) { |
1254 | const iWidget *child = constAs_Widget(i.object); | 1483 | const iWidget *child = constAs_Widget(i.object); |
1255 | if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { | 1484 | if (~child->flags & keepOnTop_WidgetFlag && isDrawn_Widget_(child)) { |
1485 | incrementDrawCount_(child); | ||
1256 | class_Widget(child)->draw(child); | 1486 | class_Widget(child)->draw(child); |
1257 | } | 1487 | } |
1258 | } | 1488 | } |
1489 | } | ||
1490 | |||
1491 | void drawRoot_Widget(const iWidget *d) { | ||
1492 | iAssert(d == d->root->widget); | ||
1259 | /* Root draws the on-top widgets on top of everything else. */ | 1493 | /* Root draws the on-top widgets on top of everything else. */ |
1260 | if (d == d->root->widget) { | 1494 | iPtrArray pvs; |
1261 | iConstForEach(PtrArray, i, onTop_Root(d->root)) { | 1495 | init_PtrArray(&pvs); |
1262 | const iWidget *top = *i.value; | 1496 | findPotentiallyVisible_Widget_(d, &pvs); |
1263 | class_Widget(top)->draw(top); | 1497 | iReverseConstForEach(PtrArray, i, &pvs) { |
1264 | } | 1498 | incrementDrawCount_(i.ptr); |
1265 | } | 1499 | class_Widget(i.ptr)->draw(i.ptr); |
1500 | } | ||
1501 | deinit_PtrArray(&pvs); | ||
1502 | } | ||
1503 | |||
1504 | void setDrawBufferEnabled_Widget(iWidget *d, iBool enable) { | ||
1505 | if (enable && !d->drawBuf) { | ||
1506 | d->drawBuf = new_WidgetDrawBuffer(); | ||
1507 | } | ||
1508 | else if (!enable && d->drawBuf) { | ||
1509 | delete_WidgetDrawBuffer(d->drawBuf); | ||
1510 | d->drawBuf = NULL; | ||
1511 | } | ||
1512 | } | ||
1513 | |||
1514 | static void beginBufferDraw_Widget_(const iWidget *d) { | ||
1515 | if (d->drawBuf) { | ||
1516 | // printf("[%p] drawbuffer update %d\n", d, d->drawBuf->isValid); | ||
1517 | if (d->drawBuf->isValid) { | ||
1518 | iAssert(!isEqual_I2(d->drawBuf->size, boundsForDraw_Widget_(d).size)); | ||
1519 | // printf(" drawBuf:%dx%d boundsForDraw:%dx%d\n", | ||
1520 | // d->drawBuf->size.x, d->drawBuf->size.y, | ||
1521 | // boundsForDraw_Widget_(d).size.x, | ||
1522 | // boundsForDraw_Widget_(d).size.y); | ||
1523 | } | ||
1524 | const iRect bounds = bounds_Widget(d); | ||
1525 | SDL_Renderer *render = renderer_Window(get_Window()); | ||
1526 | d->drawBuf->oldTarget = SDL_GetRenderTarget(render); | ||
1527 | d->drawBuf->oldOrigin = origin_Paint; | ||
1528 | realloc_WidgetDrawBuffer(d->drawBuf, render, boundsForDraw_Widget_(d).size); | ||
1529 | SDL_SetRenderTarget(render, d->drawBuf->texture); | ||
1530 | // SDL_SetRenderDrawColor(render, 255, 0, 0, 128); | ||
1531 | SDL_SetRenderDrawColor(render, 0, 0, 0, 0); | ||
1532 | SDL_RenderClear(render); | ||
1533 | origin_Paint = neg_I2(bounds.pos); /* with current visual offset */ | ||
1534 | // printf("beginBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); | ||
1535 | // fflush(stdout); | ||
1536 | } | ||
1537 | } | ||
1538 | |||
1539 | static void endBufferDraw_Widget_(const iWidget *d) { | ||
1540 | if (d->drawBuf) { | ||
1541 | d->drawBuf->isValid = iTrue; | ||
1542 | SDL_SetRenderTarget(renderer_Window(get_Window()), d->drawBuf->oldTarget); | ||
1543 | origin_Paint = d->drawBuf->oldOrigin; | ||
1544 | // printf("endBufferDraw: origin %d,%d\n", origin_Paint.x, origin_Paint.y); | ||
1545 | // fflush(stdout); | ||
1546 | } | ||
1266 | } | 1547 | } |
1267 | 1548 | ||
1268 | void draw_Widget(const iWidget *d) { | 1549 | void draw_Widget(const iWidget *d) { |
1269 | drawBackground_Widget(d); | 1550 | iAssert(window_Widget(d) == get_Window()); |
1270 | drawChildren_Widget(d); | 1551 | if (!isDrawn_Widget_(d)) { |
1552 | if (d->drawBuf) { | ||
1553 | // printf("[%p] drawBuffer released\n", d); | ||
1554 | release_WidgetDrawBuffer(d->drawBuf); | ||
1555 | } | ||
1556 | return; | ||
1557 | } | ||
1558 | drawLayerEffects_Widget(d); | ||
1559 | if (!d->drawBuf || !checkDrawBuffer_Widget_(d)) { | ||
1560 | beginBufferDraw_Widget_(d); | ||
1561 | drawBackground_Widget(d); | ||
1562 | drawChildren_Widget(d); | ||
1563 | endBufferDraw_Widget_(d); | ||
1564 | } | ||
1565 | if (d->drawBuf) { | ||
1566 | //iAssert(d->drawBuf->isValid); | ||
1567 | const iRect bounds = bounds_Widget(d); | ||
1568 | SDL_RenderCopy(renderer_Window(get_Window()), d->drawBuf->texture, NULL, | ||
1569 | &(SDL_Rect){ bounds.pos.x, bounds.pos.y, | ||
1570 | d->drawBuf->size.x, d->drawBuf->size.y }); | ||
1571 | } | ||
1572 | if (d->flags & overflowScrollable_WidgetFlag) { | ||
1573 | iWidgetScrollInfo info; | ||
1574 | scrollInfo_Widget(d, &info); | ||
1575 | if (info.thumbHeight > 0) { | ||
1576 | iPaint p; | ||
1577 | init_Paint(&p); | ||
1578 | const int scrollWidth = gap_UI / 2; | ||
1579 | iRect bounds = bounds_Widget(d); | ||
1580 | bounds.pos.x = right_Rect(bounds) - scrollWidth * 3; | ||
1581 | bounds.size.x = scrollWidth; | ||
1582 | bounds.pos.y = info.thumbY; | ||
1583 | bounds.size.y = info.thumbHeight; | ||
1584 | fillRect_Paint(&p, bounds, tmQuote_ColorId); | ||
1585 | } | ||
1586 | } | ||
1271 | } | 1587 | } |
1272 | 1588 | ||
1273 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { | 1589 | iAny *addChild_Widget(iWidget *d, iAnyObject *child) { |
@@ -1288,6 +1604,12 @@ iAny *addChildPosFlags_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos | |||
1288 | d->children = new_ObjectList(); | 1604 | d->children = new_ObjectList(); |
1289 | } | 1605 | } |
1290 | if (addPos == back_WidgetAddPos) { | 1606 | if (addPos == back_WidgetAddPos) { |
1607 | /* Remove a redundant border flags. */ | ||
1608 | if (!isEmpty_ObjectList(d->children) && | ||
1609 | as_Widget(back_ObjectList(d->children))->flags & borderBottom_WidgetFlag && | ||
1610 | widget->flags & borderTop_WidgetFlag) { | ||
1611 | widget->flags &= ~borderTop_WidgetFlag; | ||
1612 | } | ||
1291 | pushBack_ObjectList(d->children, widget); /* ref */ | 1613 | pushBack_ObjectList(d->children, widget); /* ref */ |
1292 | } | 1614 | } |
1293 | else { | 1615 | else { |
@@ -1402,6 +1724,7 @@ iAny *hitChild_Widget(const iWidget *d, iInt2 coord) { | |||
1402 | } | 1724 | } |
1403 | 1725 | ||
1404 | iAny *findChild_Widget(const iWidget *d, const char *id) { | 1726 | iAny *findChild_Widget(const iWidget *d, const char *id) { |
1727 | if (!d) return NULL; | ||
1405 | if (cmp_String(id_Widget(d), id) == 0) { | 1728 | if (cmp_String(id_Widget(d), id) == 0) { |
1406 | return iConstCast(iAny *, d); | 1729 | return iConstCast(iAny *, d); |
1407 | } | 1730 | } |
@@ -1506,7 +1829,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch | |||
1506 | if (equal_Command(cmd, checkCommand)) { | 1829 | if (equal_Command(cmd, checkCommand)) { |
1507 | const iWidget *src = pointer_Command(cmd); | 1830 | const iWidget *src = pointer_Command(cmd); |
1508 | iAssert(!src || strstr(cmd, " ptr:")); | 1831 | iAssert(!src || strstr(cmd, " ptr:")); |
1509 | return src == widget || hasParent_Widget(src, widget); | 1832 | if (src == widget || hasParent_Widget(src, widget)) { |
1833 | return iTrue; | ||
1834 | } | ||
1835 | // if (src && type_Window(window_Widget(src)) == popup_WindowType) { | ||
1836 | // /* Special case: command was emitted from a popup widget. The popup root widget actually | ||
1837 | // belongs to someone else. */ | ||
1838 | // iWidget *realParent = userData_Object(src->root->widget); | ||
1839 | // iAssert(realParent); | ||
1840 | // iAssert(isInstance_Object(realParent, &Class_Widget)); | ||
1841 | // return realParent == widget || hasParent_Widget(realParent, widget); | ||
1842 | // } | ||
1510 | } | 1843 | } |
1511 | return iFalse; | 1844 | return iFalse; |
1512 | } | 1845 | } |
@@ -1557,7 +1890,9 @@ iWidget *focus_Widget(void) { | |||
1557 | } | 1890 | } |
1558 | 1891 | ||
1559 | void setHover_Widget(iWidget *d) { | 1892 | void setHover_Widget(iWidget *d) { |
1560 | get_Window()->hover = d; | 1893 | iWindow *win = get_Window(); |
1894 | iAssert(win); | ||
1895 | win->hover = d; | ||
1561 | } | 1896 | } |
1562 | 1897 | ||
1563 | iWidget *hover_Widget(void) { | 1898 | iWidget *hover_Widget(void) { |
@@ -1646,6 +1981,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { | |||
1646 | } | 1981 | } |
1647 | if (!isGlobal) { | 1982 | if (!isGlobal) { |
1648 | iAssert(isInstance_Object(d, &Class_Widget)); | 1983 | iAssert(isInstance_Object(d, &Class_Widget)); |
1984 | if (type_Window(window_Widget(d)) == popup_WindowType) { | ||
1985 | postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d); | ||
1986 | d = userData_Object(root_Widget(d)); | ||
1987 | } | ||
1649 | appendFormat_String(&str, " ptr:%p", d); | 1988 | appendFormat_String(&str, " ptr:%p", d); |
1650 | } | 1989 | } |
1651 | postCommandString_Root(((const iWidget *) d)->root, &str); | 1990 | postCommandString_Root(((const iWidget *) d)->root, &str); |
@@ -1653,11 +1992,20 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) { | |||
1653 | } | 1992 | } |
1654 | 1993 | ||
1655 | void refresh_Widget(const iAnyObject *d) { | 1994 | void refresh_Widget(const iAnyObject *d) { |
1995 | if (!d) return; | ||
1656 | /* TODO: Could be widget specific, if parts of the tree are cached. */ | 1996 | /* TODO: Could be widget specific, if parts of the tree are cached. */ |
1657 | /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general | 1997 | /* TODO: The visbuffer in DocumentWidget and ListWidget could be moved to be a general |
1658 | purpose feature of Widget. */ | 1998 | purpose feature of Widget. */ |
1659 | iAssert(isInstance_Object(d, &Class_Widget)); | 1999 | iAssert(isInstance_Object(d, &Class_Widget)); |
1660 | iUnused(d); | 2000 | /* Mark draw buffers invalid. */ |
2001 | for (const iWidget *w = d; w; w = w->parent) { | ||
2002 | if (w->drawBuf) { | ||
2003 | // if (w->drawBuf->isValid) { | ||
2004 | // printf("[%p] drawbuffer invalidated by %p\n", w, d); fflush(stdout); | ||
2005 | // } | ||
2006 | w->drawBuf->isValid = iFalse; | ||
2007 | } | ||
2008 | } | ||
1661 | postRefresh_App(); | 2009 | postRefresh_App(); |
1662 | } | 2010 | } |
1663 | 2011 | ||
diff --git a/src/ui/widget.h b/src/ui/widget.h index 1a944c0a..9243c00a 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -34,7 +34,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
34 | #include <the_Foundation/string.h> | 34 | #include <the_Foundation/string.h> |
35 | #include <SDL_events.h> | 35 | #include <SDL_events.h> |
36 | 36 | ||
37 | iDeclareType(Root) /* each widget is associated with a Root */ | 37 | iDeclareType(Root) /* each widget is associated with a Root */ |
38 | iDeclareType(Window) /* each Root is inside a Window */ | ||
38 | 39 | ||
39 | #define iDeclareWidgetClass(className) \ | 40 | #define iDeclareWidgetClass(className) \ |
40 | iDeclareType(className); \ | 41 | iDeclareType(className); \ |
@@ -120,6 +121,7 @@ enum iWidgetFlag { | |||
120 | #define destroyPending_WidgetFlag iBit64(61) | 121 | #define destroyPending_WidgetFlag iBit64(61) |
121 | #define leftEdgeDraggable_WidgetFlag iBit64(62) | 122 | #define leftEdgeDraggable_WidgetFlag iBit64(62) |
122 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ | 123 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ |
124 | #define nativeMenu_WidgetFlag iBit64(64) | ||
123 | 125 | ||
124 | enum iWidgetAddPos { | 126 | enum iWidgetAddPos { |
125 | back_WidgetAddPos, | 127 | back_WidgetAddPos, |
@@ -131,6 +133,8 @@ enum iWidgetFocusDir { | |||
131 | backward_WidgetFocusDir, | 133 | backward_WidgetFocusDir, |
132 | }; | 134 | }; |
133 | 135 | ||
136 | iDeclareType(WidgetDrawBuffer) | ||
137 | |||
134 | struct Impl_Widget { | 138 | struct Impl_Widget { |
135 | iObject object; | 139 | iObject object; |
136 | iString id; | 140 | iString id; |
@@ -148,6 +152,7 @@ struct Impl_Widget { | |||
148 | iWidget * parent; | 152 | iWidget * parent; |
149 | iBool (*commandHandler)(iWidget *, const char *); | 153 | iBool (*commandHandler)(iWidget *, const char *); |
150 | iRoot * root; | 154 | iRoot * root; |
155 | iWidgetDrawBuffer *drawBuf; | ||
151 | }; | 156 | }; |
152 | 157 | ||
153 | iDeclareObjectConstruction(Widget) | 158 | iDeclareObjectConstruction(Widget) |
@@ -182,6 +187,7 @@ void releaseChildren_Widget (iWidget *); | |||
182 | - inner: 0,0 is at the top left corner of the widget */ | 187 | - inner: 0,0 is at the top left corner of the widget */ |
183 | 188 | ||
184 | iWidget * root_Widget (const iWidget *); | 189 | iWidget * root_Widget (const iWidget *); |
190 | iWindow * window_Widget (const iAnyObject *); | ||
185 | const iString * id_Widget (const iWidget *); | 191 | const iString * id_Widget (const iWidget *); |
186 | int64_t flags_Widget (const iWidget *); | 192 | int64_t flags_Widget (const iWidget *); |
187 | iRect bounds_Widget (const iWidget *); /* outer bounds */ | 193 | iRect bounds_Widget (const iWidget *); /* outer bounds */ |
@@ -201,8 +207,15 @@ iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetF | |||
201 | iAny * findOverflowScrollable_Widget (iWidget *); | 207 | iAny * findOverflowScrollable_Widget (iWidget *); |
202 | size_t childCount_Widget (const iWidget *); | 208 | size_t childCount_Widget (const iWidget *); |
203 | void draw_Widget (const iWidget *); | 209 | void draw_Widget (const iWidget *); |
210 | void drawLayerEffects_Widget (const iWidget *); | ||
204 | void drawBackground_Widget (const iWidget *); | 211 | void drawBackground_Widget (const iWidget *); |
205 | void drawChildren_Widget (const iWidget *); | 212 | void drawChildren_Widget (const iWidget *); |
213 | void drawRoot_Widget (const iWidget *); /* root only */ | ||
214 | void setDrawBufferEnabled_Widget (iWidget *, iBool enable); | ||
215 | |||
216 | iLocalDef iBool isDrawBufferEnabled_Widget(const iWidget *d) { | ||
217 | return d && d->drawBuf; | ||
218 | } | ||
206 | 219 | ||
207 | iLocalDef int width_Widget(const iAnyObject *d) { | 220 | iLocalDef int width_Widget(const iAnyObject *d) { |
208 | if (d) { | 221 | if (d) { |
@@ -276,6 +289,18 @@ void refresh_Widget (const iAnyObject *); | |||
276 | 289 | ||
277 | iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand); | 290 | iBool equalWidget_Command (const char *cmd, const iWidget *widget, const char *checkCommand); |
278 | 291 | ||
292 | iDeclareType(WidgetScrollInfo) | ||
293 | |||
294 | struct Impl_WidgetScrollInfo { | ||
295 | int height; /* widget's height */ | ||
296 | int avail; /* available height */ | ||
297 | float normScroll; | ||
298 | int thumbY; /* window coords */ | ||
299 | int thumbHeight; | ||
300 | }; | ||
301 | |||
302 | void scrollInfo_Widget (const iWidget *, iWidgetScrollInfo *info); | ||
303 | |||
279 | int backgroundFadeColor_Widget (void); | 304 | int backgroundFadeColor_Widget (void); |
280 | 305 | ||
281 | void setFocus_Widget (iWidget *); | 306 | void setFocus_Widget (iWidget *); |
diff --git a/src/ui/window.c b/src/ui/window.c index f8391ed9..066ea102 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
30 | #include "keys.h" | 30 | #include "keys.h" |
31 | #include "labelwidget.h" | 31 | #include "labelwidget.h" |
32 | #include "documentwidget.h" | 32 | #include "documentwidget.h" |
33 | #include "sidebarwidget.h" | ||
33 | #include "paint.h" | 34 | #include "paint.h" |
34 | #include "root.h" | 35 | #include "root.h" |
35 | #include "touch.h" | 36 | #include "touch.h" |
@@ -57,7 +58,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
57 | #include "stb_image.h" | 58 | #include "stb_image.h" |
58 | #include "stb_image_resize.h" | 59 | #include "stb_image_resize.h" |
59 | 60 | ||
60 | static iWindow *theWindow_ = NULL; | 61 | static iWindow * theWindow_; |
62 | static iMainWindow *theMainWindow_; | ||
61 | 63 | ||
62 | #if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) | 64 | #if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) |
63 | static float initialUiScale_ = 1.0f; | 65 | static float initialUiScale_ = 1.0f; |
@@ -67,7 +69,10 @@ static float initialUiScale_ = 1.1f; | |||
67 | 69 | ||
68 | static iBool isOpenGLRenderer_; | 70 | static iBool isOpenGLRenderer_; |
69 | 71 | ||
70 | iDefineTypeConstructionArgs(Window, (iRect rect), rect) | 72 | iDefineTypeConstructionArgs(Window, |
73 | (enum iWindowType type, iRect rect, uint32_t flags), | ||
74 | type, rect, flags) | ||
75 | iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) | ||
71 | 76 | ||
72 | /* TODO: Define menus per platform. */ | 77 | /* TODO: Define menus per platform. */ |
73 | 78 | ||
@@ -116,6 +121,7 @@ static const iMenuItem viewMenuItems_[] = { | |||
116 | static iMenuItem bookmarksMenuItems_[] = { | 121 | static iMenuItem bookmarksMenuItems_[] = { |
117 | { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 122 | { "${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
118 | { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | 123 | { "${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, |
124 | { "${menu.newfolder}", 0, 0, "bookmarks.addfolder" }, | ||
119 | { "---", 0, 0, NULL }, | 125 | { "---", 0, 0, NULL }, |
120 | { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" }, | 126 | { "${menu.import.links}", 0, 0, "bookmark.links confirm:1" }, |
121 | { "---", 0, 0, NULL }, | 127 | { "---", 0, 0, NULL }, |
@@ -124,6 +130,8 @@ static iMenuItem bookmarksMenuItems_[] = { | |||
124 | { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" }, | 130 | { "${macos.menu.bookmarks.bytime}", 0, 0, "open url:about:bookmarks?created" }, |
125 | { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" }, | 131 | { "${menu.feeds.entrylist}", 0, 0, "open url:about:feeds" }, |
126 | { "---", 0, 0, NULL }, | 132 | { "---", 0, 0, NULL }, |
133 | { "${menu.sort.alpha}", 0, 0, "bookmarks.sort" }, | ||
134 | { "---", 0, 0, NULL }, | ||
127 | { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" }, | 135 | { "${menu.bookmarks.refresh}", 0, 0, "bookmarks.reload.remote" }, |
128 | { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, | 136 | { "${menu.feeds.refresh}", SDLK_r, KMOD_PRIMARY | KMOD_SHIFT, "feeds.refresh" }, |
129 | }; | 137 | }; |
@@ -169,17 +177,17 @@ int numRoots_Window(const iWindow *d) { | |||
169 | return num; | 177 | return num; |
170 | } | 178 | } |
171 | 179 | ||
172 | static void windowSizeChanged_Window_(iWindow *d) { | 180 | static void windowSizeChanged_MainWindow_(iMainWindow *d) { |
173 | const int numRoots = numRoots_Window(d); | 181 | const int numRoots = numRoots_Window(as_Window(d)); |
174 | const iInt2 rootSize = d->size; | 182 | const iInt2 rootSize = d->base.size; |
175 | const int weights[2] = { | 183 | const int weights[2] = { |
176 | d->roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0, | 184 | d->base.roots[0] ? (d->splitMode & twoToOne_WindowSplit ? 2 : 1) : 0, |
177 | d->roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0, | 185 | d->base.roots[1] ? (d->splitMode & oneToTwo_WindowSplit ? 2 : 1) : 0, |
178 | }; | 186 | }; |
179 | const int totalWeight = weights[0] + weights[1]; | 187 | const int totalWeight = weights[0] + weights[1]; |
180 | int w = 0; | 188 | int w = 0; |
181 | iForIndices(i, d->roots) { | 189 | iForIndices(i, d->base.roots) { |
182 | iRoot *root = d->roots[i]; | 190 | iRoot *root = d->base.roots[i]; |
183 | if (root) { | 191 | if (root) { |
184 | iRect *rect = &root->widget->rect; | 192 | iRect *rect = &root->widget->rect; |
185 | /* Horizontal split frame. */ | 193 | /* Horizontal split frame. */ |
@@ -199,26 +207,27 @@ static void windowSizeChanged_Window_(iWindow *d) { | |||
199 | } | 207 | } |
200 | } | 208 | } |
201 | 209 | ||
202 | static void setupUserInterface_Window(iWindow *d) { | 210 | static void setupUserInterface_MainWindow(iMainWindow *d) { |
203 | #if defined (iHaveNativeMenus) | 211 | #if defined (iHaveNativeMenus) |
204 | insertMacMenus_(); | 212 | insertMacMenus_(); |
205 | #endif | 213 | #endif |
206 | /* One root is created by default. */ | 214 | /* One root is created by default. */ |
207 | d->roots[0] = new_Root(); | 215 | d->base.roots[0] = new_Root(); |
208 | setCurrent_Root(d->roots[0]); | 216 | d->base.roots[0]->window = as_Window(d); |
209 | createUserInterface_Root(d->roots[0]); | 217 | setCurrent_Root(d->base.roots[0]); |
218 | createUserInterface_Root(d->base.roots[0]); | ||
210 | setCurrent_Root(NULL); | 219 | setCurrent_Root(NULL); |
211 | /* One of the roots always has keyboard input focus. */ | 220 | /* One of the roots always has keyboard input focus. */ |
212 | d->keyRoot = d->roots[0]; | 221 | d->base.keyRoot = d->base.roots[0]; |
213 | } | 222 | } |
214 | 223 | ||
215 | static void updateSize_Window_(iWindow *d, iBool notifyAlways) { | 224 | static void updateSize_MainWindow_(iMainWindow *d, iBool notifyAlways) { |
216 | iInt2 *size = &d->size; | 225 | iInt2 *size = &d->base.size; |
217 | const iInt2 oldSize = *size; | 226 | const iInt2 oldSize = *size; |
218 | SDL_GetRendererOutputSize(d->render, &size->x, &size->y); | 227 | SDL_GetRendererOutputSize(d->base.render, &size->x, &size->y); |
219 | size->y -= d->keyboardHeight; | 228 | size->y -= d->keyboardHeight; |
220 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { | 229 | if (notifyAlways || !isEqual_I2(oldSize, *size)) { |
221 | windowSizeChanged_Window_(d); | 230 | windowSizeChanged_MainWindow_(d); |
222 | if (!isEqual_I2(*size, d->place.lastNotifiedSize)) { | 231 | if (!isEqual_I2(*size, d->place.lastNotifiedSize)) { |
223 | const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); | 232 | const iBool isHoriz = (d->place.lastNotifiedSize.x != size->x); |
224 | const iBool isVert = (d->place.lastNotifiedSize.y != size->y); | 233 | const iBool isVert = (d->place.lastNotifiedSize.y != size->y); |
@@ -234,8 +243,8 @@ static void updateSize_Window_(iWindow *d, iBool notifyAlways) { | |||
234 | } | 243 | } |
235 | } | 244 | } |
236 | 245 | ||
237 | void drawWhileResizing_Window(iWindow *d, int w, int h) { | 246 | void drawWhileResizing_MainWindow(iMainWindow *d, int w, int h) { |
238 | draw_Window(d); | 247 | draw_MainWindow(d); |
239 | } | 248 | } |
240 | 249 | ||
241 | static float pixelRatio_Window_(const iWindow *d) { | 250 | static float pixelRatio_Window_(const iWindow *d) { |
@@ -308,7 +317,7 @@ static iRoot *rootAt_Window_(const iWindow *d, iInt2 coord) { | |||
308 | } | 317 | } |
309 | 318 | ||
310 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 319 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
311 | static SDL_HitTestResult hitTest_Window_(SDL_Window *win, const SDL_Point *pos, void *data) { | 320 | static SDL_HitTestResult hitTest_MainWindow_(SDL_Window *win, const SDL_Point *pos, void *data) { |
312 | iWindow *d = data; | 321 | iWindow *d = data; |
313 | iAssert(d->win == win); | 322 | iAssert(d->win == win); |
314 | if (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_FULLSCREEN_DESKTOP)) { | 323 | if (SDL_GetWindowFlags(d->win) & (SDL_WINDOW_MOUSE_CAPTURE | SDL_WINDOW_FULLSCREEN_DESKTOP)) { |
@@ -361,19 +370,22 @@ SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos) { | |||
361 | #endif | 370 | #endif |
362 | 371 | ||
363 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { | 372 | iBool create_Window_(iWindow *d, iRect rect, uint32_t flags) { |
364 | flags |= SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; | 373 | flags |= SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN; |
374 | if (d->type == main_WindowType) { | ||
375 | flags |= SDL_WINDOW_RESIZABLE; | ||
365 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 376 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
366 | if (prefs_App()->customFrame) { | 377 | if (prefs_App()->customFrame) { |
367 | /* We are drawing a custom frame so hide the default one. */ | 378 | /* We are drawing a custom frame so hide the default one. */ |
368 | flags |= SDL_WINDOW_BORDERLESS; | 379 | flags |= SDL_WINDOW_BORDERLESS; |
369 | } | 380 | } |
370 | #endif | 381 | #endif |
382 | } | ||
371 | if (SDL_CreateWindowAndRenderer( | 383 | if (SDL_CreateWindowAndRenderer( |
372 | width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { | 384 | width_Rect(rect), height_Rect(rect), flags, &d->win, &d->render)) { |
373 | return iFalse; | 385 | return iFalse; |
374 | } | 386 | } |
375 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 387 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
376 | if (prefs_App()->customFrame) { | 388 | if (type_Window(d) == main_WindowType && prefs_App()->customFrame) { |
377 | /* Register a handler for window hit testing (drag, resize). */ | 389 | /* Register a handler for window hit testing (drag, resize). */ |
378 | SDL_SetWindowHitTest(d->win, hitTest_Window_, d); | 390 | SDL_SetWindowHitTest(d->win, hitTest_Window_, d); |
379 | SDL_SetWindowResizable(d->win, SDL_TRUE); | 391 | SDL_SetWindowResizable(d->win, SDL_TRUE); |
@@ -397,40 +409,27 @@ static SDL_Surface *loadImage_(const iBlock *data, int resized) { | |||
397 | pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32); | 409 | pixels, w, h, 8 * num, w * num, SDL_PIXELFORMAT_RGBA32); |
398 | } | 410 | } |
399 | 411 | ||
400 | void init_Window(iWindow *d, iRect rect) { | 412 | void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags) { |
401 | theWindow_ = d; | 413 | d->type = type; |
402 | d->win = NULL; | 414 | d->win = NULL; |
403 | d->size = zero_I2(); /* will be updated below */ | 415 | d->size = zero_I2(); /* will be updated below */ |
404 | iZap(d->roots); | 416 | d->hover = NULL; |
405 | d->splitMode = d->pendingSplitMode = 0; | 417 | d->lastHover = NULL; |
406 | d->pendingSplitUrl = new_String(); | 418 | d->mouseGrab = NULL; |
407 | d->hover = NULL; | 419 | d->focus = NULL; |
408 | d->mouseGrab = NULL; | ||
409 | d->focus = NULL; | ||
410 | iZap(d->cursors); | ||
411 | d->place.initialPos = rect.pos; | ||
412 | d->place.normalRect = rect; | ||
413 | d->place.lastNotifiedSize = zero_I2(); | ||
414 | d->place.snap = 0; | ||
415 | d->pendingCursor = NULL; | 420 | d->pendingCursor = NULL; |
416 | d->isDrawFrozen = iTrue; | 421 | d->isExposed = iFalse; |
417 | d->isExposed = iFalse; | 422 | d->isMinimized = iFalse; |
418 | d->isMinimized = iFalse; | ||
419 | d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ | 423 | d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ |
420 | d->isMouseInside = iTrue; | 424 | d->isMouseInside = iTrue; |
421 | d->ignoreClick = iFalse; | 425 | d->ignoreClick = iFalse; |
422 | d->focusGainedAt = 0; | 426 | d->focusGainedAt = 0; |
423 | d->keyboardHeight = 0; | 427 | d->presentTime = 0.0; |
424 | init_Anim(&d->rootOffset, 0.0f); | 428 | d->frameTime = SDL_GetTicks(); |
425 | uint32_t flags = 0; | 429 | d->keyRoot = NULL; |
426 | #if defined (iPlatformAppleDesktop) | 430 | d->borderShadow = NULL; |
427 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); | 431 | iZap(d->roots); |
428 | #elif defined (iPlatformAppleMobile) | 432 | iZap(d->cursors); |
429 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); | ||
430 | #else | ||
431 | flags |= SDL_WINDOW_OPENGL; | ||
432 | #endif | ||
433 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); | ||
434 | /* First try SDL's default renderer that should be the best option. */ | 433 | /* First try SDL's default renderer that should be the best option. */ |
435 | if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) { | 434 | if (forceSoftwareRender_App() || !create_Window_(d, rect, flags)) { |
436 | /* No luck, maybe software only? This should always work as long as there is a display. */ | 435 | /* No luck, maybe software only? This should always work as long as there is a display. */ |
@@ -443,36 +442,95 @@ void init_Window(iWindow *d, iRect rect) { | |||
443 | if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) { | 442 | if (left_Rect(rect) >= 0 || top_Rect(rect) >= 0) { |
444 | SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); | 443 | SDL_SetWindowPosition(d->win, left_Rect(rect), top_Rect(rect)); |
445 | } | 444 | } |
445 | SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y); | ||
446 | drawBlank_Window_(d); | ||
447 | d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */ | ||
448 | d->displayScale = displayScale_Window_(d); | ||
449 | d->uiScale = initialUiScale_; | ||
450 | /* TODO: Ratios, scales, and metrics must be window-specific, not global. */ | ||
451 | setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); | ||
452 | d->text = new_Text(d->render); | ||
453 | } | ||
454 | |||
455 | static void deinitRoots_Window_(iWindow *d) { | ||
456 | iRecycle(); | ||
457 | iForIndices(i, d->roots) { | ||
458 | if (d->roots[i]) { | ||
459 | setCurrent_Root(d->roots[i]); | ||
460 | delete_Root(d->roots[i]); | ||
461 | d->roots[i] = NULL; | ||
462 | } | ||
463 | } | ||
464 | setCurrent_Root(NULL); | ||
465 | } | ||
466 | |||
467 | void deinit_Window(iWindow *d) { | ||
468 | if (d->type == popup_WindowType) { | ||
469 | removePopup_App(d); | ||
470 | } | ||
471 | deinitRoots_Window_(d); | ||
472 | delete_Text(d->text); | ||
473 | SDL_DestroyRenderer(d->render); | ||
474 | SDL_DestroyWindow(d->win); | ||
475 | iForIndices(i, d->cursors) { | ||
476 | if (d->cursors[i]) { | ||
477 | SDL_FreeCursor(d->cursors[i]); | ||
478 | } | ||
479 | } | ||
480 | } | ||
481 | |||
482 | void init_MainWindow(iMainWindow *d, iRect rect) { | ||
483 | theWindow_ = &d->base; | ||
484 | theMainWindow_ = d; | ||
485 | uint32_t flags = 0; | ||
486 | #if defined (iPlatformAppleDesktop) | ||
487 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); | ||
488 | #elif defined (iPlatformAppleMobile) | ||
489 | SDL_SetHint(SDL_HINT_RENDER_DRIVER, "metal"); | ||
490 | #else | ||
491 | flags |= SDL_WINDOW_OPENGL; | ||
492 | #endif | ||
493 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); | ||
494 | init_Window(&d->base, main_WindowType, rect, flags); | ||
495 | d->isDrawFrozen = iTrue; | ||
496 | d->splitMode = 0; | ||
497 | d->pendingSplitMode = 0; | ||
498 | d->pendingSplitUrl = new_String(); | ||
499 | d->place.initialPos = rect.pos; | ||
500 | d->place.normalRect = rect; | ||
501 | d->place.lastNotifiedSize = zero_I2(); | ||
502 | d->place.snap = 0; | ||
503 | d->keyboardHeight = 0; | ||
504 | #if defined(iPlatformMobile) | ||
505 | const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ | ||
506 | #else | ||
446 | const iInt2 minSize = init_I2(425, 325); | 507 | const iInt2 minSize = init_I2(425, 325); |
447 | SDL_SetWindowMinimumSize(d->win, minSize.x, minSize.y); | 508 | #endif |
448 | SDL_SetWindowTitle(d->win, "Lagrange"); | 509 | SDL_SetWindowMinimumSize(d->base.win, minSize.x, minSize.y); |
510 | SDL_SetWindowTitle(d->base.win, "Lagrange"); | ||
449 | /* Some info. */ { | 511 | /* Some info. */ { |
450 | SDL_RendererInfo info; | 512 | SDL_RendererInfo info; |
451 | SDL_GetRendererInfo(d->render, &info); | 513 | SDL_GetRendererInfo(d->base.render, &info); |
452 | isOpenGLRenderer_ = !iCmpStr(info.name, "opengl"); | 514 | isOpenGLRenderer_ = !iCmpStr(info.name, "opengl"); |
453 | printf("[window] renderer: %s%s\n", info.name, | 515 | printf("[window] renderer: %s%s\n", |
516 | info.name, | ||
454 | info.flags & SDL_RENDERER_ACCELERATED ? " (accelerated)" : ""); | 517 | info.flags & SDL_RENDERER_ACCELERATED ? " (accelerated)" : ""); |
455 | #if !defined (NDEBUG) | 518 | #if !defined(NDEBUG) |
456 | printf("[window] max texture size: %d x %d\n", | 519 | printf("[window] max texture size: %d x %d\n", |
457 | info.max_texture_width, | 520 | info.max_texture_width, |
458 | info.max_texture_height); | 521 | info.max_texture_height); |
459 | for (size_t i = 0; i < info.num_texture_formats; ++i) { | 522 | for (size_t i = 0; i < info.num_texture_formats; ++i) { |
460 | printf("[window] supported texture format: %s\n", SDL_GetPixelFormatName( | 523 | printf("[window] supported texture format: %s\n", |
461 | info.texture_formats[i])); | 524 | SDL_GetPixelFormatName(info.texture_formats[i])); |
462 | } | 525 | } |
463 | #endif | 526 | #endif |
464 | } | 527 | } |
465 | drawBlank_Window_(d); | 528 | #if defined(iPlatformMsys) |
466 | d->pixelRatio = pixelRatio_Window_(d); /* point/pixel conversion */ | 529 | SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.displayScale, minSize.y * d->base.displayScale); |
467 | d->displayScale = displayScale_Window_(d); | 530 | useExecutableIconResource_SDLWindow(d->base.win); |
468 | d->uiScale = initialUiScale_; | ||
469 | setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); | ||
470 | #if defined (iPlatformMsys) | ||
471 | SDL_SetWindowMinimumSize(d->win, minSize.x * d->displayScale, minSize.y * d->displayScale); | ||
472 | useExecutableIconResource_SDLWindow(d->win); | ||
473 | #endif | 531 | #endif |
474 | #if defined (iPlatformLinux) | 532 | #if defined (iPlatformLinux) |
475 | SDL_SetWindowMinimumSize(d->win, minSize.x * d->pixelRatio, minSize.y * d->pixelRatio); | 533 | SDL_SetWindowMinimumSize(d->win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio); |
476 | /* Load the window icon. */ { | 534 | /* Load the window icon. */ { |
477 | SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0); | 535 | SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0); |
478 | SDL_SetWindowIcon(d->win, surf); | 536 | SDL_SetWindowIcon(d->win, surf); |
@@ -481,20 +539,16 @@ void init_Window(iWindow *d, iRect rect) { | |||
481 | } | 539 | } |
482 | #endif | 540 | #endif |
483 | #if defined (iPlatformAppleMobile) | 541 | #if defined (iPlatformAppleMobile) |
484 | setupWindow_iOS(d); | 542 | setupWindow_iOS(as_Window(d)); |
485 | #endif | 543 | #endif |
486 | d->presentTime = 0.0; | 544 | setCurrent_Text(d->base.text); |
487 | d->frameTime = SDL_GetTicks(); | 545 | SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); |
488 | d->loadAnimTimer = 0; | 546 | setupUserInterface_MainWindow(d); |
489 | init_Text(d->render); | ||
490 | SDL_GetRendererOutputSize(d->render, &d->size.x, &d->size.y); | ||
491 | setupUserInterface_Window(d); | ||
492 | postCommand_App("~bindings.changed"); /* update from bindings */ | 547 | postCommand_App("~bindings.changed"); /* update from bindings */ |
493 | //updateSize_Window_(d, iFalse); | ||
494 | /* Load the border shadow texture. */ { | 548 | /* Load the border shadow texture. */ { |
495 | SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); | 549 | SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); |
496 | d->borderShadow = SDL_CreateTextureFromSurface(d->render, surf); | 550 | d->base.borderShadow = SDL_CreateTextureFromSurface(d->base.render, surf); |
497 | SDL_SetTextureBlendMode(d->borderShadow, SDL_BLENDMODE_BLEND); | 551 | SDL_SetTextureBlendMode(d->base.borderShadow, SDL_BLENDMODE_BLEND); |
498 | free(surf->pixels); | 552 | free(surf->pixels); |
499 | SDL_FreeSurface(surf); | 553 | SDL_FreeSurface(surf); |
500 | } | 554 | } |
@@ -504,36 +558,26 @@ void init_Window(iWindow *d, iRect rect) { | |||
504 | if (prefs_App()->customFrame) { | 558 | if (prefs_App()->customFrame) { |
505 | SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root()); | 559 | SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root()); |
506 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); | 560 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); |
507 | d->appIcon = SDL_CreateTextureFromSurface(d->render, surf); | 561 | d->appIcon = SDL_CreateTextureFromSurface(d->base.render, surf); |
508 | free(surf->pixels); | 562 | free(surf->pixels); |
509 | SDL_FreeSurface(surf); | 563 | SDL_FreeSurface(surf); |
510 | /* We need to observe non-client-area events. */ | 564 | /* We need to observe non-client-area events. */ |
511 | SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE); | 565 | SDL_EventState(SDL_SYSWMEVENT, SDL_TRUE); |
512 | } | 566 | } |
513 | #endif | 567 | #endif |
568 | SDL_HideWindow(d->base.win); | ||
514 | } | 569 | } |
515 | 570 | ||
516 | void deinit_Window(iWindow *d) { | 571 | void deinit_MainWindow(iMainWindow *d) { |
517 | iRecycle(); | 572 | deinitRoots_Window_(as_Window(d)); |
518 | iForIndices(i, d->roots) { | 573 | if (theWindow_ == as_Window(d)) { |
519 | if (d->roots[i]) { | ||
520 | setCurrent_Root(d->roots[i]); | ||
521 | deinit_Root(d->roots[i]); | ||
522 | } | ||
523 | } | ||
524 | if (theWindow_ == d) { | ||
525 | theWindow_ = NULL; | 574 | theWindow_ = NULL; |
526 | } | 575 | } |
527 | setCurrent_Root(NULL); | 576 | if (theMainWindow_ == d) { |
528 | delete_String(d->pendingSplitUrl); | 577 | theMainWindow_ = NULL; |
529 | deinit_Text(); | ||
530 | SDL_DestroyRenderer(d->render); | ||
531 | SDL_DestroyWindow(d->win); | ||
532 | iForIndices(i, d->cursors) { | ||
533 | if (d->cursors[i]) { | ||
534 | SDL_FreeCursor(d->cursors[i]); | ||
535 | } | ||
536 | } | 578 | } |
579 | delete_String(d->pendingSplitUrl); | ||
580 | deinit_Window(&d->base); | ||
537 | } | 581 | } |
538 | 582 | ||
539 | SDL_Renderer *renderer_Window(const iWindow *d) { | 583 | SDL_Renderer *renderer_Window(const iWindow *d) { |
@@ -546,8 +590,8 @@ iInt2 maxTextureSize_Window(const iWindow *d) { | |||
546 | return init_I2(info.max_texture_width, info.max_texture_height); | 590 | return init_I2(info.max_texture_width, info.max_texture_height); |
547 | } | 591 | } |
548 | 592 | ||
549 | iBool isFullscreen_Window(const iWindow *d) { | 593 | iBool isFullscreen_MainWindow(const iMainWindow *d) { |
550 | return snap_Window(d) == fullscreen_WindowSnap; | 594 | return snap_MainWindow(d) == fullscreen_WindowSnap; |
551 | } | 595 | } |
552 | 596 | ||
553 | iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { | 597 | iRoot *findRoot_Window(const iWindow *d, const iWidget *widget) { |
@@ -566,36 +610,41 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) { | |||
566 | return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0]; | 610 | return root == d->roots[0] && d->roots[1] ? d->roots[1] : d->roots[0]; |
567 | } | 611 | } |
568 | 612 | ||
569 | static void invalidate_Window_(iWindow *d, iBool forced) { | 613 | static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { |
570 | if (d && (!d->isInvalidated || forced)) { | 614 | if (d && (!d->base.isInvalidated || forced)) { |
571 | d->isInvalidated = iTrue; | 615 | d->base.isInvalidated = iTrue; |
572 | resetFonts_Text(); | 616 | resetFonts_Text(text_Window(d)); |
573 | postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ | 617 | postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ |
574 | } | 618 | } |
575 | } | 619 | } |
576 | 620 | ||
577 | void invalidate_Window(iWindow *d) { | 621 | void invalidate_Window(iAnyWindow *d) { |
578 | invalidate_Window_(d, iFalse); | 622 | if (type_Window(d) == main_WindowType) { |
623 | invalidate_MainWindow_(as_MainWindow(d), iFalse); | ||
624 | } | ||
625 | else { | ||
626 | iAssert(type_Window(d) == main_WindowType); | ||
627 | } | ||
579 | } | 628 | } |
580 | 629 | ||
581 | static iBool isNormalPlacement_Window_(const iWindow *d) { | 630 | static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) { |
582 | if (d->isDrawFrozen) return iFalse; | 631 | if (d->isDrawFrozen) return iFalse; |
583 | #if defined (iPlatformApple) | 632 | #if defined (iPlatformApple) |
584 | /* Maximized mode is not special on macOS. */ | 633 | /* Maximized mode is not special on macOS. */ |
585 | if (snap_Window(d) == maximized_WindowSnap) { | 634 | if (snap_MainWindow(d) == maximized_WindowSnap) { |
586 | return iTrue; | 635 | return iTrue; |
587 | } | 636 | } |
588 | #endif | 637 | #endif |
589 | if (snap_Window(d)) return iFalse; | 638 | if (snap_MainWindow(d)) return iFalse; |
590 | return !(SDL_GetWindowFlags(d->win) & SDL_WINDOW_MINIMIZED); | 639 | return !(SDL_GetWindowFlags(d->base.win) & SDL_WINDOW_MINIMIZED); |
591 | } | 640 | } |
592 | 641 | ||
593 | static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { | 642 | static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) { |
594 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 643 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
595 | if (!prefs_App()->customFrame) { | 644 | if (!prefs_App()->customFrame) { |
596 | return iFalse; | 645 | return iFalse; |
597 | } | 646 | } |
598 | const int snap = snap_Window(d); | 647 | const int snap = snap_MainWindow(d); |
599 | if (snap == yMaximized_WindowSnap || snap == left_WindowSnap || snap == right_WindowSnap) { | 648 | if (snap == yMaximized_WindowSnap || snap == left_WindowSnap || snap == right_WindowSnap) { |
600 | if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT || | 649 | if (!newPos || (d->place.lastHit == SDL_HITTEST_RESIZE_LEFT || |
601 | d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) { | 650 | d->place.lastHit == SDL_HITTEST_RESIZE_RIGHT)) { |
@@ -603,21 +652,21 @@ static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { | |||
603 | } | 652 | } |
604 | if (newPos) { | 653 | if (newPos) { |
605 | SDL_Rect usable; | 654 | SDL_Rect usable; |
606 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); | 655 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); |
607 | /* Snap to top. */ | 656 | /* Snap to top. */ |
608 | if (snap == yMaximized_WindowSnap && | 657 | if (snap == yMaximized_WindowSnap && |
609 | iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) { | 658 | iAbs(newPos->y - usable.y) < lineHeight_Text(uiContent_FontId) * 2) { |
610 | setSnap_Window(d, redo_WindowSnap | yMaximized_WindowSnap); | 659 | setSnap_MainWindow(d, redo_WindowSnap | yMaximized_WindowSnap); |
611 | return iFalse; | 660 | return iFalse; |
612 | } | 661 | } |
613 | } | 662 | } |
614 | } | 663 | } |
615 | if (snap && snap != fullscreen_WindowSnap) { | 664 | if (snap && snap != fullscreen_WindowSnap) { |
616 | if (snap_Window(d) == yMaximized_WindowSnap && newPos) { | 665 | if (snap_MainWindow(d) == yMaximized_WindowSnap && newPos) { |
617 | d->place.normalRect.pos = *newPos; | 666 | d->place.normalRect.pos = *newPos; |
618 | } | 667 | } |
619 | //printf("unsnap\n"); fflush(stdout); | 668 | //printf("unsnap\n"); fflush(stdout); |
620 | setSnap_Window(d, none_WindowSnap); | 669 | setSnap_MainWindow(d, none_WindowSnap); |
621 | return iTrue; | 670 | return iTrue; |
622 | } | 671 | } |
623 | #endif | 672 | #endif |
@@ -627,7 +676,7 @@ static iBool unsnap_Window_(iWindow *d, const iInt2 *newPos) { | |||
627 | static void notifyMetricsChange_Window_(const iWindow *d) { | 676 | static void notifyMetricsChange_Window_(const iWindow *d) { |
628 | /* Dynamic UI metrics change. Widgets need to update themselves. */ | 677 | /* Dynamic UI metrics change. Widgets need to update themselves. */ |
629 | setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); | 678 | setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); |
630 | resetFonts_Text(); | 679 | resetFonts_Text(d->text); |
631 | postCommand_App("metrics.changed"); | 680 | postCommand_App("metrics.changed"); |
632 | } | 681 | } |
633 | 682 | ||
@@ -649,146 +698,178 @@ static void checkPixelRatioChange_Window_(iWindow *d) { | |||
649 | } | 698 | } |
650 | 699 | ||
651 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | 700 | static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { |
701 | if (ev->windowID != SDL_GetWindowID(d->win)) { | ||
702 | return iFalse; | ||
703 | } | ||
652 | switch (ev->event) { | 704 | switch (ev->event) { |
653 | #if defined (iPlatformDesktop) | ||
654 | case SDL_WINDOWEVENT_EXPOSED: | 705 | case SDL_WINDOWEVENT_EXPOSED: |
655 | if (!d->isExposed) { | 706 | d->isExposed = iTrue; |
656 | drawBlank_Window_(d); /* avoid showing system-provided contents */ | 707 | postRefresh_App(); |
657 | d->isExposed = iTrue; | 708 | return iTrue; |
658 | } | 709 | case SDL_WINDOWEVENT_RESTORED: |
710 | case SDL_WINDOWEVENT_SHOWN: | ||
711 | postRefresh_App(); | ||
712 | return iTrue; | ||
713 | case SDL_WINDOWEVENT_FOCUS_LOST: | ||
714 | /* Popup windows are currently only used for menus. */ | ||
715 | closeMenu_Widget(d->roots[0]->widget); | ||
716 | return iTrue; | ||
717 | case SDL_WINDOWEVENT_LEAVE: | ||
718 | unhover_Widget(); | ||
719 | d->isMouseInside = iFalse; | ||
720 | //postCommand_App("window.mouse.exited"); | ||
721 | // SDL_SetWindowInputFocus(mainWindow_App()->base.win); | ||
722 | printf("mouse leaves popup\n"); fflush(stdout); | ||
723 | //SDL_RaiseWindow(mainWindow_App()->base.win); | ||
724 | postRefresh_App(); | ||
725 | return iTrue; | ||
726 | case SDL_WINDOWEVENT_ENTER: | ||
727 | d->isMouseInside = iTrue; | ||
728 | //postCommand_App("window.mouse.entered"); | ||
729 | printf("mouse enters popup\n"); fflush(stdout); | ||
730 | return iTrue; | ||
731 | } | ||
732 | return iFalse; | ||
733 | } | ||
734 | |||
735 | static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { | ||
736 | switch (ev->event) { | ||
737 | #if defined(iPlatformDesktop) | ||
738 | case SDL_WINDOWEVENT_EXPOSED: | ||
739 | d->base.isExposed = iTrue; | ||
659 | /* Since we are manually controlling when to redraw the window, we are responsible | 740 | /* Since we are manually controlling when to redraw the window, we are responsible |
660 | for ensuring that window contents get redrawn after expose events. Under certain | 741 | for ensuring that window contents get redrawn after expose events. Under certain |
661 | circumstances (e.g., under openbox), not doing this would mean that the window | 742 | circumstances (e.g., under openbox), not doing this would mean that the window |
662 | is missing contents until other events trigger a refresh. */ | 743 | is missing contents until other events trigger a refresh. */ |
663 | postRefresh_App(); | 744 | postRefresh_App(); |
664 | #if defined (LAGRANGE_ENABLE_WINDOWPOS_FIX) | 745 | #if defined(LAGRANGE_ENABLE_WINDOWPOS_FIX) |
665 | if (d->place.initialPos.x >= 0) { | 746 | if (d->place.initialPos.x >= 0) { |
666 | int bx, by; | 747 | int bx, by; |
667 | SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); | 748 | SDL_GetWindowBordersSize(d->win, &by, &bx, NULL, NULL); |
668 | SDL_SetWindowPosition(d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); | 749 | SDL_SetWindowPosition( |
750 | d->win, d->place.initialPos.x + bx, d->place.initialPos.y + by); | ||
669 | d->place.initialPos = init1_I2(-1); | 751 | d->place.initialPos = init1_I2(-1); |
670 | } | 752 | } |
671 | #endif | 753 | #endif |
672 | return iFalse; | 754 | return iFalse; |
673 | case SDL_WINDOWEVENT_MOVED: { | 755 | case SDL_WINDOWEVENT_MOVED: { |
674 | if (d->isMinimized) { | 756 | if (d->base.isMinimized) { |
675 | return iFalse; | 757 | return iFalse; |
676 | } | 758 | } |
677 | checkPixelRatioChange_Window_(d); | 759 | checkPixelRatioChange_Window_(as_Window(d)); |
678 | const iInt2 newPos = init_I2(ev->data1, ev->data2); | 760 | const iInt2 newPos = init_I2(ev->data1, ev->data2); |
679 | if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ | 761 | if (isEqual_I2(newPos, init1_I2(-32000))) { /* magic! */ |
680 | /* Maybe minimized? Seems like a Windows constant of some kind. */ | 762 | /* Maybe minimized? Seems like a Windows constant of some kind. */ |
681 | d->isMinimized = iTrue; | 763 | d->base.isMinimized = iTrue; |
682 | return iFalse; | 764 | return iFalse; |
683 | } | 765 | } |
684 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 766 | #if defined(LAGRANGE_ENABLE_CUSTOM_FRAME) |
685 | /* Set the snap position depending on where the mouse cursor is. */ | 767 | /* Set the snap position depending on where the mouse cursor is. */ |
686 | if (prefs_App()->customFrame) { | 768 | if (prefs_App()->customFrame) { |
687 | SDL_Rect usable; | 769 | SDL_Rect usable; |
688 | iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */ | 770 | iInt2 mouse = cursor_Win32(); /* SDL is unaware of the current cursor pos */ |
689 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); | 771 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); |
690 | const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20; | 772 | const iBool isTop = iAbs(mouse.y - usable.y) < gap_UI * 20; |
691 | const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20; | 773 | const iBool isBottom = iAbs(usable.y + usable.h - mouse.y) < gap_UI * 20; |
692 | if (iAbs(mouse.x - usable.x) < gap_UI) { | 774 | if (iAbs(mouse.x - usable.x) < gap_UI) { |
693 | setSnap_Window(d, | 775 | setSnap_MainWindow(d, |
694 | redo_WindowSnap | left_WindowSnap | | 776 | redo_WindowSnap | left_WindowSnap | |
695 | (isTop ? topBit_WindowSnap : 0) | | 777 | (isTop ? topBit_WindowSnap : 0) | |
696 | (isBottom ? bottomBit_WindowSnap : 0)); | 778 | (isBottom ? bottomBit_WindowSnap : 0)); |
697 | return iTrue; | 779 | return iTrue; |
698 | } | 780 | } |
699 | if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) { | 781 | if (iAbs(mouse.x - usable.x - usable.w) < gap_UI) { |
700 | setSnap_Window(d, | 782 | setSnap_MainWindow(d, |
701 | redo_WindowSnap | right_WindowSnap | | 783 | redo_WindowSnap | right_WindowSnap | |
702 | (isTop ? topBit_WindowSnap : 0) | | 784 | (isTop ? topBit_WindowSnap : 0) | |
703 | (isBottom ? bottomBit_WindowSnap : 0)); | 785 | (isBottom ? bottomBit_WindowSnap : 0)); |
704 | return iTrue; | 786 | return iTrue; |
705 | } | 787 | } |
706 | if (iAbs(mouse.y - usable.y) < 2) { | 788 | if (iAbs(mouse.y - usable.y) < 2) { |
707 | setSnap_Window(d, | 789 | setSnap_MainWindow(d, |
708 | redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP | 790 | redo_WindowSnap | (d->place.lastHit == SDL_HITTEST_RESIZE_TOP |
709 | ? yMaximized_WindowSnap | 791 | ? yMaximized_WindowSnap |
710 | : maximized_WindowSnap)); | 792 | : maximized_WindowSnap)); |
711 | return iTrue; | 793 | return iTrue; |
712 | } | 794 | } |
713 | } | 795 | } |
714 | #endif /* defined LAGRANGE_ENABLE_CUSTOM_FRAME */ | 796 | #endif /* defined LAGRANGE_ENABLE_CUSTOM_FRAME */ |
715 | //printf("MOVED: %d, %d\n", ev->data1, ev->data2); fflush(stdout); | 797 | if (unsnap_MainWindow_(d, &newPos)) { |
716 | if (unsnap_Window_(d, &newPos)) { | ||
717 | return iTrue; | 798 | return iTrue; |
718 | } | 799 | } |
719 | if (isNormalPlacement_Window_(d)) { | 800 | if (isNormalPlacement_MainWindow_(d)) { |
720 | d->place.normalRect.pos = newPos; | 801 | d->place.normalRect.pos = newPos; |
721 | //printf("normal rect set (move)\n"); fflush(stdout); | 802 | // printf("normal rect set (move)\n"); fflush(stdout); |
722 | iInt2 border = zero_I2(); | 803 | iInt2 border = zero_I2(); |
723 | #if !defined (iPlatformApple) | 804 | #if !defined(iPlatformApple) |
724 | SDL_GetWindowBordersSize(d->win, &border.y, &border.x, NULL, NULL); | 805 | SDL_GetWindowBordersSize(d->base.win, &border.y, &border.x, NULL, NULL); |
725 | #endif | 806 | #endif |
726 | d->place.normalRect.pos = max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); | 807 | d->place.normalRect.pos = |
808 | max_I2(zero_I2(), sub_I2(d->place.normalRect.pos, border)); | ||
727 | } | 809 | } |
728 | return iTrue; | 810 | return iTrue; |
729 | } | 811 | } |
730 | case SDL_WINDOWEVENT_RESIZED: | 812 | case SDL_WINDOWEVENT_RESIZED: |
731 | if (d->isMinimized) { | 813 | if (d->base.isMinimized) { |
732 | //updateSize_Window_(d, iTrue); | 814 | // updateSize_Window_(d, iTrue); |
733 | return iTrue; | 815 | return iTrue; |
734 | } | 816 | } |
735 | if (unsnap_Window_(d, NULL)) { | 817 | if (unsnap_MainWindow_(d, NULL)) { |
736 | return iTrue; | 818 | return iTrue; |
737 | } | 819 | } |
738 | if (isNormalPlacement_Window_(d)) { | 820 | if (isNormalPlacement_MainWindow_(d)) { |
739 | d->place.normalRect.size = init_I2(ev->data1, ev->data2); | 821 | d->place.normalRect.size = init_I2(ev->data1, ev->data2); |
740 | //printf("normal rect set (resize)\n"); fflush(stdout); | 822 | // printf("normal rect set (resize)\n"); fflush(stdout); |
741 | } | 823 | } |
742 | checkPixelRatioChange_Window_(d); | 824 | checkPixelRatioChange_Window_(as_Window(d)); |
743 | //updateSize_Window_(d, iTrue /* we were already redrawing during the resize */); | ||
744 | postRefresh_App(); | 825 | postRefresh_App(); |
745 | return iTrue; | 826 | return iTrue; |
746 | case SDL_WINDOWEVENT_RESTORED: | 827 | case SDL_WINDOWEVENT_RESTORED: |
747 | case SDL_WINDOWEVENT_SHOWN: | 828 | case SDL_WINDOWEVENT_SHOWN: |
748 | updateSize_Window_(d, iTrue); | 829 | updateSize_MainWindow_(d, iTrue); |
749 | invalidate_Window_(d, iTrue); | 830 | invalidate_MainWindow_(d, iTrue); |
750 | d->isMinimized = iFalse; | 831 | d->base.isMinimized = iFalse; |
751 | postRefresh_App(); | 832 | postRefresh_App(); |
752 | return iTrue; | 833 | return iTrue; |
753 | case SDL_WINDOWEVENT_MINIMIZED: | 834 | case SDL_WINDOWEVENT_MINIMIZED: |
754 | d->isMinimized = iTrue; | 835 | d->base.isMinimized = iTrue; |
836 | return iTrue; | ||
837 | #else /* if defined (!iPlatformDesktop) */ | ||
838 | case SDL_WINDOWEVENT_RESIZED: | ||
839 | /* On mobile, this occurs when the display is rotated. */ | ||
840 | invalidate_Window(d); | ||
841 | postRefresh_App(); | ||
755 | return iTrue; | 842 | return iTrue; |
756 | #endif /* defined (iPlatformDesktop) */ | 843 | #endif |
757 | case SDL_WINDOWEVENT_LEAVE: | 844 | case SDL_WINDOWEVENT_LEAVE: |
758 | unhover_Widget(); | 845 | unhover_Widget(); |
759 | d->isMouseInside = iFalse; | 846 | d->base.isMouseInside = iFalse; |
760 | postCommand_App("window.mouse.exited"); | 847 | postCommand_App("window.mouse.exited"); |
761 | return iTrue; | 848 | return iTrue; |
762 | case SDL_WINDOWEVENT_ENTER: | 849 | case SDL_WINDOWEVENT_ENTER: |
763 | d->isMouseInside = iTrue; | 850 | d->base.isMouseInside = iTrue; |
851 | SDL_SetWindowInputFocus(d->base.win); | ||
764 | postCommand_App("window.mouse.entered"); | 852 | postCommand_App("window.mouse.entered"); |
765 | return iTrue; | 853 | return iTrue; |
766 | #if defined (iPlatformMobile) | ||
767 | case SDL_WINDOWEVENT_RESIZED: | ||
768 | /* On mobile, this occurs when the display is rotated. */ | ||
769 | invalidate_Window(d); | ||
770 | postRefresh_App(); | ||
771 | return iTrue; | ||
772 | #endif | ||
773 | case SDL_WINDOWEVENT_FOCUS_GAINED: | 854 | case SDL_WINDOWEVENT_FOCUS_GAINED: |
774 | d->focusGainedAt = SDL_GetTicks(); | 855 | d->base.focusGainedAt = SDL_GetTicks(); |
775 | setCapsLockDown_Keys(iFalse); | 856 | setCapsLockDown_Keys(iFalse); |
776 | postCommand_App("window.focus.gained"); | 857 | postCommand_App("window.focus.gained"); |
777 | d->isExposed = iTrue; | 858 | d->base.isExposed = iTrue; |
778 | #if defined (iPlatformMobile) | 859 | #if !defined (iPlatformDesktop) |
779 | /* Returned to foreground, may have lost buffered content. */ | 860 | /* Returned to foreground, may have lost buffered content. */ |
780 | invalidate_Window_(d, iTrue); | 861 | invalidate_MainWindow_(d, iTrue); |
781 | postCommand_App("window.unfreeze"); | 862 | postCommand_App("window.unfreeze"); |
782 | #endif | 863 | #endif |
783 | return iFalse; | 864 | return iFalse; |
784 | case SDL_WINDOWEVENT_FOCUS_LOST: | 865 | case SDL_WINDOWEVENT_FOCUS_LOST: |
785 | postCommand_App("window.focus.lost"); | 866 | postCommand_App("window.focus.lost"); |
786 | #if defined (iPlatformMobile) | 867 | #if !defined (iPlatformDesktop) |
787 | setFreezeDraw_Window(d, iTrue); | 868 | setFreezeDraw_MainWindow(d, iTrue); |
788 | #endif | 869 | #endif |
789 | return iFalse; | 870 | return iFalse; |
790 | case SDL_WINDOWEVENT_TAKE_FOCUS: | 871 | case SDL_WINDOWEVENT_TAKE_FOCUS: |
791 | SDL_SetWindowInputFocus(d->win); | 872 | SDL_SetWindowInputFocus(d->base.win); |
792 | postRefresh_App(); | 873 | postRefresh_App(); |
793 | return iTrue; | 874 | return iTrue; |
794 | default: | 875 | default: |
@@ -805,6 +886,7 @@ static void applyCursor_Window_(iWindow *d) { | |||
805 | } | 886 | } |
806 | 887 | ||
807 | iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | 888 | iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { |
889 | iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL); | ||
808 | switch (ev->type) { | 890 | switch (ev->type) { |
809 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 891 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
810 | case SDL_SYSWMEVENT: { | 892 | case SDL_SYSWMEVENT: { |
@@ -817,21 +899,28 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
817 | } | 899 | } |
818 | #endif | 900 | #endif |
819 | case SDL_WINDOWEVENT: { | 901 | case SDL_WINDOWEVENT: { |
820 | return handleWindowEvent_Window_(d, &ev->window); | 902 | if (mw) { |
903 | return handleWindowEvent_MainWindow_(mw, &ev->window); | ||
904 | } | ||
905 | else { | ||
906 | return handleWindowEvent_Window_(d, &ev->window); | ||
907 | } | ||
821 | } | 908 | } |
822 | case SDL_RENDER_TARGETS_RESET: | 909 | case SDL_RENDER_TARGETS_RESET: |
823 | case SDL_RENDER_DEVICE_RESET: { | 910 | case SDL_RENDER_DEVICE_RESET: { |
824 | invalidate_Window_(d, iTrue /* force full reset */); | 911 | if (mw) { |
912 | invalidate_MainWindow_(mw, iTrue /* force full reset */); | ||
913 | } | ||
825 | break; | 914 | break; |
826 | } | 915 | } |
827 | default: { | 916 | default: { |
828 | SDL_Event event = *ev; | 917 | SDL_Event event = *ev; |
829 | if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { | 918 | if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze") && mw) { |
830 | d->isDrawFrozen = iFalse; | 919 | mw->isDrawFrozen = iFalse; |
831 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { | 920 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { |
832 | SDL_ShowWindow(d->win); | 921 | SDL_ShowWindow(d->win); |
833 | } | 922 | } |
834 | postRefresh_App(); | 923 | draw_MainWindow(mw); /* don't show a frame of placeholder content */ |
835 | postCommand_App("media.player.update"); /* in case a player needs updating */ | 924 | postCommand_App("media.player.update"); /* in case a player needs updating */ |
836 | return iTrue; | 925 | return iTrue; |
837 | } | 926 | } |
@@ -873,7 +962,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
873 | } | 962 | } |
874 | } | 963 | } |
875 | } | 964 | } |
876 | const iWidget *oldHover = d->hover; | 965 | // const iWidget *oldHover = d->hover; |
877 | iBool wasUsed = iFalse; | 966 | iBool wasUsed = iFalse; |
878 | /* Dispatch first to the mouse-grabbed widget. */ | 967 | /* Dispatch first to the mouse-grabbed widget. */ |
879 | // iWidget *widget = d->root.widget; | 968 | // iWidget *widget = d->root.widget; |
@@ -913,7 +1002,7 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
913 | updateMetrics_Root(d->roots[i]); | 1002 | updateMetrics_Root(d->roots[i]); |
914 | } | 1003 | } |
915 | } | 1004 | } |
916 | if (isCommand_UserEvent(&event, "lang.changed")) { | 1005 | if (isCommand_UserEvent(&event, "lang.changed") && mw) { |
917 | #if defined (iHaveNativeMenus) | 1006 | #if defined (iHaveNativeMenus) |
918 | /* Retranslate the menus. */ | 1007 | /* Retranslate the menus. */ |
919 | removeMacMenus_(); | 1008 | removeMacMenus_(); |
@@ -927,9 +1016,6 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
927 | } | 1016 | } |
928 | } | 1017 | } |
929 | } | 1018 | } |
930 | if (oldHover != d->hover) { | ||
931 | postRefresh_App(); | ||
932 | } | ||
933 | if (event.type == SDL_MOUSEMOTION) { | 1019 | if (event.type == SDL_MOUSEMOTION) { |
934 | applyCursor_Window_(d); | 1020 | applyCursor_Window_(d); |
935 | } | 1021 | } |
@@ -949,6 +1035,10 @@ iBool setKeyRoot_Window(iWindow *d, iRoot *root) { | |||
949 | return iFalse; | 1035 | return iFalse; |
950 | } | 1036 | } |
951 | 1037 | ||
1038 | iLocalDef iBool isEscapeKeypress_(const SDL_Event *ev) { | ||
1039 | return (ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP) && ev->key.keysym.sym == SDLK_ESCAPE; | ||
1040 | } | ||
1041 | |||
952 | iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { | 1042 | iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { |
953 | if (ev->type == SDL_MOUSEMOTION) { | 1043 | if (ev->type == SDL_MOUSEMOTION) { |
954 | /* Hover widget may change. */ | 1044 | /* Hover widget may change. */ |
@@ -964,12 +1054,19 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
964 | } | 1054 | } |
965 | if ((ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP || ev->type == SDL_TEXTINPUT) | 1055 | if ((ev->type == SDL_KEYDOWN || ev->type == SDL_KEYUP || ev->type == SDL_TEXTINPUT) |
966 | && d->keyRoot != root) { | 1056 | && d->keyRoot != root) { |
967 | continue; /* Key events go only to the root with keyboard focus. */ | 1057 | if (!isEscapeKeypress_(ev)) { |
1058 | /* Key events go only to the root with keyboard focus, with the exception | ||
1059 | of Escape that will also affect the entire window. */ | ||
1060 | continue; | ||
1061 | } | ||
968 | } | 1062 | } |
969 | if (ev->type == SDL_MOUSEWHEEL && !contains_Rect(rect_Root(root), | 1063 | if (ev->type == SDL_MOUSEWHEEL && !contains_Rect(rect_Root(root), |
970 | coord_MouseWheelEvent(&ev->wheel))) { | 1064 | coord_MouseWheelEvent(&ev->wheel))) { |
971 | continue; /* Only process the event in the relevant split. */ | 1065 | continue; /* Only process the event in the relevant split. */ |
972 | } | 1066 | } |
1067 | if (!root->widget) { | ||
1068 | continue; | ||
1069 | } | ||
973 | setCurrent_Root(root); | 1070 | setCurrent_Root(root); |
974 | const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); | 1071 | const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); |
975 | if (wasUsed) { | 1072 | if (wasUsed) { |
@@ -1012,32 +1109,60 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) { | |||
1012 | } | 1109 | } |
1013 | 1110 | ||
1014 | void draw_Window(iWindow *d) { | 1111 | void draw_Window(iWindow *d) { |
1112 | if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) { | ||
1113 | return; | ||
1114 | } | ||
1115 | iPaint p; | ||
1116 | init_Paint(&p); | ||
1117 | iRoot *root = d->roots[0]; | ||
1118 | setCurrent_Root(root); | ||
1119 | unsetClip_Paint(&p); /* update clip to full window */ | ||
1120 | const iColor back = get_Color(uiBackground_ColorId); | ||
1121 | SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255); | ||
1122 | SDL_RenderClear(d->render); | ||
1123 | d->frameTime = SDL_GetTicks(); | ||
1124 | if (isExposed_Window(d)) { | ||
1125 | d->isInvalidated = iFalse; | ||
1126 | extern int drawCount_; | ||
1127 | drawRoot_Widget(root->widget); | ||
1128 | #if !defined (NDEBUG) | ||
1129 | draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_); | ||
1130 | drawCount_ = 0; | ||
1131 | #endif | ||
1132 | } | ||
1133 | // drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId); | ||
1134 | setCurrent_Root(NULL); | ||
1135 | SDL_RenderPresent(d->render); | ||
1136 | } | ||
1137 | |||
1138 | void draw_MainWindow(iMainWindow *d) { | ||
1139 | /* TODO: Try to make this a specialization of `draw_Window`? */ | ||
1140 | iWindow *w = as_Window(d); | ||
1015 | if (d->isDrawFrozen) { | 1141 | if (d->isDrawFrozen) { |
1016 | return; | 1142 | return; |
1017 | } | 1143 | } |
1018 | //#if defined (iPlatformMobile) | 1144 | setCurrent_Text(d->base.text); |
1019 | /* Check if root needs resizing. */ { | 1145 | /* Check if root needs resizing. */ { |
1020 | iInt2 renderSize; | 1146 | iInt2 renderSize; |
1021 | SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); | 1147 | SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); |
1022 | if (!isEqual_I2(renderSize, d->size)) { | 1148 | if (!isEqual_I2(renderSize, w->size)) { |
1023 | updateSize_Window_(d, iTrue); | 1149 | updateSize_MainWindow_(d, iTrue); |
1024 | processEvents_App(postedEventsOnly_AppEventMode); | 1150 | processEvents_App(postedEventsOnly_AppEventMode); |
1025 | } | 1151 | } |
1026 | } | 1152 | } |
1027 | //#endif | 1153 | const int winFlags = SDL_GetWindowFlags(d->base.win); |
1028 | const int winFlags = SDL_GetWindowFlags(d->win); | ||
1029 | const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; | 1154 | const iBool gotFocus = (winFlags & SDL_WINDOW_INPUT_FOCUS) != 0; |
1030 | iPaint p; | 1155 | iPaint p; |
1031 | init_Paint(&p); | 1156 | init_Paint(&p); |
1032 | /* Clear the window. The clear color is visible as a border around the window | 1157 | /* Clear the window. The clear color is visible as a border around the window |
1033 | when the custom frame is being used. */ { | 1158 | when the custom frame is being used. */ { |
1034 | setCurrent_Root(d->roots[0]); | 1159 | setCurrent_Root(w->roots[0]); |
1035 | #if defined (iPlatformAppleMobile) | 1160 | #if defined (iPlatformMobile) |
1036 | iColor back = get_Color(uiBackground_ColorId); | 1161 | iColor back = get_Color(uiBackground_ColorId); |
1037 | if (deviceType_App() == phone_AppDeviceType) { | 1162 | if (deviceType_App() == phone_AppDeviceType) { |
1038 | /* Page background extends to safe area, so fill it completely. */ | 1163 | /* Page background extends to safe area, so fill it completely. */ |
1039 | back = get_Color(tmBackground_ColorId); | 1164 | back = get_Color(tmBackground_ColorId); |
1040 | } | 1165 | } |
1041 | #else | 1166 | #else |
1042 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && | 1167 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && |
1043 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP | 1168 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP |
@@ -1045,19 +1170,20 @@ void draw_Window(iWindow *d) { | |||
1045 | : uiSeparator_ColorId); | 1170 | : uiSeparator_ColorId); |
1046 | #endif | 1171 | #endif |
1047 | unsetClip_Paint(&p); /* update clip to full window */ | 1172 | unsetClip_Paint(&p); /* update clip to full window */ |
1048 | SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255); | 1173 | SDL_SetRenderDrawColor(w->render, back.r, back.g, back.b, 255); |
1049 | SDL_RenderClear(d->render); | 1174 | SDL_RenderClear(w->render); |
1050 | } | 1175 | } |
1051 | /* Draw widgets. */ | 1176 | /* Draw widgets. */ |
1052 | d->frameTime = SDL_GetTicks(); | 1177 | w->frameTime = SDL_GetTicks(); |
1053 | if (isExposed_Window(d)) { | 1178 | if (isExposed_Window(w)) { |
1054 | d->isInvalidated = iFalse; | 1179 | w->isInvalidated = iFalse; |
1055 | iForIndices(i, d->roots) { | 1180 | extern int drawCount_; |
1056 | iRoot *root = d->roots[i]; | 1181 | iForIndices(i, w->roots) { |
1182 | iRoot *root = w->roots[i]; | ||
1057 | if (root) { | 1183 | if (root) { |
1058 | setCurrent_Root(root); | 1184 | setCurrent_Root(root); |
1059 | unsetClip_Paint(&p); /* update clip to current root */ | 1185 | unsetClip_Paint(&p); /* update clip to current root */ |
1060 | draw_Widget(root->widget); | 1186 | drawRoot_Widget(root->widget); |
1061 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 1187 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) |
1062 | /* App icon. */ | 1188 | /* App icon. */ |
1063 | const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon"); | 1189 | const iWidget *appIcon = findChild_Widget(root->widget, "winbar.icon"); |
@@ -1070,14 +1196,14 @@ void draw_Window(iWindow *d) { | |||
1070 | SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b); | 1196 | SDL_SetTextureColorMod(d->appIcon, iconColor.r, iconColor.g, iconColor.b); |
1071 | SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92); | 1197 | SDL_SetTextureAlphaMod(d->appIcon, gotFocus || !isLight ? 255 : 92); |
1072 | SDL_RenderCopy( | 1198 | SDL_RenderCopy( |
1073 | d->render, | 1199 | w->render, |
1074 | d->appIcon, | 1200 | d->appIcon, |
1075 | NULL, | 1201 | NULL, |
1076 | &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size }); | 1202 | &(SDL_Rect){ left_Rect(rect) + gap_UI * 1.25f, mid.y - size / 2, size, size }); |
1077 | } | 1203 | } |
1078 | #endif | 1204 | #endif |
1079 | /* Root separator and keyboard focus indicator. */ | 1205 | /* Root separator and keyboard focus indicator. */ |
1080 | if (numRoots_Window(d) > 1){ | 1206 | if (numRoots_Window(w) > 1){ |
1081 | const iRect bounds = bounds_Widget(root->widget); | 1207 | const iRect bounds = bounds_Widget(root->widget); |
1082 | if (i == 1) { | 1208 | if (i == 1) { |
1083 | fillRect_Paint(&p, (iRect){ | 1209 | fillRect_Paint(&p, (iRect){ |
@@ -1085,7 +1211,7 @@ void draw_Window(iWindow *d) { | |||
1085 | init_I2(gap_UI / 4, height_Rect(bounds)) | 1211 | init_I2(gap_UI / 4, height_Rect(bounds)) |
1086 | }, uiSeparator_ColorId); | 1212 | }, uiSeparator_ColorId); |
1087 | } | 1213 | } |
1088 | if (root == d->keyRoot) { | 1214 | if (root == w->keyRoot) { |
1089 | const iBool isDark = isDark_ColorTheme(colorTheme_App()); | 1215 | const iBool isDark = isDark_ColorTheme(colorTheme_App()); |
1090 | fillRect_Paint(&p, (iRect){ | 1216 | fillRect_Paint(&p, (iRect){ |
1091 | topLeft_Rect(bounds), | 1217 | topLeft_Rect(bounds), |
@@ -1097,6 +1223,10 @@ void draw_Window(iWindow *d) { | |||
1097 | } | 1223 | } |
1098 | } | 1224 | } |
1099 | setCurrent_Root(NULL); | 1225 | setCurrent_Root(NULL); |
1226 | #if !defined (NDEBUG) | ||
1227 | draw_Text(defaultBold_FontId, safeRect_Root(w->roots[0]).pos, red_ColorId, "%d", drawCount_); | ||
1228 | drawCount_ = 0; | ||
1229 | #endif | ||
1100 | } | 1230 | } |
1101 | #if 0 | 1231 | #if 0 |
1102 | /* Text cache debugging. */ { | 1232 | /* Text cache debugging. */ { |
@@ -1106,21 +1236,21 @@ void draw_Window(iWindow *d) { | |||
1106 | SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect); | 1236 | SDL_RenderCopy(d->render, glyphCache_Text(), NULL, &rect); |
1107 | } | 1237 | } |
1108 | #endif | 1238 | #endif |
1109 | SDL_RenderPresent(d->render); | 1239 | SDL_RenderPresent(w->render); |
1110 | } | 1240 | } |
1111 | 1241 | ||
1112 | void resize_Window(iWindow *d, int w, int h) { | 1242 | void resize_MainWindow(iMainWindow *d, int w, int h) { |
1113 | if (w > 0 && h > 0) { | 1243 | if (w > 0 && h > 0) { |
1114 | SDL_SetWindowSize(d->win, w, h); | 1244 | SDL_SetWindowSize(d->base.win, w, h); |
1115 | updateSize_Window_(d, iFalse); | 1245 | updateSize_MainWindow_(d, iFalse); |
1116 | } | 1246 | } |
1117 | else { | 1247 | else { |
1118 | updateSize_Window_(d, iTrue); /* notify always */ | 1248 | updateSize_MainWindow_(d, iTrue); /* notify always */ |
1119 | } | 1249 | } |
1120 | } | 1250 | } |
1121 | 1251 | ||
1122 | void setTitle_Window(iWindow *d, const iString *title) { | 1252 | void setTitle_MainWindow(iMainWindow *d, const iString *title) { |
1123 | SDL_SetWindowTitle(d->win, cstr_String(title)); | 1253 | SDL_SetWindowTitle(d->base.win, cstr_String(title)); |
1124 | iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title"); | 1254 | iLabelWidget *bar = findChild_Widget(get_Root()->widget, "winbar.title"); |
1125 | if (bar) { | 1255 | if (bar) { |
1126 | updateText_LabelWidget(bar, title); | 1256 | updateText_LabelWidget(bar, title); |
@@ -1128,6 +1258,9 @@ void setTitle_Window(iWindow *d, const iString *title) { | |||
1128 | } | 1258 | } |
1129 | 1259 | ||
1130 | void setUiScale_Window(iWindow *d, float uiScale) { | 1260 | void setUiScale_Window(iWindow *d, float uiScale) { |
1261 | if (uiScale <= 0.0f) { | ||
1262 | uiScale = 1.0f; | ||
1263 | } | ||
1131 | uiScale = iClamp(uiScale, 0.5f, 4.0f); | 1264 | uiScale = iClamp(uiScale, 0.5f, 4.0f); |
1132 | if (d) { | 1265 | if (d) { |
1133 | if (iAbs(d->uiScale - uiScale) > 0.0001f) { | 1266 | if (iAbs(d->uiScale - uiScale) > 0.0001f) { |
@@ -1140,7 +1273,7 @@ void setUiScale_Window(iWindow *d, float uiScale) { | |||
1140 | } | 1273 | } |
1141 | } | 1274 | } |
1142 | 1275 | ||
1143 | void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) { | 1276 | void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) { |
1144 | d->isDrawFrozen = freezeDraw; | 1277 | d->isDrawFrozen = freezeDraw; |
1145 | } | 1278 | } |
1146 | 1279 | ||
@@ -1191,63 +1324,80 @@ iWindow *get_Window(void) { | |||
1191 | return theWindow_; | 1324 | return theWindow_; |
1192 | } | 1325 | } |
1193 | 1326 | ||
1327 | void setCurrent_Window(iAnyWindow *d) { | ||
1328 | theWindow_ = d; | ||
1329 | if (type_Window(d) == main_WindowType) { | ||
1330 | theMainWindow_ = d; | ||
1331 | } | ||
1332 | if (d) { | ||
1333 | setCurrent_Text(theWindow_->text); | ||
1334 | setCurrent_Root(theWindow_->keyRoot); | ||
1335 | } | ||
1336 | else { | ||
1337 | setCurrent_Text(NULL); | ||
1338 | setCurrent_Root(NULL); | ||
1339 | } | ||
1340 | } | ||
1341 | |||
1342 | iMainWindow *get_MainWindow(void) { | ||
1343 | return theMainWindow_; | ||
1344 | } | ||
1345 | |||
1194 | iBool isOpenGLRenderer_Window(void) { | 1346 | iBool isOpenGLRenderer_Window(void) { |
1195 | return isOpenGLRenderer_; | 1347 | return isOpenGLRenderer_; |
1196 | } | 1348 | } |
1197 | 1349 | ||
1198 | void setKeyboardHeight_Window(iWindow *d, int height) { | 1350 | void setKeyboardHeight_MainWindow(iMainWindow *d, int height) { |
1199 | if (d->keyboardHeight != height) { | 1351 | if (d->keyboardHeight != height) { |
1200 | d->keyboardHeight = height; | 1352 | d->keyboardHeight = height; |
1201 | if (height == 0) { | ||
1202 | setFlags_Anim(&d->rootOffset, easeBoth_AnimFlag, iTrue); | ||
1203 | setValue_Anim(&d->rootOffset, 0, 250); | ||
1204 | } | ||
1205 | postCommandf_App("keyboard.changed arg:%d", height); | 1353 | postCommandf_App("keyboard.changed arg:%d", height); |
1206 | postRefresh_App(); | 1354 | postRefresh_App(); |
1207 | } | 1355 | } |
1208 | } | 1356 | } |
1209 | 1357 | ||
1210 | void checkPendingSplit_Window(iWindow *d) { | 1358 | void checkPendingSplit_MainWindow(iMainWindow *d) { |
1211 | if (d->splitMode != d->pendingSplitMode) { | 1359 | if (d->splitMode != d->pendingSplitMode) { |
1212 | setSplitMode_Window(d, d->pendingSplitMode); | 1360 | setSplitMode_MainWindow(d, d->pendingSplitMode); |
1213 | } | 1361 | } |
1214 | } | 1362 | } |
1215 | 1363 | ||
1216 | void swapRoots_Window(iWindow *d) { | 1364 | void swapRoots_MainWindow(iMainWindow *d) { |
1217 | if (numRoots_Window(d) == 2) { | 1365 | iWindow *w = as_Window(d); |
1218 | iSwap(iRoot *, d->roots[0], d->roots[1]); | 1366 | if (numRoots_Window(w) == 2) { |
1219 | updateSize_Window_(d, iTrue); | 1367 | iSwap(iRoot *, w->roots[0], w->roots[1]); |
1368 | updateSize_MainWindow_(d, iTrue); | ||
1220 | } | 1369 | } |
1221 | } | 1370 | } |
1222 | 1371 | ||
1223 | void setSplitMode_Window(iWindow *d, int splitFlags) { | 1372 | void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) { |
1224 | const int splitMode = splitFlags & mode_WindowSplit; | 1373 | const int splitMode = splitFlags & mode_WindowSplit; |
1225 | if (deviceType_App() == phone_AppDeviceType) { | 1374 | if (deviceType_App() == phone_AppDeviceType) { |
1226 | /* There isn't enough room on the phone. */ | 1375 | /* There isn't enough room on the phone. */ |
1227 | /* TODO: Maybe in landscape only? */ | 1376 | /* TODO: Maybe in landscape only? */ |
1228 | return; | 1377 | return; |
1229 | } | 1378 | } |
1379 | iWindow *w = as_Window(d); | ||
1230 | iAssert(current_Root() == NULL); | 1380 | iAssert(current_Root() == NULL); |
1231 | if (d->splitMode != splitMode) { | 1381 | if (d->splitMode != splitMode) { |
1232 | int oldCount = numRoots_Window(d); | 1382 | int oldCount = numRoots_Window(w); |
1233 | setFreezeDraw_Window(d, iTrue); | 1383 | setFreezeDraw_MainWindow(d, iTrue); |
1234 | if (oldCount == 2 && splitMode == 0) { | 1384 | if (oldCount == 2 && splitMode == 0) { |
1235 | /* Keep references to the tabs of the second root. */ | 1385 | /* Keep references to the tabs of the second root. */ |
1236 | const iDocumentWidget *curPage = document_Root(d->keyRoot); | 1386 | const iDocumentWidget *curPage = document_Root(w->keyRoot); |
1237 | if (!curPage) { | 1387 | if (!curPage) { |
1238 | /* All tabs closed on that side. */ | 1388 | /* All tabs closed on that side. */ |
1239 | curPage = document_Root(otherRoot_Window(d, d->keyRoot)); | 1389 | curPage = document_Root(otherRoot_Window(w, w->keyRoot)); |
1240 | } | 1390 | } |
1241 | iObjectList *tabs = listDocuments_App(d->roots[1]); | 1391 | iObjectList *tabs = listDocuments_App(w->roots[1]); |
1242 | iForEach(ObjectList, i, tabs) { | 1392 | iForEach(ObjectList, i, tabs) { |
1243 | setRoot_Widget(i.object, d->roots[0]); | 1393 | setRoot_Widget(i.object, w->roots[0]); |
1244 | } | 1394 | } |
1245 | setFocus_Widget(NULL); | 1395 | setFocus_Widget(NULL); |
1246 | delete_Root(d->roots[1]); | 1396 | delete_Root(w->roots[1]); |
1247 | d->roots[1] = NULL; | 1397 | w->roots[1] = NULL; |
1248 | d->keyRoot = d->roots[0]; | 1398 | w->keyRoot = w->roots[0]; |
1249 | /* Move the deleted root's tabs to the first root. */ | 1399 | /* Move the deleted root's tabs to the first root. */ |
1250 | setCurrent_Root(d->roots[0]); | 1400 | setCurrent_Root(w->roots[0]); |
1251 | iWidget *docTabs = findWidget_Root("doctabs"); | 1401 | iWidget *docTabs = findWidget_Root("doctabs"); |
1252 | iForEach(ObjectList, j, tabs) { | 1402 | iForEach(ObjectList, j, tabs) { |
1253 | appendTabPage_Widget(docTabs, j.object, "", 0, 0); | 1403 | appendTabPage_Widget(docTabs, j.object, "", 0, 0); |
@@ -1259,38 +1409,48 @@ void setSplitMode_Window(iWindow *d, int splitFlags) { | |||
1259 | } | 1409 | } |
1260 | else if (splitMode && oldCount == 1) { | 1410 | else if (splitMode && oldCount == 1) { |
1261 | /* Add a second root. */ | 1411 | /* Add a second root. */ |
1262 | iDocumentWidget *moved = document_Root(d->roots[0]); | 1412 | iDocumentWidget *moved = document_Root(w->roots[0]); |
1263 | iAssert(d->roots[1] == NULL); | 1413 | iAssert(w->roots[1] == NULL); |
1264 | const iBool addToLeft = (prefs_App()->pinSplit == 2); | 1414 | const iBool addToLeft = (prefs_App()->pinSplit == 2); |
1265 | size_t newRootIndex = 1; | 1415 | size_t newRootIndex = 1; |
1266 | if (addToLeft) { | 1416 | if (addToLeft) { |
1267 | iSwap(iRoot *, d->roots[0], d->roots[1]); | 1417 | iSwap(iRoot *, w->roots[0], w->roots[1]); |
1268 | newRootIndex = 0; | 1418 | newRootIndex = 0; |
1269 | } | 1419 | } |
1270 | d->roots[newRootIndex] = new_Root(); | 1420 | w->roots[newRootIndex] = new_Root(); |
1271 | d->keyRoot = d->roots[newRootIndex]; | 1421 | w->keyRoot = w->roots[newRootIndex]; |
1272 | setCurrent_Root(d->roots[newRootIndex]); | 1422 | w->keyRoot->window = w; |
1273 | createUserInterface_Root(d->roots[newRootIndex]); | 1423 | setCurrent_Root(w->roots[newRootIndex]); |
1424 | createUserInterface_Root(w->roots[newRootIndex]); | ||
1425 | /* Bookmark folder state will match the old root's state. */ { | ||
1426 | for (int sb = 0; sb < 2; sb++) { | ||
1427 | const char *sbId = (sb == 0 ? "sidebar" : "sidebar2"); | ||
1428 | setClosedFolders_SidebarWidget( | ||
1429 | findChild_Widget(w->roots[newRootIndex]->widget, sbId), | ||
1430 | closedFolders_SidebarWidget( | ||
1431 | findChild_Widget(w->roots[newRootIndex ^ 1]->widget, sbId))); | ||
1432 | } | ||
1433 | } | ||
1274 | if (!isEmpty_String(d->pendingSplitUrl)) { | 1434 | if (!isEmpty_String(d->pendingSplitUrl)) { |
1275 | postCommandf_Root(d->roots[newRootIndex], "open url:%s", | 1435 | postCommandf_Root(w->roots[newRootIndex], "open url:%s", |
1276 | cstr_String(d->pendingSplitUrl)); | 1436 | cstr_String(d->pendingSplitUrl)); |
1277 | clear_String(d->pendingSplitUrl); | 1437 | clear_String(d->pendingSplitUrl); |
1278 | } | 1438 | } |
1279 | else if (~splitFlags & noEvents_WindowSplit) { | 1439 | else if (~splitFlags & noEvents_WindowSplit) { |
1280 | iWidget *docTabs0 = findChild_Widget(d->roots[newRootIndex ^ 1]->widget, "doctabs"); | 1440 | iWidget *docTabs0 = findChild_Widget(w->roots[newRootIndex ^ 1]->widget, "doctabs"); |
1281 | iWidget *docTabs1 = findChild_Widget(d->roots[newRootIndex]->widget, "doctabs"); | 1441 | iWidget *docTabs1 = findChild_Widget(w->roots[newRootIndex]->widget, "doctabs"); |
1282 | /* If the old root has multiple tabs, move the current one to the new split. */ | 1442 | /* If the old root has multiple tabs, move the current one to the new split. */ |
1283 | if (tabCount_Widget(docTabs0) >= 2) { | 1443 | if (tabCount_Widget(docTabs0) >= 2) { |
1284 | int movedIndex = tabPageIndex_Widget(docTabs0, moved); | 1444 | int movedIndex = tabPageIndex_Widget(docTabs0, moved); |
1285 | removeTabPage_Widget(docTabs0, movedIndex); | 1445 | removeTabPage_Widget(docTabs0, movedIndex); |
1286 | showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0))); | 1446 | showTabPage_Widget(docTabs0, tabPage_Widget(docTabs0, iMax(movedIndex - 1, 0))); |
1287 | iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ | 1447 | iRelease(removeTabPage_Widget(docTabs1, 0)); /* delete the default tab */ |
1288 | setRoot_Widget(as_Widget(moved), d->roots[newRootIndex]); | 1448 | setRoot_Widget(as_Widget(moved), w->roots[newRootIndex]); |
1289 | prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); | 1449 | prependTabPage_Widget(docTabs1, iClob(moved), "", 0, 0); |
1290 | postCommandf_App("tabs.switch page:%p", moved); | 1450 | postCommandf_App("tabs.switch page:%p", moved); |
1291 | } | 1451 | } |
1292 | else { | 1452 | else { |
1293 | postCommand_Root(d->roots[newRootIndex], "navigate.home"); | 1453 | postCommand_Root(w->roots[newRootIndex], "navigate.home"); |
1294 | } | 1454 | } |
1295 | } | 1455 | } |
1296 | setCurrent_Root(NULL); | 1456 | setCurrent_Root(NULL); |
@@ -1319,26 +1479,26 @@ void setSplitMode_Window(iWindow *d, int splitFlags) { | |||
1319 | } | 1479 | } |
1320 | #endif | 1480 | #endif |
1321 | if (~splitFlags & noEvents_WindowSplit) { | 1481 | if (~splitFlags & noEvents_WindowSplit) { |
1322 | updateSize_Window_(d, iTrue); | 1482 | updateSize_MainWindow_(d, iTrue); |
1323 | postCommand_App("window.unfreeze"); | 1483 | postCommand_App("window.unfreeze"); |
1324 | } | 1484 | } |
1325 | } | 1485 | } |
1326 | } | 1486 | } |
1327 | 1487 | ||
1328 | void setSnap_Window(iWindow *d, int snapMode) { | 1488 | void setSnap_MainWindow(iMainWindow *d, int snapMode) { |
1329 | if (!prefs_App()->customFrame) { | 1489 | if (!prefs_App()->customFrame) { |
1330 | if (snapMode == maximized_WindowSnap) { | 1490 | if (snapMode == maximized_WindowSnap) { |
1331 | SDL_MaximizeWindow(d->win); | 1491 | SDL_MaximizeWindow(d->base.win); |
1332 | } | 1492 | } |
1333 | else if (snapMode == fullscreen_WindowSnap) { | 1493 | else if (snapMode == fullscreen_WindowSnap) { |
1334 | SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); | 1494 | SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP); |
1335 | } | 1495 | } |
1336 | else { | 1496 | else { |
1337 | if (snap_Window(d) == fullscreen_WindowSnap) { | 1497 | if (snap_MainWindow(d) == fullscreen_WindowSnap) { |
1338 | SDL_SetWindowFullscreen(d->win, 0); | 1498 | SDL_SetWindowFullscreen(d->base.win, 0); |
1339 | } | 1499 | } |
1340 | else { | 1500 | else { |
1341 | SDL_RestoreWindow(d->win); | 1501 | SDL_RestoreWindow(d->base.win); |
1342 | } | 1502 | } |
1343 | } | 1503 | } |
1344 | return; | 1504 | return; |
@@ -1350,9 +1510,9 @@ void setSnap_Window(iWindow *d, int snapMode) { | |||
1350 | const int snapDist = gap_UI * 4; | 1510 | const int snapDist = gap_UI * 4; |
1351 | iRect newRect = zero_Rect(); | 1511 | iRect newRect = zero_Rect(); |
1352 | SDL_Rect usable; | 1512 | SDL_Rect usable; |
1353 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->win), &usable); | 1513 | SDL_GetDisplayUsableBounds(SDL_GetWindowDisplayIndex(d->base.win), &usable); |
1354 | if (d->place.snap == fullscreen_WindowSnap) { | 1514 | if (d->place.snap == fullscreen_WindowSnap) { |
1355 | SDL_SetWindowFullscreen(d->win, 0); | 1515 | SDL_SetWindowFullscreen(d->base.win, 0); |
1356 | } | 1516 | } |
1357 | d->place.snap = snapMode & ~redo_WindowSnap; | 1517 | d->place.snap = snapMode & ~redo_WindowSnap; |
1358 | switch (snapMode & mask_WindowSnap) { | 1518 | switch (snapMode & mask_WindowSnap) { |
@@ -1373,8 +1533,8 @@ void setSnap_Window(iWindow *d, int snapMode) { | |||
1373 | case yMaximized_WindowSnap: | 1533 | case yMaximized_WindowSnap: |
1374 | newRect.pos.y = 0; | 1534 | newRect.pos.y = 0; |
1375 | newRect.size.y = usable.h; | 1535 | newRect.size.y = usable.h; |
1376 | SDL_GetWindowSize(d->win, &newRect.size.x, NULL); | 1536 | SDL_GetWindowSize(d->base.win, &newRect.size.x, NULL); |
1377 | SDL_GetWindowPosition(d->win, &newRect.pos.x, NULL); | 1537 | SDL_GetWindowPosition(d->base.win, &newRect.pos.x, NULL); |
1378 | /* Snap the window to left/right edges, if close by. */ | 1538 | /* Snap the window to left/right edges, if close by. */ |
1379 | if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) { | 1539 | if (iAbs(right_Rect(newRect) - (usable.x + usable.w)) < snapDist) { |
1380 | newRect.pos.x = usable.x + usable.w - width_Rect(newRect); | 1540 | newRect.pos.x = usable.x + usable.w - width_Rect(newRect); |
@@ -1384,7 +1544,7 @@ void setSnap_Window(iWindow *d, int snapMode) { | |||
1384 | } | 1544 | } |
1385 | break; | 1545 | break; |
1386 | case fullscreen_WindowSnap: | 1546 | case fullscreen_WindowSnap: |
1387 | SDL_SetWindowFullscreen(d->win, SDL_WINDOW_FULLSCREEN_DESKTOP); | 1547 | SDL_SetWindowFullscreen(d->base.win, SDL_WINDOW_FULLSCREEN_DESKTOP); |
1388 | break; | 1548 | break; |
1389 | } | 1549 | } |
1390 | if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) { | 1550 | if (snapMode & (topBit_WindowSnap | bottomBit_WindowSnap)) { |
@@ -1416,9 +1576,9 @@ void setSnap_Window(iWindow *d, int snapMode) { | |||
1416 | #endif /* defined (LAGRANGE_ENABLE_CUSTOM_FRAME) */ | 1576 | #endif /* defined (LAGRANGE_ENABLE_CUSTOM_FRAME) */ |
1417 | } | 1577 | } |
1418 | 1578 | ||
1419 | int snap_Window(const iWindow *d) { | 1579 | int snap_MainWindow(const iMainWindow *d) { |
1420 | if (!prefs_App()->customFrame) { | 1580 | if (!prefs_App()->customFrame) { |
1421 | const int flags = SDL_GetWindowFlags(d->win); | 1581 | const int flags = SDL_GetWindowFlags(d->base.win); |
1422 | if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { | 1582 | if (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) { |
1423 | return fullscreen_WindowSnap; | 1583 | return fullscreen_WindowSnap; |
1424 | } | 1584 | } |
@@ -1429,3 +1589,27 @@ int snap_Window(const iWindow *d) { | |||
1429 | } | 1589 | } |
1430 | return d->place.snap; | 1590 | return d->place.snap; |
1431 | } | 1591 | } |
1592 | |||
1593 | /*----------------------------------------------------------------------------------------------*/ | ||
1594 | |||
1595 | iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) { | ||
1596 | iWindow *win = | ||
1597 | new_Window(popup_WindowType, | ||
1598 | (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) }, | ||
1599 | SDL_WINDOW_ALWAYS_ON_TOP | | ||
1600 | #if !defined (iPlatformAppleDesktop) | ||
1601 | SDL_WINDOW_BORDERLESS | | ||
1602 | #endif | ||
1603 | SDL_WINDOW_POPUP_MENU | | ||
1604 | SDL_WINDOW_SKIP_TASKBAR); | ||
1605 | #if defined (iPlatformAppleDesktop) | ||
1606 | hideTitleBar_MacOS(win); /* make it a borderless window */ | ||
1607 | #endif | ||
1608 | iRoot *root = new_Root(); | ||
1609 | win->roots[0] = root; | ||
1610 | win->keyRoot = root; | ||
1611 | root->widget = rootWidget; | ||
1612 | root->window = win; | ||
1613 | setRoot_Widget(rootWidget, root); | ||
1614 | return win; | ||
1615 | } | ||
diff --git a/src/ui/window.h b/src/ui/window.h index 63f7e5f2..f1827931 100644 --- a/src/ui/window.h +++ b/src/ui/window.h | |||
@@ -29,8 +29,19 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
29 | #include <SDL_render.h> | 29 | #include <SDL_render.h> |
30 | #include <SDL_video.h> | 30 | #include <SDL_video.h> |
31 | 31 | ||
32 | enum iWindowType { | ||
33 | main_WindowType, | ||
34 | popup_WindowType, | ||
35 | }; | ||
36 | |||
37 | iDeclareType(MainWindow) | ||
38 | iDeclareType(Text) | ||
32 | iDeclareType(Window) | 39 | iDeclareType(Window) |
33 | iDeclareTypeConstructionArgs(Window, iRect rect) | 40 | |
41 | iDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags) | ||
42 | iDeclareTypeConstructionArgs(MainWindow, iRect rect) | ||
43 | |||
44 | typedef iAny iAnyWindow; | ||
34 | 45 | ||
35 | enum iWindowSnap { | 46 | enum iWindowSnap { |
36 | none_WindowSnap = 0, | 47 | none_WindowSnap = 0, |
@@ -69,9 +80,8 @@ enum iWindowSplit { | |||
69 | }; | 80 | }; |
70 | 81 | ||
71 | struct Impl_Window { | 82 | struct Impl_Window { |
83 | enum iWindowType type; | ||
72 | SDL_Window * win; | 84 | SDL_Window * win; |
73 | iWindowPlacement place; | ||
74 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ | ||
75 | iBool isExposed; | 85 | iBool isExposed; |
76 | iBool isMinimized; | 86 | iBool isMinimized; |
77 | iBool isMouseInside; | 87 | iBool isMouseInside; |
@@ -80,12 +90,8 @@ struct Impl_Window { | |||
80 | uint32_t focusGainedAt; | 90 | uint32_t focusGainedAt; |
81 | SDL_Renderer *render; | 91 | SDL_Renderer *render; |
82 | iInt2 size; | 92 | iInt2 size; |
83 | int splitMode; | ||
84 | int pendingSplitMode; | ||
85 | iString * pendingSplitUrl; /* URL to open in a newly opened split */ | ||
86 | iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ | ||
87 | iRoot * keyRoot; /* root that has the current keyboard input focus */ | ||
88 | iWidget * hover; | 93 | iWidget * hover; |
94 | iWidget * lastHover; /* cleared if deleted */ | ||
89 | iWidget * mouseGrab; | 95 | iWidget * mouseGrab; |
90 | iWidget * focus; | 96 | iWidget * focus; |
91 | float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */ | 97 | float pixelRatio; /* conversion between points and pixels, e.g., coords, window size */ |
@@ -93,57 +99,118 @@ struct Impl_Window { | |||
93 | float uiScale; | 99 | float uiScale; |
94 | uint32_t frameTime; | 100 | uint32_t frameTime; |
95 | double presentTime; | 101 | double presentTime; |
96 | SDL_Texture * appIcon; | ||
97 | SDL_Texture * borderShadow; | ||
98 | SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; | 102 | SDL_Cursor * cursors[SDL_NUM_SYSTEM_CURSORS]; |
99 | SDL_Cursor * pendingCursor; | 103 | SDL_Cursor * pendingCursor; |
100 | int loadAnimTimer; | 104 | iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ |
101 | iAnim rootOffset; | 105 | iRoot * keyRoot; /* root that has the current keyboard input focus */ |
102 | int keyboardHeight; /* mobile software keyboards */ | 106 | SDL_Texture * borderShadow; |
107 | iText * text; | ||
103 | }; | 108 | }; |
104 | 109 | ||
110 | struct Impl_MainWindow { | ||
111 | iWindow base; | ||
112 | iWindowPlacement place; | ||
113 | iBool isDrawFrozen; /* avoids premature draws while restoring window state */ | ||
114 | int splitMode; | ||
115 | int pendingSplitMode; | ||
116 | iString * pendingSplitUrl; /* URL to open in a newly opened split */ | ||
117 | SDL_Texture * appIcon; | ||
118 | int keyboardHeight; /* mobile software keyboards */ | ||
119 | }; | ||
120 | |||
121 | iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { | ||
122 | if (d) { | ||
123 | return ((const iWindow *) d)->type; | ||
124 | } | ||
125 | return main_WindowType; | ||
126 | } | ||
127 | |||
128 | uint32_t id_Window (const iWindow *); | ||
129 | iInt2 size_Window (const iWindow *); | ||
130 | iInt2 maxTextureSize_Window (const iWindow *); | ||
131 | float uiScale_Window (const iWindow *); | ||
132 | iInt2 coord_Window (const iWindow *, int x, int y); | ||
133 | iInt2 mouseCoord_Window (const iWindow *, int whichDevice); | ||
134 | iAnyObject * hitChild_Window (const iWindow *, iInt2 coord); | ||
135 | uint32_t frameTime_Window (const iWindow *); | ||
136 | SDL_Renderer * renderer_Window (const iWindow *); | ||
137 | int numRoots_Window (const iWindow *); | ||
138 | iRoot * findRoot_Window (const iWindow *, const iWidget *widget); | ||
139 | iRoot * otherRoot_Window (const iWindow *, iRoot *root); | ||
140 | |||
105 | iBool processEvent_Window (iWindow *, const SDL_Event *); | 141 | iBool processEvent_Window (iWindow *, const SDL_Event *); |
106 | iBool dispatchEvent_Window (iWindow *, const SDL_Event *); | 142 | iBool dispatchEvent_Window (iWindow *, const SDL_Event *); |
107 | void invalidate_Window (iWindow *); /* discard all cached graphics */ | 143 | void invalidate_Window (iAnyWindow *); /* discard all cached graphics */ |
108 | void draw_Window (iWindow *); | 144 | void draw_Window (iWindow *); |
109 | void drawWhileResizing_Window(iWindow *d, int w, int h); /* workaround for SDL bug */ | ||
110 | void resize_Window (iWindow *, int w, int h); | ||
111 | void setTitle_Window (iWindow *, const iString *title); | ||
112 | void setUiScale_Window (iWindow *, float uiScale); | 145 | void setUiScale_Window (iWindow *, float uiScale); |
113 | void setFreezeDraw_Window (iWindow *, iBool freezeDraw); | ||
114 | iBool setKeyRoot_Window (iWindow *, iRoot *root); | ||
115 | void setCursor_Window (iWindow *, int cursor); | 146 | void setCursor_Window (iWindow *, int cursor); |
116 | void setSnap_Window (iWindow *, int snapMode); | 147 | iBool setKeyRoot_Window (iWindow *, iRoot *root); |
117 | void setKeyboardHeight_Window(iWindow *, int height); | ||
118 | void setSplitMode_Window (iWindow *, int splitMode); | ||
119 | void showToolbars_Window (iWindow *, iBool show); | ||
120 | iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); | 148 | iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); |
121 | void checkPendingSplit_Window(iWindow *); | ||
122 | void swapRoots_Window (iWindow *); | ||
123 | |||
124 | uint32_t id_Window (const iWindow *); | ||
125 | iInt2 size_Window (const iWindow *); | ||
126 | iInt2 maxTextureSize_Window (const iWindow *); | ||
127 | float uiScale_Window (const iWindow *); | ||
128 | iInt2 coord_Window (const iWindow *, int x, int y); | ||
129 | iInt2 mouseCoord_Window (const iWindow *, int whichDevice); | ||
130 | iAnyObject *hitChild_Window (const iWindow *, iInt2 coord); | ||
131 | uint32_t frameTime_Window (const iWindow *); | ||
132 | SDL_Renderer *renderer_Window (const iWindow *); | ||
133 | int snap_Window (const iWindow *); | ||
134 | iBool isFullscreen_Window (const iWindow *); | ||
135 | int numRoots_Window (const iWindow *); | ||
136 | iRoot * findRoot_Window (const iWindow *, const iWidget *widget); | ||
137 | iRoot * otherRoot_Window (const iWindow *, iRoot *root); | ||
138 | iWindow * get_Window (void); | ||
139 | 149 | ||
150 | iWindow * get_Window (void); | ||
140 | iBool isOpenGLRenderer_Window (void); | 151 | iBool isOpenGLRenderer_Window (void); |
141 | 152 | ||
142 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | 153 | void setCurrent_Window (iAnyWindow *); |
143 | SDL_HitTestResult hitTest_Window(const iWindow *d, iInt2 pos); | ||
144 | #endif | ||
145 | 154 | ||
146 | iLocalDef iBool isExposed_Window(const iWindow *d) { | 155 | iLocalDef iBool isExposed_Window(const iWindow *d) { |
147 | iAssert(d); | 156 | iAssert(d); |
148 | return d->isExposed; | 157 | return d->isExposed; |
149 | } | 158 | } |
159 | |||
160 | iLocalDef iWindow *as_Window(iAnyWindow *d) { | ||
161 | iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType); | ||
162 | return (iWindow *) d; | ||
163 | } | ||
164 | |||
165 | iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) { | ||
166 | iAssert(type_Window(d) == main_WindowType || type_Window(d) == popup_WindowType); | ||
167 | return (const iWindow *) d; | ||
168 | } | ||
169 | |||
170 | iLocalDef iText *text_Window(const iAnyWindow *d) { | ||
171 | return constAs_Window(d)->text; | ||
172 | } | ||
173 | |||
174 | /*----------------------------------------------------------------------------------------------*/ | ||
175 | |||
176 | iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { | ||
177 | iAssert(type_Window(d) == main_WindowType); | ||
178 | return &d->base; | ||
179 | } | ||
180 | |||
181 | void setTitle_MainWindow (iMainWindow *, const iString *title); | ||
182 | void setSnap_MainWindow (iMainWindow *, int snapMode); | ||
183 | void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw); | ||
184 | void setKeyboardHeight_MainWindow (iMainWindow *, int height); | ||
185 | void setSplitMode_MainWindow (iMainWindow *, int splitMode); | ||
186 | void checkPendingSplit_MainWindow (iMainWindow *); | ||
187 | void swapRoots_MainWindow (iMainWindow *); | ||
188 | void showToolbars_MainWindow (iMainWindow *, iBool show); | ||
189 | void resize_MainWindow (iMainWindow *, int w, int h); | ||
190 | |||
191 | iBool processEvent_MainWindow (iMainWindow *, const SDL_Event *); | ||
192 | void draw_MainWindow (iMainWindow *); | ||
193 | void drawWhileResizing_MainWindow (iMainWindow *, int w, int h); /* workaround for SDL bug */ | ||
194 | |||
195 | int snap_MainWindow (const iMainWindow *); | ||
196 | iBool isFullscreen_Window (const iMainWindow *); | ||
197 | |||
198 | #if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) | ||
199 | SDL_HitTestResult hitTest_MainWindow(const iMainWindow *d, iInt2 pos); | ||
200 | #endif | ||
201 | |||
202 | iMainWindow * get_MainWindow (void); | ||
203 | |||
204 | iLocalDef iMainWindow *as_MainWindow(iAnyWindow *d) { | ||
205 | iAssert(type_Window(d) == main_WindowType); | ||
206 | return (iMainWindow *) d; | ||
207 | } | ||
208 | |||
209 | iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) { | ||
210 | iAssert(type_Window(d) == main_WindowType); | ||
211 | return (const iMainWindow *) d; | ||
212 | } | ||
213 | |||
214 | /*----------------------------------------------------------------------------------------------*/ | ||
215 | |||
216 | iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget); | ||
diff --git a/src/visited.c b/src/visited.c index 94cff492..e9f691c6 100644 --- a/src/visited.c +++ b/src/visited.c | |||
@@ -29,7 +29,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
29 | #include <the_Foundation/ptrarray.h> | 29 | #include <the_Foundation/ptrarray.h> |
30 | #include <the_Foundation/sortedarray.h> | 30 | #include <the_Foundation/sortedarray.h> |
31 | 31 | ||
32 | const int maxAge_Visited = 2 * 3600 * 24 * 30; /* two months */ | 32 | const int maxAge_Visited = 6 * 3600 * 24 * 30; /* six months */ |
33 | 33 | ||
34 | void init_VisitedUrl(iVisitedUrl *d) { | 34 | void init_VisitedUrl(iVisitedUrl *d) { |
35 | initCurrent_Time(&d->when); | 35 | initCurrent_Time(&d->when); |