diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/app.c | 48 | ||||
-rw-r--r-- | src/app.h | 1 | ||||
-rw-r--r-- | src/defs.h | 11 | ||||
-rw-r--r-- | src/gmdocument.c | 19 | ||||
-rw-r--r-- | src/ios.m | 20 | ||||
-rw-r--r-- | src/lang.c | 9 | ||||
-rw-r--r-- | src/ui/color.c | 2 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 34 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 267 | ||||
-rw-r--r-- | src/ui/labelwidget.c | 14 | ||||
-rw-r--r-- | src/ui/listwidget.c | 2 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 2 | ||||
-rw-r--r-- | src/ui/mobile.c | 795 | ||||
-rw-r--r-- | src/ui/mobile.h | 32 | ||||
-rw-r--r-- | src/ui/paint.c | 17 | ||||
-rw-r--r-- | src/ui/root.c | 132 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 2 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 97 | ||||
-rw-r--r-- | src/ui/text.c | 60 | ||||
-rw-r--r-- | src/ui/text.h | 7 | ||||
-rw-r--r-- | src/ui/touch.c | 54 | ||||
-rw-r--r-- | src/ui/touch.h | 1 | ||||
-rw-r--r-- | src/ui/util.c | 728 | ||||
-rw-r--r-- | src/ui/util.h | 7 | ||||
-rw-r--r-- | src/ui/widget.c | 133 | ||||
-rw-r--r-- | src/ui/widget.h | 8 | ||||
-rw-r--r-- | src/ui/window.c | 45 |
27 files changed, 1557 insertions, 990 deletions
@@ -146,6 +146,7 @@ iDeclareType(Ticker) | |||
146 | 146 | ||
147 | struct Impl_Ticker { | 147 | struct Impl_Ticker { |
148 | iAny *context; | 148 | iAny *context; |
149 | iRoot *root; | ||
149 | void (*callback)(iAny *); | 150 | void (*callback)(iAny *); |
150 | }; | 151 | }; |
151 | 152 | ||
@@ -411,15 +412,17 @@ static iBool loadState_App_(iApp *d) { | |||
411 | const uint8_t rootIndex = bits & 0xff; | 412 | const uint8_t rootIndex = bits & 0xff; |
412 | const uint8_t flags = bits >> 8; | 413 | const uint8_t flags = bits >> 8; |
413 | iRoot *root = d->window->roots[rootIndex]; | 414 | iRoot *root = d->window->roots[rootIndex]; |
414 | if (root && deviceType_App() != phone_AppDeviceType) { | 415 | if (root) { |
415 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); | 416 | iSidebarWidget *sidebar = findChild_Widget(root->widget, "sidebar"); |
416 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); | 417 | iSidebarWidget *sidebar2 = findChild_Widget(root->widget, "sidebar2"); |
417 | setWidth_SidebarWidget(sidebar, widths[0]); | ||
418 | setWidth_SidebarWidget(sidebar2, widths[1]); | ||
419 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); | 418 | postCommandf_Root(root, "sidebar.mode arg:%u", modes & 0xf); |
420 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); | 419 | postCommandf_Root(root, "sidebar2.mode arg:%u", modes >> 4); |
421 | if (flags & 1) postCommand_Root(root, "sidebar.toggle"); | 420 | if (deviceType_App() != phone_AppDeviceType) { |
422 | if (flags & 2) postCommand_Root(root, "sidebar2.toggle"); | 421 | setWidth_SidebarWidget(sidebar, widths[0]); |
422 | setWidth_SidebarWidget(sidebar2, widths[1]); | ||
423 | if (flags & 1) postCommand_Root(root, "sidebar.toggle noanim:1"); | ||
424 | if (flags & 2) postCommand_Root(root, "sidebar2.toggle noanim:1"); | ||
425 | } | ||
423 | } | 426 | } |
424 | } | 427 | } |
425 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { | 428 | else if (!memcmp(magic, magicTabDocument_App_, 4)) { |
@@ -776,6 +779,10 @@ static void init_App_(iApp *d, int argc, char **argv) { | |||
776 | iRelease(openCmds); | 779 | iRelease(openCmds); |
777 | } | 780 | } |
778 | fetchRemote_Bookmarks(d->bookmarks); | 781 | fetchRemote_Bookmarks(d->bookmarks); |
782 | if (deviceType_App() != desktop_AppDeviceType) { | ||
783 | /* HACK: Force a resize so widgets update their state. */ | ||
784 | resize_Window(d->window, -1, -1); | ||
785 | } | ||
779 | } | 786 | } |
780 | 787 | ||
781 | static void deinit_App(iApp *d) { | 788 | static void deinit_App(iApp *d) { |
@@ -1061,7 +1068,14 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1061 | } | 1068 | } |
1062 | continue; | 1069 | continue; |
1063 | } | 1070 | } |
1064 | else if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | 1071 | d->lastEventTime = SDL_GetTicks(); |
1072 | if (d->isIdling) { | ||
1073 | // printf("[App] ...woke up\n"); | ||
1074 | // fflush(stdout); | ||
1075 | } | ||
1076 | d->isIdling = iFalse; | ||
1077 | #endif | ||
1078 | if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) { | ||
1065 | printf("[App] rearrange\n"); | 1079 | printf("[App] rearrange\n"); |
1066 | resize_Window(d->window, -1, -1); | 1080 | resize_Window(d->window, -1, -1); |
1067 | iForIndices(i, d->window->roots) { | 1081 | iForIndices(i, d->window->roots) { |
@@ -1078,13 +1092,6 @@ void processEvents_App(enum iAppEventMode eventMode) { | |||
1078 | // postRefresh_App(); | 1092 | // postRefresh_App(); |
1079 | continue; | 1093 | continue; |
1080 | } | 1094 | } |
1081 | d->lastEventTime = SDL_GetTicks(); | ||
1082 | if (d->isIdling) { | ||
1083 | // printf("[App] ...woke up\n"); | ||
1084 | // fflush(stdout); | ||
1085 | } | ||
1086 | d->isIdling = iFalse; | ||
1087 | #endif | ||
1088 | gotEvents = iTrue; | 1095 | gotEvents = iTrue; |
1089 | /* Keyboard modifier mapping. */ | 1096 | /* Keyboard modifier mapping. */ |
1090 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { | 1097 | if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { |
@@ -1187,7 +1194,7 @@ static void runTickers_App_(iApp *d) { | |||
1187 | iConstForEach(Array, i, &pending->values) { | 1194 | iConstForEach(Array, i, &pending->values) { |
1188 | const iTicker *ticker = i.value; | 1195 | const iTicker *ticker = i.value; |
1189 | if (ticker->callback) { | 1196 | if (ticker->callback) { |
1190 | setCurrent_Root(findRoot_Window(d->window, ticker->context)); /* root might be NULL */ | 1197 | setCurrent_Root(ticker->root); /* root might be NULL */ |
1191 | ticker->callback(ticker->context); | 1198 | ticker->callback(ticker->context); |
1192 | } | 1199 | } |
1193 | } | 1200 | } |
@@ -1418,13 +1425,19 @@ iAny *findWidget_App(const char *id) { | |||
1418 | 1425 | ||
1419 | void addTicker_App(iTickerFunc ticker, iAny *context) { | 1426 | void addTicker_App(iTickerFunc ticker, iAny *context) { |
1420 | iApp *d = &app_; | 1427 | iApp *d = &app_; |
1421 | insert_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 1428 | insert_SortedArray(&d->tickers, &(iTicker){ context, get_Root(), ticker }); |
1429 | postRefresh_App(); | ||
1430 | } | ||
1431 | |||
1432 | void addTickerRoot_App(iTickerFunc ticker, iRoot *root, iAny *context) { | ||
1433 | iApp *d = &app_; | ||
1434 | insert_SortedArray(&d->tickers, &(iTicker){ context, root, ticker }); | ||
1422 | postRefresh_App(); | 1435 | postRefresh_App(); |
1423 | } | 1436 | } |
1424 | 1437 | ||
1425 | void removeTicker_App(iTickerFunc ticker, iAny *context) { | 1438 | void removeTicker_App(iTickerFunc ticker, iAny *context) { |
1426 | iApp *d = &app_; | 1439 | iApp *d = &app_; |
1427 | remove_SortedArray(&d->tickers, &(iTicker){ context, ticker }); | 1440 | remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); |
1428 | } | 1441 | } |
1429 | 1442 | ||
1430 | iMimeHooks *mimeHooks_App(void) { | 1443 | iMimeHooks *mimeHooks_App(void) { |
@@ -1507,6 +1520,7 @@ static void updateFontButton_(iLabelWidget *button, int font) { | |||
1507 | 1520 | ||
1508 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { | 1521 | static iBool handlePrefsCommands_(iWidget *d, const char *cmd) { |
1509 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { | 1522 | if (equal_Command(cmd, "prefs.dismiss") || equal_Command(cmd, "preferences")) { |
1523 | setupSheetTransition_Mobile(d, iFalse); | ||
1510 | setUiScale_Window(get_Window(), | 1524 | setUiScale_Window(get_Window(), |
1511 | toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); | 1525 | toFloat_String(text_InputWidget(findChild_Widget(d, "prefs.uiscale")))); |
1512 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) | 1526 | #if defined (LAGRANGE_ENABLE_DOWNLOAD_EDIT) |
@@ -2381,7 +2395,7 @@ iBool handleCommand_App(const char *cmd) { | |||
2381 | iCertImportWidget *imp = new_CertImportWidget(); | 2395 | iCertImportWidget *imp = new_CertImportWidget(); |
2382 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); | 2396 | setPageContent_CertImportWidget(imp, sourceContent_DocumentWidget(document_App())); |
2383 | addChild_Widget(get_Root()->widget, iClob(imp)); | 2397 | addChild_Widget(get_Root()->widget, iClob(imp)); |
2384 | finalizeSheet_Widget(as_Widget(imp)); | 2398 | finalizeSheet_Mobile(as_Widget(imp)); |
2385 | postRefresh_App(); | 2399 | postRefresh_App(); |
2386 | return iTrue; | 2400 | return iTrue; |
2387 | } | 2401 | } |
@@ -110,6 +110,7 @@ typedef void (*iTickerFunc)(iAny *); | |||
110 | 110 | ||
111 | iAny * findWidget_App (const char *id); | 111 | iAny * findWidget_App (const char *id); |
112 | void addTicker_App (iTickerFunc ticker, iAny *context); | 112 | void addTicker_App (iTickerFunc ticker, iAny *context); |
113 | void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); | ||
113 | void removeTicker_App (iTickerFunc ticker, iAny *context); | 114 | void removeTicker_App (iTickerFunc ticker, iAny *context); |
114 | void postRefresh_App (void); | 115 | void postRefresh_App (void); |
115 | void postImmediateRefresh_App(void); | 116 | void postImmediateRefresh_App(void); |
@@ -66,6 +66,7 @@ enum iFileVersion { | |||
66 | #define ballotCheck_Icon "\U0001f5f9" | 66 | #define ballotCheck_Icon "\U0001f5f9" |
67 | #define inbox_Icon "\U0001f4e5" | 67 | #define inbox_Icon "\U0001f4e5" |
68 | #define book_Icon "\U0001f56e" | 68 | #define book_Icon "\U0001f56e" |
69 | #define bookmark_Icon "\U0001f516" | ||
69 | #define folder_Icon "\U0001f4c1" | 70 | #define folder_Icon "\U0001f4c1" |
70 | #define openTab_Icon "\u2750" | 71 | #define openTab_Icon "\u2750" |
71 | #define openTabBg_Icon "\u2b1a" | 72 | #define openTabBg_Icon "\u2b1a" |
@@ -90,6 +91,16 @@ enum iFileVersion { | |||
90 | #define globe_Icon "\U0001f310" | 91 | #define globe_Icon "\U0001f310" |
91 | #define magnifyingGlass_Icon "\U0001f50d" | 92 | #define magnifyingGlass_Icon "\U0001f50d" |
92 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" | 93 | #define midEllipsis_Icon "\u00b7\u00b7\u00b7" |
94 | #define return_Icon "\u21a9" | ||
95 | |||
96 | #if defined (iPlatformApple) | ||
97 | # define shift_Icon "\u21e7" | ||
98 | # define shiftReturn_Icon shift_Icon return_Icon | ||
99 | #else | ||
100 | # define shift_Icon "Shift" | ||
101 | # define shiftReturn_Icon shift_Icon " " return_Icon | ||
102 | #endif | ||
103 | |||
93 | 104 | ||
94 | /* UI labels that depend on the platform */ | 105 | /* UI labels that depend on the platform */ |
95 | 106 | ||
diff --git a/src/gmdocument.c b/src/gmdocument.c index 3daa4714..67adb9cc 100644 --- a/src/gmdocument.c +++ b/src/gmdocument.c | |||
@@ -716,12 +716,25 @@ static void doLayout_GmDocument_(iGmDocument *d) { | |||
716 | meta->flags |= topLeft_GmPreMetaFlag; | 716 | meta->flags |= topLeft_GmPreMetaFlag; |
717 | } | 717 | } |
718 | } | 718 | } |
719 | /* Visited links are never bold. */ | 719 | float lineHeightReduction = 0.0f; |
720 | if (!isMono && run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { | 720 | if (!isMono) { |
721 | run.font = paragraph_FontId; | 721 | /* Upper-level headings are typeset a bit tighter. */ |
722 | if (type == heading1_GmLineType) { | ||
723 | lineHeightReduction = 0.10f; | ||
724 | } | ||
725 | else if (type == heading2_GmLineType) { | ||
726 | lineHeightReduction = 0.05f; | ||
727 | } | ||
728 | /* Visited links are never bold. */ | ||
729 | if (run.linkId && linkFlags_GmDocument(d, run.linkId) & visited_GmLinkFlag) { | ||
730 | run.font = paragraph_FontId; | ||
731 | } | ||
722 | } | 732 | } |
723 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ | 733 | iAssert(!isEmpty_Range(&runLine)); /* must have something at this point */ |
724 | while (!isEmpty_Range(&runLine)) { | 734 | while (!isEmpty_Range(&runLine)) { |
735 | if (~run.flags & startOfLine_GmRunFlag && lineHeightReduction > 0.0f) { | ||
736 | pos.y -= lineHeightReduction * lineHeight_Text(run.font); | ||
737 | } | ||
725 | run.bounds.pos = addX_I2(pos, indent * gap_Text); | 738 | run.bounds.pos = addX_I2(pos, indent * gap_Text); |
726 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; | 739 | const int wrapAvail = d->size.x - run.bounds.pos.x - rightMargin * gap_Text; |
727 | const int avail = isWordWrapped ? wrapAvail : 0; | 740 | const int avail = isWordWrapped ? wrapAvail : 0; |
@@ -196,7 +196,7 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls { | |||
196 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; | 196 | CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; |
197 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); | 197 | // NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); |
198 | iWindow *window = get_Window(); | 198 | iWindow *window = get_Window(); |
199 | const iInt2 rootSize = rootSize_Window(window); | 199 | const iInt2 rootSize = size_Root(window->roots[0]); |
200 | const int keyTop = keyboardFrame.origin.y * window->pixelRatio; | 200 | const int keyTop = keyboardFrame.origin.y * window->pixelRatio; |
201 | setKeyboardHeight_Window(window, rootSize.y - keyTop); | 201 | setKeyboardHeight_Window(window, rootSize.y - keyTop); |
202 | } | 202 | } |
@@ -302,6 +302,24 @@ iBool processEvent_iOS(const SDL_Event *ev) { | |||
302 | postCommandf_App("os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); | 302 | postCommandf_App("os.theme.changed dark:%d contrast:1", isSystemDarkMode_ ? 1 : 0); |
303 | } | 303 | } |
304 | } | 304 | } |
305 | else if (equal_Command(cmd, "theme.changed")) { | ||
306 | if (@available(iOS 13.0, *)) { | ||
307 | /* SDL doesn't expose this as a setting, so we'll rely on a hack. | ||
308 | Adding an SDL hint for this would be a cleaner solution than calling | ||
309 | a private method. */ | ||
310 | UIViewController *vc = viewController_(get_Window()); | ||
311 | SEL sel = NSSelectorFromString(@"setStatusStyle:"); /* custom method */ | ||
312 | if ([vc respondsToSelector:sel]) { | ||
313 | NSInvocation *call = [NSInvocation invocationWithMethodSignature: | ||
314 | [NSMethodSignature signatureWithObjCTypes:"v@:i"]]; | ||
315 | [call setSelector:sel]; | ||
316 | int style = isDark_ColorTheme(colorTheme_App()) ? | ||
317 | UIStatusBarStyleLightContent : UIStatusBarStyleDarkContent; | ||
318 | [call setArgument:&style atIndex:2]; | ||
319 | [call invokeWithTarget:vc]; | ||
320 | } | ||
321 | } | ||
322 | } | ||
305 | } | 323 | } |
306 | return iFalse; /* allow normal processing */ | 324 | return iFalse; /* allow normal processing */ |
307 | } | 325 | } |
@@ -44,6 +44,7 @@ int cmp_MsgStr_(const void *e1, const void *e2) { | |||
44 | enum iPluralType { | 44 | enum iPluralType { |
45 | none_PluralType, | 45 | none_PluralType, |
46 | notEqualToOne_PluralType, | 46 | notEqualToOne_PluralType, |
47 | polish_PluralType, | ||
47 | slavic_PluralType, | 48 | slavic_PluralType, |
48 | }; | 49 | }; |
49 | 50 | ||
@@ -58,6 +59,10 @@ static size_t pluralIndex_Lang_(const iLang *d, int n) { | |||
58 | switch (d->pluralType) { | 59 | switch (d->pluralType) { |
59 | case notEqualToOne_PluralType: | 60 | case notEqualToOne_PluralType: |
60 | return n != 1; | 61 | return n != 1; |
62 | case polish_PluralType: | ||
63 | return n == 1 ? 0 | ||
64 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 | ||
65 | : 2; | ||
61 | case slavic_PluralType: | 66 | case slavic_PluralType: |
62 | return n % 10 == 1 && n % 100 != 11 ? 0 | 67 | return n % 10 == 1 && n % 100 != 11 ? 0 |
63 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 | 68 | : n % 10 >= 2 && n % 10 <= 4 && (n % 100 < 10 || n % 100 >= 20) ? 1 |
@@ -81,6 +86,7 @@ static void load_Lang_(iLang *d, const char *id) { | |||
81 | : equal_CStr(id, "de") ? &blobDe_Embedded | 86 | : equal_CStr(id, "de") ? &blobDe_Embedded |
82 | : equal_CStr(id, "ia") ? &blobIa_Embedded | 87 | : equal_CStr(id, "ia") ? &blobIa_Embedded |
83 | : equal_CStr(id, "ie") ? &blobIe_Embedded | 88 | : equal_CStr(id, "ie") ? &blobIe_Embedded |
89 | : equal_CStr(id, "pl") ? &blobPl_Embedded | ||
84 | : equal_CStr(id, "sr") ? &blobSr_Embedded | 90 | : equal_CStr(id, "sr") ? &blobSr_Embedded |
85 | : equal_CStr(id, "tok") ? &blobTok_Embedded | 91 | : equal_CStr(id, "tok") ? &blobTok_Embedded |
86 | : equal_CStr(id, "zh_Hans") ? &blobZh_Hans_Embedded | 92 | : equal_CStr(id, "zh_Hans") ? &blobZh_Hans_Embedded |
@@ -89,6 +95,9 @@ static void load_Lang_(iLang *d, const char *id) { | |||
89 | if (data == &blobRu_Embedded || data == &blobSr_Embedded) { | 95 | if (data == &blobRu_Embedded || data == &blobSr_Embedded) { |
90 | d->pluralType = slavic_PluralType; | 96 | d->pluralType = slavic_PluralType; |
91 | } | 97 | } |
98 | else if (data == &blobPl_Embedded) { | ||
99 | d->pluralType = polish_PluralType; | ||
100 | } | ||
92 | else if (data == &blobZh_Hans_Embedded || data == &blobZh_Hant_Embedded || | 101 | else if (data == &blobZh_Hans_Embedded || data == &blobZh_Hant_Embedded || |
93 | data == &blobTok_Embedded) { | 102 | data == &blobTok_Embedded) { |
94 | d->pluralType = none_PluralType; | 103 | d->pluralType = none_PluralType; |
diff --git a/src/ui/color.c b/src/ui/color.c index 0a177a8a..6c51bc06 100644 --- a/src/ui/color.c +++ b/src/ui/color.c | |||
@@ -305,7 +305,7 @@ void setThemePalette_Color(enum iColorTheme theme) { | |||
305 | 0.4f)); | 305 | 0.4f)); |
306 | uiPalette_[uiMarked_ColorId ].a = 128; | 306 | uiPalette_[uiMarked_ColorId ].a = 128; |
307 | uiPalette_[uiMatching_ColorId].a = 128; | 307 | uiPalette_[uiMatching_ColorId].a = 128; |
308 | if (deviceType_App() == phone_AppDeviceType) { | 308 | if (deviceType_App() != desktop_AppDeviceType) { |
309 | copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId); | 309 | copy_(uiInputBackground_ColorId, uiBackgroundSidebar_ColorId); |
310 | copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId); | 310 | copy_(uiInputFrame_ColorId, uiBackgroundSidebar_ColorId); |
311 | copy_(uiInputFrameFocused_ColorId, uiBackgroundSidebar_ColorId); | 311 | copy_(uiInputFrameFocused_ColorId, uiBackgroundSidebar_ColorId); |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index d23e95a6..5dcdace6 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -1466,7 +1466,8 @@ static void scrollBegan_DocumentWidget_(iAnyObject *any, int offset, uint32_t du | |||
1466 | } | 1466 | } |
1467 | /* Show and hide toolbar on scroll. */ | 1467 | /* Show and hide toolbar on scroll. */ |
1468 | if (deviceType_App() == phone_AppDeviceType) { | 1468 | if (deviceType_App() == phone_AppDeviceType) { |
1469 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5) { | 1469 | const float normPos = normScrollPos_DocumentWidget_(d); |
1470 | if (prefs_App()->hideToolbarOnScroll && iAbs(offset) > 5 && normPos >= 0) { | ||
1470 | showToolbars_Root(as_Widget(d)->root, offset < 0); | 1471 | showToolbars_Root(as_Widget(d)->root, offset < 0); |
1471 | } | 1472 | } |
1472 | } | 1473 | } |
@@ -1606,6 +1607,7 @@ static void inputQueryValidator_(iInputWidget *input, void *context) { | |||
1606 | avail < 128 ? uiTextStrong_ColorId | 1607 | avail < 128 ? uiTextStrong_ColorId |
1607 | : uiTextDim_ColorId); | 1608 | : uiTextDim_ColorId); |
1608 | delete_String(url); | 1609 | delete_String(url); |
1610 | arrange_Widget(findChild_Widget(dlg, "dialogbuttons")); | ||
1609 | } | 1611 | } |
1610 | 1612 | ||
1611 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | 1613 | static void checkResponse_DocumentWidget_(iDocumentWidget *d) { |
@@ -1637,10 +1639,28 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1637 | : cstr_String(&resp->meta), | 1639 | : cstr_String(&resp->meta), |
1638 | uiTextCaution_ColorEscape "${dlg.input.send}", | 1640 | uiTextCaution_ColorEscape "${dlg.input.send}", |
1639 | format_CStr("!document.input.submit doc:%p", d)); | 1641 | format_CStr("!document.input.submit doc:%p", d)); |
1640 | setId_Widget(addChildPosFlags_Widget(findChild_Widget(dlg, "dialogbuttons"), | 1642 | iWidget *buttons = findChild_Widget(dlg, "dialogbuttons"); |
1643 | iLabelWidget *lineBreak; | ||
1644 | /* The line break and URL length counters are positioned differently on mobile. */ | ||
1645 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1646 | lineBreak = new_LabelWidget("${dlg.input.linebreak}" | ||
1647 | uiTextAction_ColorEscape | ||
1648 | " " shiftReturn_Icon, | ||
1649 | NULL); | ||
1650 | insertChildAfter_Widget(buttons, iClob(lineBreak), 0); | ||
1651 | } | ||
1652 | else { | ||
1653 | lineBreak = new_LabelWidget("${dlg.input.linebreak}", "text.insert arg:10"); | ||
1654 | } | ||
1655 | setFlags_Widget(as_Widget(lineBreak), frameless_WidgetFlag, iTrue); | ||
1656 | setTextColor_LabelWidget(lineBreak, uiTextDim_ColorId); | ||
1657 | setId_Widget(addChildPosFlags_Widget(buttons, | ||
1641 | iClob(new_LabelWidget("", NULL)), | 1658 | iClob(new_LabelWidget("", NULL)), |
1642 | front_WidgetAddPos, frameless_WidgetFlag), | 1659 | front_WidgetAddPos, frameless_WidgetFlag), |
1643 | "valueinput.counter"); | 1660 | "valueinput.counter"); |
1661 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1662 | addChildPos_Widget(buttons, iClob(lineBreak), front_WidgetAddPos); | ||
1663 | } | ||
1644 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); | 1664 | setValidator_InputWidget(findChild_Widget(dlg, "input"), inputQueryValidator_, d); |
1645 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), | 1665 | setSensitiveContent_InputWidget(findChild_Widget(dlg, "input"), |
1646 | statusCode == sensitiveInput_GmStatusCode); | 1666 | statusCode == sensitiveInput_GmStatusCode); |
@@ -1653,7 +1673,7 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1653 | break; | 1673 | break; |
1654 | } | 1674 | } |
1655 | case categorySuccess_GmStatusCode: | 1675 | case categorySuccess_GmStatusCode: |
1656 | reset_SmoothScroll(&d->scrollY); | 1676 | //reset_SmoothScroll(&d->scrollY); |
1657 | iRelease(d->doc); /* new content incoming */ | 1677 | iRelease(d->doc); /* new content incoming */ |
1658 | d->doc = new_GmDocument(); | 1678 | d->doc = new_GmDocument(); |
1659 | delete_Gempub(d->sourceGempub); | 1679 | delete_Gempub(d->sourceGempub); |
@@ -2310,7 +2330,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2310 | } | 2330 | } |
2311 | updateFetchProgress_DocumentWidget_(d); | 2331 | updateFetchProgress_DocumentWidget_(d); |
2312 | checkResponse_DocumentWidget_(d); | 2332 | checkResponse_DocumentWidget_(d); |
2313 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | 2333 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { |
2334 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | ||
2335 | } | ||
2314 | d->state = ready_RequestState; | 2336 | d->state = ready_RequestState; |
2315 | postProcessRequestContent_DocumentWidget_(d, iFalse); | 2337 | postProcessRequestContent_DocumentWidget_(d, iFalse); |
2316 | /* The response may be cached. */ | 2338 | /* The response may be cached. */ |
@@ -3081,7 +3103,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3081 | pushBackN_Array(&items, | 3103 | pushBackN_Array(&items, |
3082 | (iMenuItem[]){ { "---", 0, 0, NULL }, | 3104 | (iMenuItem[]){ { "---", 0, 0, NULL }, |
3083 | { "${link.copy}", 0, 0, "document.copylink" }, | 3105 | { "${link.copy}", 0, 0, "document.copylink" }, |
3084 | { pin_Icon " ${link.bookmark}", | 3106 | { bookmark_Icon " ${link.bookmark}", |
3085 | 0, | 3107 | 0, |
3086 | 0, | 3108 | 0, |
3087 | format_CStr("!bookmark.add title:%s url:%s", | 3109 | format_CStr("!bookmark.add title:%s url:%s", |
@@ -3126,7 +3148,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3126 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | 3148 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, |
3127 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | 3149 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, |
3128 | { "---", 0, 0, NULL }, | 3150 | { "---", 0, 0, NULL }, |
3129 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | 3151 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, |
3130 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | 3152 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, |
3131 | { "---", 0, 0, NULL }, | 3153 | { "---", 0, 0, NULL }, |
3132 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | 3154 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 6719fb40..784fabdd 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -42,8 +42,12 @@ static const size_t maxUndo_InputWidget_ = 64; | |||
42 | 42 | ||
43 | static void enableEditorKeysInMenus_(iBool enable) { | 43 | static void enableEditorKeysInMenus_(iBool enable) { |
44 | #if defined (iPlatformAppleDesktop) | 44 | #if defined (iPlatformAppleDesktop) |
45 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); | 45 | enableMenuItemsByKey_MacOS(SDLK_LEFT, KMOD_PRIMARY, enable); |
46 | enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); | 46 | enableMenuItemsByKey_MacOS(SDLK_RIGHT, KMOD_PRIMARY, enable); |
47 | enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY, enable); | ||
48 | enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY, enable); | ||
49 | enableMenuItemsByKey_MacOS(SDLK_UP, KMOD_PRIMARY | KMOD_SHIFT, enable); | ||
50 | enableMenuItemsByKey_MacOS(SDLK_DOWN, KMOD_PRIMARY | KMOD_SHIFT, enable); | ||
47 | #else | 51 | #else |
48 | iUnused(enable); | 52 | iUnused(enable); |
49 | #endif | 53 | #endif |
@@ -84,6 +88,7 @@ iDeclareType(InputLine) | |||
84 | 88 | ||
85 | struct Impl_InputLine { | 89 | struct Impl_InputLine { |
86 | size_t offset; /* character position from the beginning */ | 90 | size_t offset; /* character position from the beginning */ |
91 | size_t len; /* length as characters */ | ||
87 | iString text; /* UTF-8 */ | 92 | iString text; /* UTF-8 */ |
88 | }; | 93 | }; |
89 | 94 | ||
@@ -109,6 +114,7 @@ struct Impl_InputWidget { | |||
109 | iArray text; /* iChar[] */ | 114 | iArray text; /* iChar[] */ |
110 | iArray oldText; /* iChar[] */ | 115 | iArray oldText; /* iChar[] */ |
111 | iArray lines; | 116 | iArray lines; |
117 | int lastUpdateWidth; | ||
112 | iString hint; | 118 | iString hint; |
113 | iString srcHint; | 119 | iString srcHint; |
114 | int leftPadding; | 120 | int leftPadding; |
@@ -138,7 +144,26 @@ static void clearUndo_InputWidget_(iInputWidget *d) { | |||
138 | clear_Array(&d->undoStack); | 144 | clear_Array(&d->undoStack); |
139 | } | 145 | } |
140 | 146 | ||
147 | iLocalDef iInt2 padding_(void) { | ||
148 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
149 | } | ||
150 | |||
151 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | ||
152 | const iWidget *w = constAs_Widget(d); | ||
153 | // const iRect widgetBounds = bounds_Widget(w); | ||
154 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
155 | addX_I2(padding_(), d->leftPadding), | ||
156 | neg_I2(addX_I2(padding_(), d->rightPadding))); | ||
157 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
158 | bounds.pos.y += padding_().y / 2; | ||
159 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | ||
160 | bounds.pos.y += gap_UI; | ||
161 | } | ||
162 | return bounds; | ||
163 | } | ||
164 | |||
141 | static void updateCursorLine_InputWidget_(iInputWidget *d) { | 165 | static void updateCursorLine_InputWidget_(iInputWidget *d) { |
166 | iWidget *w = as_Widget(d); | ||
142 | d->cursorLine = 0; | 167 | d->cursorLine = 0; |
143 | iConstForEach(Array, i, &d->lines) { | 168 | iConstForEach(Array, i, &d->lines) { |
144 | const iInputLine *line = i.value; | 169 | const iInputLine *line = i.value; |
@@ -147,6 +172,20 @@ static void updateCursorLine_InputWidget_(iInputWidget *d) { | |||
147 | } | 172 | } |
148 | d->cursorLine = index_ArrayConstIterator(&i); | 173 | d->cursorLine = index_ArrayConstIterator(&i); |
149 | } | 174 | } |
175 | /* May need to scroll to keep the cursor visible. */ | ||
176 | iWidget *flow = findOverflowScrollable_Widget(w); | ||
177 | if (flow) { | ||
178 | const iRect rootRect = { rect_Root(w->root).pos, visibleSize_Root(w->root) }; | ||
179 | int yCursor = contentBounds_InputWidget_(d).pos.y + | ||
180 | lineHeight_Text(d->font) * (int) d->cursorLine; | ||
181 | const int margin = lineHeight_Text(d->font) * 3; | ||
182 | if (yCursor < top_Rect(rootRect) + margin) { | ||
183 | scrollOverflow_Widget(flow, top_Rect(rootRect) + margin - yCursor); | ||
184 | } | ||
185 | else if (yCursor > bottom_Rect(rootRect) - margin * 3 / 2) { | ||
186 | scrollOverflow_Widget(flow, bottom_Rect(rootRect) - margin * 3 / 2 - yCursor); | ||
187 | } | ||
188 | } | ||
150 | } | 189 | } |
151 | 190 | ||
152 | static void showCursor_InputWidget_(iInputWidget *d) { | 191 | static void showCursor_InputWidget_(iInputWidget *d) { |
@@ -161,21 +200,6 @@ static void invalidateBuffered_InputWidget_(iInputWidget *d) { | |||
161 | } | 200 | } |
162 | } | 201 | } |
163 | 202 | ||
164 | iLocalDef iInt2 padding_(void) { | ||
165 | return init_I2(gap_UI / 2, gap_UI / 2); | ||
166 | } | ||
167 | |||
168 | static iRect contentBounds_InputWidget_(const iInputWidget *d) { | ||
169 | const iWidget *w = constAs_Widget(d); | ||
170 | const iRect widgetBounds = bounds_Widget(w); | ||
171 | iRect bounds = adjusted_Rect(bounds_Widget(w), | ||
172 | addX_I2(padding_(), d->leftPadding), | ||
173 | neg_I2(addX_I2(padding_(), d->rightPadding))); | ||
174 | shrink_Rect(&bounds, init_I2(gap_UI * (flags_Widget(w) & tight_WidgetFlag ? 1 : 2), 0)); | ||
175 | bounds.pos.y += padding_().y / 2; | ||
176 | return bounds; | ||
177 | } | ||
178 | |||
179 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { | 203 | static void updateSizeForFixedLength_InputWidget_(iInputWidget *d) { |
180 | if (d->maxLen) { | 204 | if (d->maxLen) { |
181 | /* Set a fixed size based on maximum possible width of the text. */ | 205 | /* Set a fixed size based on maximum possible width of the text. */ |
@@ -219,6 +243,7 @@ static void clearLines_InputWidget_(iInputWidget *d) { | |||
219 | } | 243 | } |
220 | 244 | ||
221 | static void updateLines_InputWidget_(iInputWidget *d) { | 245 | static void updateLines_InputWidget_(iInputWidget *d) { |
246 | d->lastUpdateWidth = d->widget.rect.size.x; | ||
222 | clearLines_InputWidget_(d); | 247 | clearLines_InputWidget_(d); |
223 | if (d->maxLen) { | 248 | if (d->maxLen) { |
224 | /* Everything on a single line. */ | 249 | /* Everything on a single line. */ |
@@ -226,6 +251,7 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
226 | init_InputLine(&line); | 251 | init_InputLine(&line); |
227 | iString *u8 = visText_InputWidget_(d); | 252 | iString *u8 = visText_InputWidget_(d); |
228 | set_String(&line.text, u8); | 253 | set_String(&line.text, u8); |
254 | line.len = length_String(u8); | ||
229 | delete_String(u8); | 255 | delete_String(u8); |
230 | pushBack_Array(&d->lines, &line); | 256 | pushBack_Array(&d->lines, &line); |
231 | updateCursorLine_InputWidget_(d); | 257 | updateCursorLine_InputWidget_(d); |
@@ -249,14 +275,16 @@ static void updateLines_InputWidget_(iInputWidget *d) { | |||
249 | init_InputLine(&line); | 275 | init_InputLine(&line); |
250 | setRange_String(&line.text, part); | 276 | setRange_String(&line.text, part); |
251 | line.offset = charPos; | 277 | line.offset = charPos; |
278 | line.len = length_String(&line.text); | ||
252 | pushBack_Array(&d->lines, &line); | 279 | pushBack_Array(&d->lines, &line); |
253 | charPos += length_String(&line.text); | 280 | charPos += line.len; |
254 | content.start = endPos; | 281 | content.start = endPos; |
255 | } | 282 | } |
256 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { | 283 | if (isEmpty_Array(&d->lines) || endsWith_String(u8, "\n")) { |
257 | /* Always at least one empty line. */ | 284 | /* Always at least one empty line. */ |
258 | iInputLine line; | 285 | iInputLine line; |
259 | init_InputLine(&line); | 286 | init_InputLine(&line); |
287 | line.offset = charPos; | ||
260 | pushBack_Array(&d->lines, &line); | 288 | pushBack_Array(&d->lines, &line); |
261 | } | 289 | } |
262 | else { | 290 | else { |
@@ -278,7 +306,7 @@ static void updateMetrics_InputWidget_(iInputWidget *d) { | |||
278 | iWidget *w = as_Widget(d); | 306 | iWidget *w = as_Widget(d); |
279 | updateSizeForFixedLength_InputWidget_(d); | 307 | updateSizeForFixedLength_InputWidget_(d); |
280 | /* Caller must arrange the width, but the height is fixed. */ | 308 | /* Caller must arrange the width, but the height is fixed. */ |
281 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3 * padding_().y; /* TODO: Why 3x? */ | 309 | w->rect.size.y = contentHeight_InputWidget_(d, iTrue) + 3.0f * padding_().y; /* TODO: Why 3x? */ |
282 | if (flags_Widget(w) & extraPadding_WidgetFlag) { | 310 | if (flags_Widget(w) & extraPadding_WidgetFlag) { |
283 | w->rect.size.y += 2 * gap_UI; | 311 | w->rect.size.y += 2 * gap_UI; |
284 | } | 312 | } |
@@ -316,6 +344,7 @@ void init_InputWidget(iInputWidget *d, size_t maxLen) { | |||
316 | d->cursor = 0; | 344 | d->cursor = 0; |
317 | d->lastCursor = 0; | 345 | d->lastCursor = 0; |
318 | d->cursorLine = 0; | 346 | d->cursorLine = 0; |
347 | d->lastUpdateWidth = 0; | ||
319 | d->verticalMoveX = -1; /* TODO: Use this. */ | 348 | d->verticalMoveX = -1; /* TODO: Use this. */ |
320 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; | 349 | d->inFlags = eatEscape_InputWidgetFlag | enterKeyEnabled_InputWidgetFlag; |
321 | iZap(d->mark); | 350 | iZap(d->mark); |
@@ -455,31 +484,46 @@ void setContentPadding_InputWidget(iInputWidget *d, int left, int right) { | |||
455 | refresh_Widget(d); | 484 | refresh_Widget(d); |
456 | } | 485 | } |
457 | 486 | ||
487 | static iBool isHintVisible_InputWidget_(const iInputWidget *d) { | ||
488 | return !isEmpty_String(&d->hint) && size_Array(&d->lines) == 1 && | ||
489 | isEmpty_String(&line_InputWidget_(d, 0)->text); | ||
490 | } | ||
491 | |||
458 | static void updateBuffered_InputWidget_(iInputWidget *d) { | 492 | static void updateBuffered_InputWidget_(iInputWidget *d) { |
459 | invalidateBuffered_InputWidget_(d); | 493 | invalidateBuffered_InputWidget_(d); |
460 | iString *bufText = NULL; | 494 | if (isHintVisible_InputWidget_(d)) { |
495 | d->buffered = new_TextBuf(d->font, uiAnnotation_ColorId, cstr_String(&d->hint)); | ||
496 | } | ||
497 | else { | ||
498 | iString *bufText = NULL; | ||
461 | #if 0 | 499 | #if 0 |
462 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { | 500 | if (d->inFlags & isUrl_InputWidgetFlag && as_Widget(d)->root == win->keyRoot) { |
463 | /* TODO: Move this omitting to `updateLines_`? */ | 501 | /* TODO: Move this omitting to `updateLines_`? */ |
464 | /* Highlight the host name. */ | 502 | /* Highlight the host name. */ |
465 | iUrl parts; | 503 | iUrl parts; |
466 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); | 504 | const iString *text = collect_String(utf32toUtf8_InputWidget_(d)); |
467 | init_Url(&parts, text); | 505 | init_Url(&parts, text); |
468 | if (!isEmpty_Range(&parts.host)) { | 506 | if (!isEmpty_Range(&parts.host)) { |
469 | bufText = new_String(); | 507 | bufText = new_String(); |
470 | appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start }); | 508 | appendRange_String(bufText, (iRangecc){ constBegin_String(text), parts.host.start }); |
471 | appendCStr_String(bufText, uiTextStrong_ColorEscape); | 509 | appendCStr_String(bufText, uiTextStrong_ColorEscape); |
472 | appendRange_String(bufText, parts.host); | 510 | appendRange_String(bufText, parts.host); |
473 | appendCStr_String(bufText, restore_ColorEscape); | 511 | appendCStr_String(bufText, restore_ColorEscape); |
474 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); | 512 | appendRange_String(bufText, (iRangecc){ parts.host.end, constEnd_String(text) }); |
513 | } | ||
475 | } | 514 | } |
476 | } | ||
477 | #endif | 515 | #endif |
478 | if (!bufText) { | 516 | if (!bufText) { |
479 | bufText = visText_InputWidget_(d); | 517 | bufText = visText_InputWidget_(d); |
518 | } | ||
519 | const int maxWidth = contentBounds_InputWidget_(d).size.x; | ||
520 | const int fg = uiInputText_ColorId; | ||
521 | const char *text = cstr_String(bufText); | ||
522 | d->buffered = | ||
523 | (d->inFlags & isUrl_InputWidgetFlag ? newBound_TextBuf(d->font, fg, maxWidth, text) | ||
524 | : newWrap_TextBuf (d->font, fg, maxWidth, text)); | ||
525 | delete_String(bufText); | ||
480 | } | 526 | } |
481 | d->buffered = new_TextBuf(d->font, uiInputText_ColorId, cstr_String(bufText)); | ||
482 | delete_String(bufText); | ||
483 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; | 527 | d->inFlags &= ~needUpdateBuffer_InputWidgetFlag; |
484 | } | 528 | } |
485 | 529 | ||
@@ -563,7 +607,11 @@ void begin_InputWidget(iInputWidget *d) { | |||
563 | } | 607 | } |
564 | updateCursorLine_InputWidget_(d); | 608 | updateCursorLine_InputWidget_(d); |
565 | SDL_StartTextInput(); | 609 | SDL_StartTextInput(); |
566 | setFlags_Widget(w, selected_WidgetFlag | keepOnTop_WidgetFlag, iTrue); | 610 | setFlags_Widget(w, selected_WidgetFlag, iTrue); |
611 | if (d->maxLayoutLines != iInvalidSize) { | ||
612 | /* This will extend beyond the arranged region. */ | ||
613 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | ||
614 | } | ||
567 | showCursor_InputWidget_(d); | 615 | showCursor_InputWidget_(d); |
568 | refresh_Widget(w); | 616 | refresh_Widget(w); |
569 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); | 617 | d->timer = SDL_AddTimer(refreshInterval_InputWidget_, cursorTimer_, d); |
@@ -657,6 +705,10 @@ void setCursor_InputWidget(iInputWidget *d, size_t pos) { | |||
657 | showCursor_InputWidget_(d); | 705 | showCursor_InputWidget_(d); |
658 | } | 706 | } |
659 | 707 | ||
708 | iLocalDef iBool isLastLine_InputWidget_(const iInputWidget *d, const iInputLine *line) { | ||
709 | return (const void *) line == constAt_Array(&d->lines, size_Array(&d->lines) - 1); | ||
710 | } | ||
711 | |||
660 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { | 712 | static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const iInputLine *line) { |
661 | size_t index = line->offset; | 713 | size_t index = line->offset; |
662 | if (x <= 0) { | 714 | if (x <= 0) { |
@@ -665,7 +717,7 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
665 | const char *endPos; | 717 | const char *endPos; |
666 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); | 718 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), x, &endPos); |
667 | if (endPos == constEnd_String(&line->text)) { | 719 | if (endPos == constEnd_String(&line->text)) { |
668 | index += length_String(&line->text); | 720 | index += line->len; |
669 | } | 721 | } |
670 | else { | 722 | else { |
671 | /* Need to know the actual character index. */ | 723 | /* Need to know the actual character index. */ |
@@ -675,6 +727,9 @@ static size_t indexForRelativeX_InputWidget_(const iInputWidget *d, int x, const | |||
675 | index++; | 727 | index++; |
676 | } | 728 | } |
677 | } | 729 | } |
730 | if (!isLastLine_InputWidget_(d, line) && index == line->offset + line->len) { | ||
731 | index = iMax(index - 1, line->offset); | ||
732 | } | ||
678 | return index; | 733 | return index; |
679 | } | 734 | } |
680 | 735 | ||
@@ -690,11 +745,6 @@ static iBool moveCursorByLine_InputWidget_(iInputWidget *d, int dir) { | |||
690 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); | 745 | newCursor = indexForRelativeX_InputWidget_(d, xPos, ++line); |
691 | } | 746 | } |
692 | if (newCursor != iInvalidPos) { | 747 | if (newCursor != iInvalidPos) { |
693 | /* Clamp it to the current line. */ | ||
694 | newCursor = iMin(newCursor, line->offset + length_String(&line->text) - | ||
695 | /* last line is allowed to go to the cursorMax */ | ||
696 | ((const void *) line < constAt_Array(&d->lines, numLines - 1) ? 1 : 0)); | ||
697 | newCursor = iMax(newCursor, line->offset); | ||
698 | setCursor_InputWidget(d, newCursor); | 748 | setCursor_InputWidget(d, newCursor); |
699 | return iTrue; | 749 | return iTrue; |
700 | } | 750 | } |
@@ -707,6 +757,7 @@ void setSensitiveContent_InputWidget(iInputWidget *d, iBool isSensitive) { | |||
707 | 757 | ||
708 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { | 758 | void setUrlContent_InputWidget(iInputWidget *d, iBool isUrl) { |
709 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); | 759 | iChangeFlags(d->inFlags, isUrl_InputWidgetFlag, isUrl); |
760 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
710 | } | 761 | } |
711 | 762 | ||
712 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { | 763 | void setSelectAllOnFocus_InputWidget(iInputWidget *d, iBool selectAllOnFocus) { |
@@ -826,7 +877,8 @@ static iInt2 textOrigin_InputWidget_(const iInputWidget *d) { //}, const char *v | |||
826 | 877 | ||
827 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { | 878 | static size_t coordIndex_InputWidget_(const iInputWidget *d, iInt2 coord) { |
828 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); | 879 | const iInt2 pos = sub_I2(coord, contentBounds_InputWidget_(d).pos); |
829 | const size_t lineNumber = iMin(pos.y / lineHeight_Text(d->font), (int) size_Array(&d->lines) - 1); | 880 | const size_t lineNumber = iMin(iMax(0, pos.y) / lineHeight_Text(d->font), |
881 | (int) size_Array(&d->lines) - 1); | ||
830 | const iInputLine *line = line_InputWidget_(d, lineNumber); | 882 | const iInputLine *line = line_InputWidget_(d, lineNumber); |
831 | const char *endPos; | 883 | const char *endPos; |
832 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); | 884 | tryAdvanceNoWrap_Text(d->font, range_String(&line->text), pos.x, &endPos); |
@@ -880,7 +932,7 @@ static iRanges lineRange_InputWidget_(const iInputWidget *d) { | |||
880 | return (iRanges){ 0, 0 }; | 932 | return (iRanges){ 0, 0 }; |
881 | } | 933 | } |
882 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); | 934 | const iInputLine *line = line_InputWidget_(d, d->cursorLine); |
883 | return (iRanges){ line->offset, line->offset + length_String(&line->text) }; | 935 | return (iRanges){ line->offset, line->offset + line->len }; |
884 | } | 936 | } |
885 | 937 | ||
886 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { | 938 | static void extendRange_InputWidget_(iInputWidget *d, size_t *pos, int dir) { |
@@ -907,6 +959,9 @@ static iRect bounds_InputWidget_(const iInputWidget *d) { | |||
907 | return bounds; | 959 | return bounds; |
908 | } | 960 | } |
909 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; | 961 | bounds.size.y = contentHeight_InputWidget_(d, iFalse) + 3 * padding_().y; |
962 | if (w->flags & extraPadding_WidgetFlag) { | ||
963 | bounds.size.y += 2 * gap_UI; | ||
964 | } | ||
910 | return bounds; | 965 | return bounds; |
911 | } | 966 | } |
912 | 967 | ||
@@ -958,11 +1013,19 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
958 | } | 1013 | } |
959 | return iFalse; | 1014 | return iFalse; |
960 | } | 1015 | } |
1016 | else if (isCommand_UserEvent(ev, "text.insert")) { | ||
1017 | pushUndo_InputWidget_(d); | ||
1018 | deleteMarked_InputWidget_(d); | ||
1019 | insertChar_InputWidget_(d, arg_Command(command_UserEvent(ev))); | ||
1020 | contentsWereChanged_InputWidget_(d); | ||
1021 | return iTrue; | ||
1022 | } | ||
961 | else if (isMetricsChange_UserEvent(ev)) { | 1023 | else if (isMetricsChange_UserEvent(ev)) { |
962 | updateMetrics_InputWidget_(d); | 1024 | updateMetrics_InputWidget_(d); |
963 | updateLinesAndResize_InputWidget_(d); | 1025 | updateLinesAndResize_InputWidget_(d); |
964 | } | 1026 | } |
965 | else if (isResize_UserEvent(ev)) { | 1027 | else if (isResize_UserEvent(ev) || d->lastUpdateWidth != w->rect.size.x) { |
1028 | d->inFlags |= needUpdateBuffer_InputWidgetFlag; | ||
966 | if (d->inFlags & isUrl_InputWidgetFlag) { | 1029 | if (d->inFlags & isUrl_InputWidgetFlag) { |
967 | /* Restore/omit the default scheme if necessary. */ | 1030 | /* Restore/omit the default scheme if necessary. */ |
968 | setText_InputWidget(d, text_InputWidget(d)); | 1031 | setText_InputWidget(d, text_InputWidget(d)); |
@@ -1077,6 +1140,17 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1077 | return iTrue; | 1140 | return iTrue; |
1078 | } | 1141 | } |
1079 | } | 1142 | } |
1143 | #if defined (iPlatformApple) | ||
1144 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { | ||
1145 | switch (key) { | ||
1146 | case SDLK_UP: | ||
1147 | case SDLK_DOWN: | ||
1148 | setCursor_InputWidget(d, key == SDLK_UP ? 0 : curMax); | ||
1149 | refresh_Widget(d); | ||
1150 | return iTrue; | ||
1151 | } | ||
1152 | } | ||
1153 | #endif | ||
1080 | d->lastCursor = d->cursor; | 1154 | d->lastCursor = d->cursor; |
1081 | switch (key) { | 1155 | switch (key) { |
1082 | case SDLK_INSERT: | 1156 | case SDLK_INSERT: |
@@ -1086,7 +1160,8 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1086 | return iTrue; | 1160 | return iTrue; |
1087 | case SDLK_RETURN: | 1161 | case SDLK_RETURN: |
1088 | case SDLK_KP_ENTER: | 1162 | case SDLK_KP_ENTER: |
1089 | if (mods == KMOD_SHIFT) { | 1163 | if (mods == KMOD_SHIFT || (~d->inFlags & isUrl_InputWidgetFlag && |
1164 | deviceType_App() != desktop_AppDeviceType)) { | ||
1090 | pushUndo_InputWidget_(d); | 1165 | pushUndo_InputWidget_(d); |
1091 | deleteMarked_InputWidget_(d); | 1166 | deleteMarked_InputWidget_(d); |
1092 | insertChar_InputWidget_(d, '\n'); | 1167 | insertChar_InputWidget_(d, '\n'); |
@@ -1170,7 +1245,12 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1170 | break; | 1245 | break; |
1171 | case SDLK_HOME: | 1246 | case SDLK_HOME: |
1172 | case SDLK_END: | 1247 | case SDLK_END: |
1173 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); | 1248 | if (mods == KMOD_PRIMARY || mods == (KMOD_PRIMARY | KMOD_SHIFT)) { |
1249 | setCursor_InputWidget(d, key == SDLK_HOME ? 0 : curMax); | ||
1250 | } | ||
1251 | else { | ||
1252 | setCursor_InputWidget(d, key == SDLK_HOME ? lineFirst : lineLast); | ||
1253 | } | ||
1174 | refresh_Widget(w); | 1254 | refresh_Widget(w); |
1175 | return iTrue; | 1255 | return iTrue; |
1176 | case SDLK_a: | 1256 | case SDLK_a: |
@@ -1216,19 +1296,20 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1216 | /* Allow focus switching. */ | 1296 | /* Allow focus switching. */ |
1217 | return processEvent_Widget(as_Widget(d), ev); | 1297 | return processEvent_Widget(as_Widget(d), ev); |
1218 | case SDLK_UP: | 1298 | case SDLK_UP: |
1219 | if (moveCursorByLine_InputWidget_(d, -1)) { | ||
1220 | refresh_Widget(d); | ||
1221 | return iTrue; | ||
1222 | } | ||
1223 | /* For moving to lookup from url entry. */ | ||
1224 | return processEvent_Widget(as_Widget(d), ev); | ||
1225 | case SDLK_DOWN: | 1299 | case SDLK_DOWN: |
1226 | if (moveCursorByLine_InputWidget_(d, +1)) { | 1300 | if (moveCursorByLine_InputWidget_(d, key == SDLK_UP ? -1 : +1)) { |
1227 | refresh_Widget(d); | 1301 | refresh_Widget(d); |
1228 | return iTrue; | 1302 | return iTrue; |
1229 | } | 1303 | } |
1230 | /* For moving to lookup from url entry. */ | 1304 | /* For moving to lookup from url entry. */ |
1231 | return processEvent_Widget(as_Widget(d), ev); | 1305 | return processEvent_Widget(as_Widget(d), ev); |
1306 | case SDLK_PAGEUP: | ||
1307 | case SDLK_PAGEDOWN: | ||
1308 | for (int count = 0; count < 5; count++) { | ||
1309 | moveCursorByLine_InputWidget_(d, key == SDLK_PAGEUP ? -1 : +1); | ||
1310 | } | ||
1311 | refresh_Widget(d); | ||
1312 | return iTrue; | ||
1232 | } | 1313 | } |
1233 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { | 1314 | if (mods & (KMOD_PRIMARY | KMOD_SECONDARY)) { |
1234 | return iFalse; | 1315 | return iFalse; |
@@ -1248,6 +1329,7 @@ static iBool processEvent_InputWidget_(iInputWidget *d, const SDL_Event *ev) { | |||
1248 | return processEvent_Widget(w, ev); | 1329 | return processEvent_Widget(w, ev); |
1249 | } | 1330 | } |
1250 | 1331 | ||
1332 | #if 0 | ||
1251 | static iBool isWhite_(const iString *str) { | 1333 | static iBool isWhite_(const iString *str) { |
1252 | iConstForEach(String, i, str) { | 1334 | iConstForEach(String, i, str) { |
1253 | if (!isSpace_Char(i.value)) { | 1335 | if (!isSpace_Char(i.value)) { |
@@ -1256,11 +1338,12 @@ static iBool isWhite_(const iString *str) { | |||
1256 | } | 1338 | } |
1257 | return iTrue; | 1339 | return iTrue; |
1258 | } | 1340 | } |
1341 | #endif | ||
1259 | 1342 | ||
1260 | static void draw_InputWidget_(const iInputWidget *d) { | 1343 | static void draw_InputWidget_(const iInputWidget *d) { |
1261 | const iWidget *w = constAs_Widget(d); | 1344 | const iWidget *w = constAs_Widget(d); |
1262 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); | 1345 | iRect bounds = adjusted_Rect(bounds_InputWidget_(d), padding_(), neg_I2(padding_())); |
1263 | iBool isHint = iFalse; | 1346 | iBool isHint = isHintVisible_InputWidget_(d); |
1264 | const iBool isFocused = isFocused_Widget(w); | 1347 | const iBool isFocused = isFocused_Widget(w); |
1265 | const iBool isHover = isHover_Widget(w) && | 1348 | const iBool isHover = isHover_Widget(w) && |
1266 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); | 1349 | contains_InputWidget_(d, mouseCoord_Window(get_Window())); |
@@ -1283,40 +1366,52 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1283 | isFocused ? gap_UI / 4 : 1, | 1366 | isFocused ? gap_UI / 4 : 1, |
1284 | isFocused ? uiInputFrameFocused_ColorId | 1367 | isFocused ? uiInputFrameFocused_ColorId |
1285 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); | 1368 | : isHover ? uiInputFrameHover_ColorId : uiInputFrame_ColorId); |
1286 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), init_I2(-d->rightPadding, 0))); | 1369 | setClip_Paint(&p, adjusted_Rect(bounds, init_I2(d->leftPadding, 0), |
1370 | init_I2(-d->rightPadding, w->flags & extraPadding_WidgetFlag ? -gap_UI / 2 : 0))); | ||
1287 | const iRect contentBounds = contentBounds_InputWidget_(d); | 1371 | const iRect contentBounds = contentBounds_InputWidget_(d); |
1288 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); | 1372 | // const iInt2 textOrigin = textOrigin_InputWidget_(d); //, cstr_String(text)); |
1289 | iInt2 drawPos = topLeft_Rect(contentBounds); | 1373 | iInt2 drawPos = topLeft_Rect(contentBounds); |
1290 | const int fg = isHint ? uiAnnotation_ColorId | 1374 | const int fg = isHint ? uiAnnotation_ColorId |
1291 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId | 1375 | : isFocused && !isEmpty_Array(&d->text) ? uiInputTextFocused_ColorId |
1292 | : uiInputText_ColorId; | 1376 | : uiInputText_ColorId; |
1293 | /* TODO: If buffered, just draw the buffered copy. */ | 1377 | /* If buffered, just draw the buffered copy. */ |
1294 | iConstForEach(Array, i, &d->lines) { | 1378 | if (d->buffered && !isFocused) { //&& !isFocused/* && !isHint*/) { |
1295 | const iInputLine *line = i.value; | 1379 | /* Most input widgets will use this, since only one is focused at a time. */ |
1296 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; | 1380 | draw_TextBuf(d->buffered, topLeft_Rect(contentBounds), white_ColorId); |
1297 | const iInputLine *nextLine = isLast ? NULL : (line + 1); | 1381 | } |
1298 | const iRanges lineRange = { line->offset, | 1382 | else if (isHint) { |
1299 | nextLine ? nextLine->offset : size_Array(&d->text) }; | 1383 | drawRange_Text(d->font, topLeft_Rect(contentBounds), uiAnnotation_ColorId, |
1300 | if (isFocused && !isEmpty_Range(&d->mark)) { | 1384 | range_String(&d->hint)); |
1301 | /* Draw the selected range. */ | 1385 | } |
1302 | const iRanges mark = mark_InputWidget_(d); | 1386 | else { |
1303 | if (mark.start < lineRange.end && mark.end > lineRange.start) { | 1387 | iConstForEach(Array, i, &d->lines) { |
1304 | const int m1 = advanceN_Text(d->font, | 1388 | const iInputLine *line = i.value; |
1305 | cstr_String(&line->text), | 1389 | const iBool isLast = index_ArrayConstIterator(&i) == size_Array(&d->lines) - 1; |
1306 | iMax(lineRange.start, mark.start) - line->offset) | 1390 | const iInputLine *nextLine = isLast ? NULL : (line + 1); |
1307 | .x; | 1391 | const iRanges lineRange = { line->offset, |
1308 | const int m2 = advanceN_Text(d->font, | 1392 | nextLine ? nextLine->offset : size_Array(&d->text) }; |
1309 | cstr_String(&line->text), | 1393 | if (isFocused && !isEmpty_Range(&d->mark)) { |
1310 | iMin(lineRange.end, mark.end) - line->offset) | 1394 | /* Draw the selected range. */ |
1311 | .x; | 1395 | const iRanges mark = mark_InputWidget_(d); |
1312 | fillRect_Paint(&p, | 1396 | if (mark.start < lineRange.end && mark.end > lineRange.start) { |
1313 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | 1397 | const int m1 = advanceN_Text(d->font, |
1314 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), lineHeight_Text(d->font)) }, | 1398 | cstr_String(&line->text), |
1315 | uiMarked_ColorId); | 1399 | iMax(lineRange.start, mark.start) - line->offset) |
1400 | .x; | ||
1401 | const int m2 = advanceN_Text(d->font, | ||
1402 | cstr_String(&line->text), | ||
1403 | iMin(lineRange.end, mark.end) - line->offset) | ||
1404 | .x; | ||
1405 | fillRect_Paint(&p, | ||
1406 | (iRect){ addX_I2(drawPos, iMin(m1, m2)), | ||
1407 | init_I2(iMax(gap_UI / 3, iAbs(m2 - m1)), | ||
1408 | lineHeight_Text(d->font)) }, | ||
1409 | uiMarked_ColorId); | ||
1410 | } | ||
1316 | } | 1411 | } |
1412 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1413 | drawPos.y += lineHeight_Text(d->font); | ||
1317 | } | 1414 | } |
1318 | drawRange_Text(d->font, drawPos, fg, range_String(&line->text)); | ||
1319 | drawPos.y += lineHeight_Text(d->font); | ||
1320 | } | 1415 | } |
1321 | // if (d->buffered && !isFocused && !isHint) { | 1416 | // if (d->buffered && !isFocused && !isHint) { |
1322 | // /* Most input widgets will use this, since only one is focused at a time. */ | 1417 | // /* Most input widgets will use this, since only one is focused at a time. */ |
@@ -1378,13 +1473,7 @@ static void draw_InputWidget_(const iInputWidget *d) { | |||
1378 | drawChildren_Widget(w); | 1473 | drawChildren_Widget(w); |
1379 | } | 1474 | } |
1380 | 1475 | ||
1381 | //static void sizeChanged_InputWidget_(iInputWidget *d) { | ||
1382 | // printf("[InputWidget] %p: size changed, updating layout\n", d); | ||
1383 | // updateLinesAndResize_InputWidget_(d, iFalse); | ||
1384 | //} | ||
1385 | |||
1386 | iBeginDefineSubclass(InputWidget, Widget) | 1476 | iBeginDefineSubclass(InputWidget, Widget) |
1387 | .processEvent = (iAny *) processEvent_InputWidget_, | 1477 | .processEvent = (iAny *) processEvent_InputWidget_, |
1388 | .draw = (iAny *) draw_InputWidget_, | 1478 | .draw = (iAny *) draw_InputWidget_, |
1389 | // .sizeChanged = (iAny *) sizeChanged_InputWidget_, | ||
1390 | iEndDefineSubclass(InputWidget) | 1479 | iEndDefineSubclass(InputWidget) |
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c index a7aa6391..a940b0cb 100644 --- a/src/ui/labelwidget.c +++ b/src/ui/labelwidget.c | |||
@@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
28 | #include "app.h" | 28 | #include "app.h" |
29 | #include "util.h" | 29 | #include "util.h" |
30 | #include "keys.h" | 30 | #include "keys.h" |
31 | #include "touch.h" | ||
31 | 32 | ||
32 | struct Impl_LabelWidget { | 33 | struct Impl_LabelWidget { |
33 | iWidget widget; | 34 | iWidget widget; |
@@ -46,6 +47,15 @@ struct Impl_LabelWidget { | |||
46 | } flags; | 47 | } flags; |
47 | }; | 48 | }; |
48 | 49 | ||
50 | static iBool isHover_LabelWidget_(const iLabelWidget *d) { | ||
51 | #if defined (iPlatformMobile) | ||
52 | if (!isHovering_Touch()) { | ||
53 | return iFalse; | ||
54 | } | ||
55 | #endif | ||
56 | return isHover_Widget(d); | ||
57 | } | ||
58 | |||
49 | static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) { | 59 | static iInt2 padding_LabelWidget_(const iLabelWidget *d, int corner) { |
50 | const iWidget *w = constAs_Widget(d); | 60 | const iWidget *w = constAs_Widget(d); |
51 | const int64_t flags = flags_Widget(w); | 61 | const int64_t flags = flags_Widget(w); |
@@ -199,7 +209,7 @@ static void getColors_LabelWidget_(const iLabelWidget *d, int *bg, int *fg, int | |||
199 | if (startsWith_String(&d->label, "\v")) { | 209 | if (startsWith_String(&d->label, "\v")) { |
200 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ | 210 | colorEscape = cstr_String(&d->label)[1] - asciiBase_ColorEscape; /* TODO: can be two bytes long */ |
201 | } | 211 | } |
202 | if (isHover_Widget(w)) { | 212 | if (isHover_LabelWidget_(d)) { |
203 | if (isFrameless) { | 213 | if (isFrameless) { |
204 | *bg = uiBackgroundFramelessHover_ColorId; | 214 | *bg = uiBackgroundFramelessHover_ColorId; |
205 | *fg = uiTextFramelessHover_ColorId; | 215 | *fg = uiTextFramelessHover_ColorId; |
@@ -257,7 +267,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) { | |||
257 | const int64_t flags = flags_Widget(w); | 267 | const int64_t flags = flags_Widget(w); |
258 | const iRect bounds = bounds_Widget(w); | 268 | const iRect bounds = bounds_Widget(w); |
259 | iRect rect = bounds; | 269 | iRect rect = bounds; |
260 | const iBool isHover = isHover_Widget(w); | 270 | const iBool isHover = isHover_LabelWidget_(d); |
261 | if (isButton) { | 271 | if (isButton) { |
262 | shrink_Rect(&rect, divi_I2(gap2_UI, 4)); | 272 | shrink_Rect(&rect, divi_I2(gap2_UI, 4)); |
263 | adjustEdges_Rect(&rect, gap_UI / 8, 0, -gap_UI / 8, 0); | 273 | adjustEdges_Rect(&rect, gap_UI / 8, 0, -gap_UI / 8, 0); |
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index 95be44a0..a3406d48 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -414,7 +414,7 @@ static void draw_ListWidget_(const iListWidget *d) { | |||
414 | init_I2(blankWidth, d->itemHeight) }; | 414 | init_I2(blankWidth, d->itemHeight) }; |
415 | iConstForEach(IntSet, v, &d->invalidItems) { | 415 | iConstForEach(IntSet, v, &d->invalidItems) { |
416 | const size_t index = *v.value; | 416 | const size_t index = *v.value; |
417 | if (contains_Range(&drawItems, index)) { | 417 | if (contains_Range(&drawItems, index) && index < size_PtrArray(&d->items)) { |
418 | const iListItem *item = constAt_PtrArray(&d->items, index); | 418 | const iListItem *item = constAt_PtrArray(&d->items, index); |
419 | const iRect itemRect = { init_I2(0, index * d->itemHeight - buf->origin), | 419 | const iRect itemRect = { init_I2(0, index * d->itemHeight - buf->origin), |
420 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; | 420 | init_I2(d->visBuf->texSize.x, d->itemHeight) }; |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index b06523f9..3eafd4bd 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -662,7 +662,7 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | |||
662 | setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); | 662 | setPos_Widget(w, windowToLocal_Widget(w, bottomLeft_Rect(bounds_Widget(url)))); |
663 | #if defined (iPlatformAppleMobile) | 663 | #if defined (iPlatformAppleMobile) |
664 | /* Adjust height based on keyboard size. */ { | 664 | /* Adjust height based on keyboard size. */ { |
665 | w->rect.size.y = visibleRootSize_Window(window).y - top_Rect(bounds_Widget(w)); | 665 | w->rect.size.y = visibleSize_Root(root).y - top_Rect(bounds_Widget(w)); |
666 | if (deviceType_App() == phone_AppDeviceType) { | 666 | if (deviceType_App() == phone_AppDeviceType) { |
667 | float l, r; | 667 | float l, r; |
668 | safeAreaInsets_iOS(&l, NULL, &r, NULL); | 668 | safeAreaInsets_iOS(&l, NULL, &r, NULL); |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c new file mode 100644 index 00000000..1f5e9758 --- /dev/null +++ b/src/ui/mobile.c | |||
@@ -0,0 +1,795 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #include "mobile.h" | ||
24 | |||
25 | #include "app.h" | ||
26 | #include "command.h" | ||
27 | #include "defs.h" | ||
28 | #include "inputwidget.h" | ||
29 | #include "labelwidget.h" | ||
30 | #include "root.h" | ||
31 | #include "text.h" | ||
32 | #include "widget.h" | ||
33 | #include "window.h" | ||
34 | |||
35 | #if defined (iPlatformAppleMobile) | ||
36 | # include "ios.h" | ||
37 | #endif | ||
38 | |||
39 | static iBool useMobileSheetLayout_(void) { | ||
40 | return deviceType_App() != desktop_AppDeviceType; | ||
41 | } | ||
42 | |||
43 | static iBool isSideBySideLayout_(void) { | ||
44 | if (deviceType_App() == phone_AppDeviceType) { | ||
45 | return isLandscape_App(); | ||
46 | } | ||
47 | return numRoots_Window(get_Window()) == 1; | ||
48 | } | ||
49 | |||
50 | static enum iFontId labelFont_(void) { | ||
51 | return deviceType_App() == phone_AppDeviceType ? defaultBig_FontId : defaultMedium_FontId; | ||
52 | } | ||
53 | |||
54 | static enum iFontId labelBoldFont_(void) { | ||
55 | return deviceType_App() == phone_AppDeviceType ? defaultBigBold_FontId : defaultMediumBold_FontId; | ||
56 | } | ||
57 | |||
58 | static void updatePanelSheetMetrics_(iWidget *sheet) { | ||
59 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | ||
60 | iWidget *naviPad = child_Widget(navi, 0); | ||
61 | int naviHeight = lineHeight_Text(labelFont_()) + 4 * gap_UI; | ||
62 | #if defined (iPlatformAppleMobile) | ||
63 | float left, right, top, bottom; | ||
64 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
65 | setPadding_Widget(sheet, left, 0, right, 0); | ||
66 | navi->rect.pos = init_I2(left, top); | ||
67 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | ||
68 | iWidget *pad = *i.value; | ||
69 | setFixedSize_Widget(pad, init1_I2(naviHeight)); | ||
70 | } | ||
71 | #endif | ||
72 | setFixedSize_Widget(navi, init_I2(-1, naviHeight)); | ||
73 | } | ||
74 | |||
75 | static iWidget *findDetailStack_(iWidget *topPanel) { | ||
76 | return findChild_Widget(parent_Widget(topPanel), "detailstack"); | ||
77 | } | ||
78 | |||
79 | static void unselectAllPanelButtons_(iWidget *topPanel) { | ||
80 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
81 | if (isInstance_Object(i.object, &Class_LabelWidget)) { | ||
82 | iLabelWidget *label = i.object; | ||
83 | if (!cmp_String(command_LabelWidget(label), "panel.open")) { | ||
84 | setFlags_Widget(i.object, selected_WidgetFlag, iFalse); | ||
85 | } | ||
86 | } | ||
87 | } | ||
88 | } | ||
89 | |||
90 | static iBool mainDetailSplitHandler_(iWidget *mainDetailSplit, const char *cmd) { | ||
91 | if (equal_Command(cmd, "window.resized")) { | ||
92 | const iBool isPortrait = (deviceType_App() == phone_AppDeviceType && isPortrait_App()); | ||
93 | 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); | ||
97 | iWidget * navi = findChild_Widget(sheet, "panel.navi"); | ||
98 | iWidget * detailStack = findChild_Widget(mainDetailSplit, "detailstack"); | ||
99 | const size_t numPanels = childCount_Widget(detailStack); | ||
100 | const iBool isSideBySide = isSideBySideLayout_() && numPanels > 0; | ||
101 | setFlags_Widget(mainDetailSplit, arrangeHorizontal_WidgetFlag, isSideBySide); | ||
102 | setFlags_Widget(detailStack, expand_WidgetFlag, isSideBySide); | ||
103 | setFlags_Widget(detailStack, hidden_WidgetFlag, numPanels == 0); | ||
104 | iWidget *topPanel = findChild_Widget(mainDetailSplit, "panel.top"); | ||
105 | const int pad = isPortrait ? 0 : 3 * gap_UI; | ||
106 | if (isSideBySide) { | ||
107 | iAssert(topPanel); | ||
108 | topPanel->rect.size.x = (deviceType_App() == phone_AppDeviceType ? | ||
109 | safeRoot.size.x * 2 / 5 : (safeRoot.size.x / 3)); | ||
110 | } | ||
111 | if (deviceType_App() == tablet_AppDeviceType) { | ||
112 | setPadding_Widget(topPanel, pad, 0, pad, pad); | ||
113 | if (numPanels == 0) { | ||
114 | setFlags_Widget(sheet, centerHorizontal_WidgetFlag, iTrue); | ||
115 | const int sheetWidth = iMin(safeRoot.size.x, safeRoot.size.y); | ||
116 | mainDetailSplit->rect.size.x = sheetWidth; | ||
117 | setFixedSize_Widget(sheet, init_I2(sheetWidth, -1)); | ||
118 | setFixedSize_Widget(navi, init_I2(sheetWidth, -1)); | ||
119 | } | ||
120 | } | ||
121 | iForEach(ObjectList, i, children_Widget(detailStack)) { | ||
122 | iWidget *panel = i.object; | ||
123 | setFlags_Widget(panel, edgeDraggable_WidgetFlag, !isSideBySide); | ||
124 | if (isSideBySide) { | ||
125 | setVisualOffset_Widget(panel, 0, 0, 0); | ||
126 | } | ||
127 | setPadding_Widget(panel, pad, 0, pad, pad); | ||
128 | } | ||
129 | arrange_Widget(mainDetailSplit); | ||
130 | } | ||
131 | return iFalse; | ||
132 | } | ||
133 | |||
134 | static iBool topPanelHandler_(iWidget *topPanel, const char *cmd) { | ||
135 | const iBool isPortrait = !isSideBySideLayout_(); | ||
136 | if (equal_Command(cmd, "panel.open")) { | ||
137 | iWidget *button = pointer_Command(cmd); | ||
138 | iWidget *panel = userData_Object(button); | ||
139 | // openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); | ||
140 | // setFlags_Widget(panel, hidden_WidgetFlag, iFalse); | ||
141 | unselectAllPanelButtons_(topPanel); | ||
142 | iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) { | ||
143 | iWidget *child = i.object; | ||
144 | setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, child != panel); | ||
145 | /* Animate the current panel in. */ | ||
146 | if (child == panel && isPortrait) { | ||
147 | setupSheetTransition_Mobile(panel, iTrue); | ||
148 | } | ||
149 | } | ||
150 | setFlags_Widget(button, selected_WidgetFlag, iTrue); | ||
151 | return iTrue; | ||
152 | } | ||
153 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && | ||
154 | argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { | ||
155 | postCommand_App("panel.close"); | ||
156 | return iTrue; | ||
157 | } | ||
158 | if (equal_Command(cmd, "panel.close")) { | ||
159 | iBool wasClosed = iFalse; | ||
160 | if (isPortrait) { | ||
161 | iForEach(ObjectList, i, children_Widget(findDetailStack_(topPanel))) { | ||
162 | iWidget *child = i.object; | ||
163 | if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { | ||
164 | // closeMenu_Widget(child); | ||
165 | setupSheetTransition_Mobile(child, iFalse); | ||
166 | setFlags_Widget(child, hidden_WidgetFlag | disabled_WidgetFlag, iTrue); | ||
167 | setFocus_Widget(NULL); | ||
168 | updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); | ||
169 | wasClosed = iTrue; | ||
170 | } | ||
171 | } | ||
172 | } | ||
173 | unselectAllPanelButtons_(topPanel); | ||
174 | if (!wasClosed) { | ||
175 | postCommand_App("prefs.dismiss"); | ||
176 | } | ||
177 | return iTrue; | ||
178 | } | ||
179 | if (equal_Command(cmd, "document.changed")) { | ||
180 | postCommand_App("prefs.dismiss"); | ||
181 | return iFalse; | ||
182 | } | ||
183 | if (equal_Command(cmd, "window.resized")) { | ||
184 | // sheet > mdsplit > panel.top | ||
185 | updatePanelSheetMetrics_(parent_Widget(parent_Widget(topPanel))); | ||
186 | } | ||
187 | return iFalse; | ||
188 | } | ||
189 | |||
190 | static iBool isTwoColumnPage_(iWidget *d) { | ||
191 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | ||
192 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | ||
193 | return iFalse; | ||
194 | } | ||
195 | if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { | ||
196 | return class_Widget(child_Widget(d, 0)) == &Class_Widget && | ||
197 | class_Widget(child_Widget(d, 1)) == &Class_Widget; | ||
198 | } | ||
199 | return iFalse; | ||
200 | } | ||
201 | |||
202 | static iBool isOmittedPref_(const iString *id) { | ||
203 | static const char *omittedPrefs[] = { | ||
204 | "prefs.smoothscroll", | ||
205 | "prefs.imageloadscroll", | ||
206 | "prefs.pinsplit", | ||
207 | "prefs.retainwindow", | ||
208 | "prefs.ca.file", | ||
209 | "prefs.ca.path", | ||
210 | }; | ||
211 | iForIndices(i, omittedPrefs) { | ||
212 | if (cmp_String(id, omittedPrefs[i]) == 0) { | ||
213 | return iTrue; | ||
214 | } | ||
215 | } | ||
216 | return iFalse; | ||
217 | } | ||
218 | |||
219 | enum iPrefsElement { | ||
220 | panelTitle_PrefsElement, | ||
221 | heading_PrefsElement, | ||
222 | toggle_PrefsElement, | ||
223 | dropdown_PrefsElement, | ||
224 | radioButton_PrefsElement, | ||
225 | textInput_PrefsElement, | ||
226 | }; | ||
227 | |||
228 | static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, | ||
229 | enum iPrefsElement elementType, | ||
230 | enum iPrefsElement precedingElementType) { | ||
231 | /* Erase redundant/unused headings. */ | ||
232 | if (precedingElementType == heading_PrefsElement && | ||
233 | (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { | ||
234 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
235 | if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { | ||
236 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
237 | } | ||
238 | } | ||
239 | if (child) { | ||
240 | /* Insert padding between different element types. */ | ||
241 | if (precedingElementType != panelTitle_PrefsElement) { | ||
242 | if (elementType == heading_PrefsElement || | ||
243 | (elementType == toggle_PrefsElement && | ||
244 | precedingElementType != toggle_PrefsElement && | ||
245 | precedingElementType != heading_PrefsElement) || | ||
246 | (elementType == dropdown_PrefsElement && | ||
247 | precedingElementType != dropdown_PrefsElement && | ||
248 | precedingElementType != heading_PrefsElement) || | ||
249 | (elementType == textInput_PrefsElement && | ||
250 | precedingElementType != textInput_PrefsElement && | ||
251 | precedingElementType != heading_PrefsElement)) { | ||
252 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
253 | } | ||
254 | } | ||
255 | if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || | ||
256 | (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { | ||
257 | flags |= borderTop_WidgetFlag; | ||
258 | } | ||
259 | return addChildFlags_Widget(panel, child, flags); | ||
260 | } | ||
261 | return NULL; | ||
262 | } | ||
263 | |||
264 | static void stripTrailingColon_(iLabelWidget *label) { | ||
265 | const iString *text = text_LabelWidget(label); | ||
266 | if (endsWith_String(text, ":")) { | ||
267 | iString *mod = copy_String(text); | ||
268 | removeEnd_String(mod, 1); | ||
269 | updateText_LabelWidget(label, mod); | ||
270 | delete_String(mod); | ||
271 | } | ||
272 | } | ||
273 | |||
274 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | ||
275 | iLabelWidget *btn = new_LabelWidget(text, command); | ||
276 | setFlags_Widget(as_Widget(btn), | ||
277 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
278 | frameless_WidgetFlag | extraPadding_WidgetFlag, | ||
279 | iTrue); | ||
280 | checkIcon_LabelWidget(btn); | ||
281 | setFont_LabelWidget(btn, labelFont_()); | ||
282 | setTextColor_LabelWidget(btn, uiTextStrong_ColorId); | ||
283 | setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); | ||
284 | return btn; | ||
285 | } | ||
286 | |||
287 | static iWidget *makeValuePadding_(iWidget *value) { | ||
288 | iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; | ||
289 | if (input) { | ||
290 | setFont_InputWidget(input, labelFont_()); | ||
291 | setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); | ||
292 | } | ||
293 | iWidget *pad = new_Widget(); | ||
294 | setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); | ||
295 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | ||
296 | addChild_Widget(pad, iClob(value)); | ||
297 | setFlags_Widget(pad, | ||
298 | borderBottom_WidgetFlag | | ||
299 | arrangeVertical_WidgetFlag | | ||
300 | resizeToParentWidth_WidgetFlag | | ||
301 | resizeWidthOfChildren_WidgetFlag | | ||
302 | arrangeHeight_WidgetFlag, | ||
303 | iTrue); | ||
304 | return pad; | ||
305 | } | ||
306 | |||
307 | static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { | ||
308 | iWidget *div = new_Widget(); | ||
309 | setFlags_Widget(div, | ||
310 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | ||
311 | resizeWidthOfChildren_WidgetFlag | | ||
312 | arrangeHorizontal_WidgetFlag, iTrue); | ||
313 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | ||
314 | setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); | ||
315 | addChildFlags_Widget(div, iClob(heading), 0); | ||
316 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | ||
317 | setFont_LabelWidget(heading, labelFont_()); | ||
318 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | ||
319 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
320 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
321 | } | ||
322 | else if (isInstance_Object(value, &Class_LabelWidget) && | ||
323 | cmp_String(command_LabelWidget((iLabelWidget *) value), "toggle")) { | ||
324 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
325 | /* TODO: This doesn't work? */ | ||
326 | // setCommand_LabelWidget(heading, | ||
327 | // collectNewFormat_String("!%s ptr:%p", | ||
328 | // cstr_String(command_LabelWidget((iLabelWidget *) value)), | ||
329 | // value)); | ||
330 | } | ||
331 | else { | ||
332 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | ||
333 | addChild_Widget(div, iClob(value)); | ||
334 | } | ||
335 | return div; | ||
336 | } | ||
337 | |||
338 | static iWidget *addChildPanel_(iWidget *parent, iLabelWidget *panelButton, | ||
339 | const iString *titleText) { | ||
340 | iWidget *panel = new_Widget(); | ||
341 | setId_Widget(panel, "panel"); | ||
342 | setUserData_Object(panelButton, panel); | ||
343 | setBackgroundColor_Widget(panel, uiBackground_ColorId); | ||
344 | setId_Widget(addChild_Widget(panel, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
345 | if (titleText) { | ||
346 | iLabelWidget *title = | ||
347 | addChildFlags_Widget(panel, | ||
348 | iClob(new_LabelWidget(cstr_String(titleText), NULL)), | ||
349 | alignLeft_WidgetFlag | frameless_WidgetFlag); | ||
350 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
351 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
352 | } | ||
353 | addChildFlags_Widget(parent, | ||
354 | iClob(panel), | ||
355 | focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | | ||
356 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
357 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
358 | drawBackgroundToBottom_WidgetFlag | | ||
359 | horizontalOffset_WidgetFlag | commandOnClick_WidgetFlag); | ||
360 | return panel; | ||
361 | } | ||
362 | |||
363 | void finalizeSheet_Mobile(iWidget *sheet) { | ||
364 | /* The sheet contents are completely rearranged and restyled on a phone. | ||
365 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | ||
366 | scrollable so they can be taller than the display. In hindsight, it may have been | ||
367 | easier to create phone versions of each dialog, but at least this works with any | ||
368 | future changes to the UI (..."works"). At least this way it is possible to enforce | ||
369 | a consistent styling. */ | ||
370 | if (useMobileSheetLayout_() && parent_Widget(sheet) == root_Widget(sheet)) { | ||
371 | if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { | ||
372 | /* Already finalized. */ | ||
373 | arrange_Widget(sheet); | ||
374 | postRefresh_App(); | ||
375 | return; | ||
376 | } | ||
377 | /* Landscape Layout Portrait Layout | ||
378 | |||
379 | ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐ | ||
380 | │ │┌───────────────────┐ │ │ │Detail | ||
381 | │ ││┌──────────────────┴┐ │ │ │Stack │ | ||
382 | │ │││┌──────────────────┴┐│ │ │┌──────┐ | ||
383 | │ ││││ ││ │ ││┌─────┴┐│ | ||
384 | │ ││││ ││ │ │││ │ | ||
385 | │Top Panel││││ ││ │Top Panel│││ ││ | ||
386 | │ ││││ Panels ││ │ │││Panels│ | ||
387 | │ ││││ ││ │ │││ ││ | ||
388 | │ │└┤│ ││ │ │││ │ | ||
389 | │ │ └┤ ││ │ │└┤ ││ | ||
390 | │ │ └───────────────────┘│ │ │ └──────┘ | ||
391 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ | ||
392 | offscreen | ||
393 | */ | ||
394 | /* Modify the top sheet to act as a fullscreen background. */ | ||
395 | setPadding1_Widget(sheet, 0); | ||
396 | setBackgroundColor_Widget(sheet, uiBackground_ColorId); | ||
397 | setFlags_Widget(sheet, | ||
398 | keepOnTop_WidgetFlag | | ||
399 | parentCannotResize_WidgetFlag | | ||
400 | arrangeSize_WidgetFlag | | ||
401 | centerHorizontal_WidgetFlag | | ||
402 | arrangeVertical_WidgetFlag | | ||
403 | arrangeHorizontal_WidgetFlag | | ||
404 | overflowScrollable_WidgetFlag, | ||
405 | iFalse); | ||
406 | setFlags_Widget(sheet, | ||
407 | frameless_WidgetFlag | | ||
408 | //resizeWidthOfChildren_WidgetFlag | | ||
409 | edgeDraggable_WidgetFlag | | ||
410 | commandOnClick_WidgetFlag, | ||
411 | iTrue); | ||
412 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ | ||
413 | iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); | ||
414 | iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); | ||
415 | iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); | ||
416 | const iBool isPrefs = (prefsTabs != NULL); | ||
417 | const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
418 | frameless_WidgetFlag | extraPadding_WidgetFlag; | ||
419 | iWidget *mainDetailSplit = makeHDiv_Widget(); | ||
420 | setCommandHandler_Widget(mainDetailSplit, mainDetailSplitHandler_); | ||
421 | setFlags_Widget(mainDetailSplit, resizeHeightOfChildren_WidgetFlag, iFalse); | ||
422 | setId_Widget(mainDetailSplit, "mdsplit"); | ||
423 | iWidget *topPanel = new_Widget(); { | ||
424 | setId_Widget(topPanel, "panel.top"); | ||
425 | setCommandHandler_Widget(topPanel, topPanelHandler_); | ||
426 | setFlags_Widget(topPanel, | ||
427 | arrangeVertical_WidgetFlag | | ||
428 | resizeWidthOfChildren_WidgetFlag | | ||
429 | arrangeHeight_WidgetFlag | | ||
430 | overflowScrollable_WidgetFlag | | ||
431 | commandOnClick_WidgetFlag, | ||
432 | iTrue); | ||
433 | addChild_Widget(mainDetailSplit, iClob(topPanel)); | ||
434 | } | ||
435 | iWidget *detailStack = new_Widget(); { | ||
436 | setId_Widget(detailStack, "detailstack"); | ||
437 | setFlags_Widget(detailStack, collapse_WidgetFlag | resizeWidthOfChildren_WidgetFlag, iTrue); | ||
438 | addChild_Widget(mainDetailSplit, iClob(detailStack)); | ||
439 | } | ||
440 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
441 | /* Slide top panel with detail panels. */ { | ||
442 | setFlags_Widget(topPanel, refChildrenOffset_WidgetFlag, iTrue); | ||
443 | topPanel->offsetRef = detailStack; | ||
444 | } | ||
445 | if (prefsTabs) { | ||
446 | iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */ | ||
447 | iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons"))); | ||
448 | /* Pull out the pages and make them panels. */ | ||
449 | iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages"); | ||
450 | size_t pageCount = tabCount_Widget(prefsTabs); | ||
451 | for (size_t i = 0; i < pageCount; i++) { | ||
452 | iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); | ||
453 | iWidget *page = removeTabPage_Widget(prefsTabs, 0); | ||
454 | iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ | ||
455 | pushBack_PtrArray(contents, ref_Object(pageContent)); | ||
456 | iLabelWidget *panelButton; | ||
457 | pushBack_PtrArray(panelButtons, | ||
458 | addChildFlags_Widget(topPanel, | ||
459 | iClob(panelButton = makePanelButton_( | ||
460 | i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), | ||
461 | "panel.open")), | ||
462 | (i == 0 ? borderTop_WidgetFlag : 0) | | ||
463 | chevron_WidgetFlag)); | ||
464 | const iChar icons[] = { | ||
465 | 0x02699, /* gear */ | ||
466 | 0x1f4f1, /* mobile phone */ | ||
467 | 0x1f3a8, /* palette */ | ||
468 | 0x1f523, | ||
469 | 0x1f5a7, /* computer network */ | ||
470 | }; | ||
471 | setIcon_LabelWidget(panelButton, icons[i]); | ||
472 | // setFont_LabelWidget(panelButton, labelFont_()); | ||
473 | // setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId); | ||
474 | iRelease(page); | ||
475 | delete_String(text); | ||
476 | } | ||
477 | destroy_Widget(prefsTabs); | ||
478 | } | ||
479 | iForEach(ObjectList, i, children_Widget(sheet)) { | ||
480 | iWidget *child = i.object; | ||
481 | if (isTwoColumnPage_(child)) { | ||
482 | pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); | ||
483 | } | ||
484 | else { | ||
485 | removeChild_Widget(sheet, child); | ||
486 | addChild_Widget(topPanel, child); | ||
487 | iRelease(child); | ||
488 | } | ||
489 | } | ||
490 | const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); | ||
491 | addChild_Widget(sheet, iClob(mainDetailSplit)); | ||
492 | iForEach(PtrArray, j, contents) { | ||
493 | iWidget *owner = topPanel; | ||
494 | if (useSlidePanels) { | ||
495 | /* Create a new child panel. */ | ||
496 | iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); | ||
497 | owner = addChildPanel_(detailStack, button, | ||
498 | collect_String(upper_String(text_LabelWidget(button)))); | ||
499 | } | ||
500 | iWidget *pageContent = j.ptr; | ||
501 | iWidget *headings = child_Widget(pageContent, 0); | ||
502 | iWidget *values = child_Widget(pageContent, 1); | ||
503 | enum iPrefsElement prevElement = panelTitle_PrefsElement; | ||
504 | /* Identify the types of controls in the dialog and restyle/organize them. */ | ||
505 | while (!isEmpty_ObjectList(children_Widget(headings))) { | ||
506 | iWidget *heading = child_Widget(headings, 0); | ||
507 | iWidget *value = child_Widget(values, 0); | ||
508 | removeChild_Widget(headings, heading); | ||
509 | removeChild_Widget(values, value); | ||
510 | /* Can we ignore these widgets? */ | ||
511 | if (isOmittedPref_(id_Widget(value)) || | ||
512 | (class_Widget(heading) == &Class_Widget && | ||
513 | class_Widget(value) == &Class_Widget) /* just padding */) { | ||
514 | iRelease(heading); | ||
515 | iRelease(value); | ||
516 | continue; | ||
517 | } | ||
518 | enum iPrefsElement element = toggle_PrefsElement; | ||
519 | iLabelWidget *headingLabel = NULL; | ||
520 | iLabelWidget *valueLabel = NULL; | ||
521 | iInputWidget *valueInput = NULL; | ||
522 | const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; | ||
523 | if (isInstance_Object(heading, &Class_LabelWidget)) { | ||
524 | headingLabel = (iLabelWidget *) heading; | ||
525 | stripTrailingColon_(headingLabel); | ||
526 | } | ||
527 | if (isInstance_Object(value, &Class_LabelWidget)) { | ||
528 | valueLabel = (iLabelWidget *) value; | ||
529 | setFont_LabelWidget(valueLabel, labelFont_()); | ||
530 | } | ||
531 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
532 | valueInput = (iInputWidget *) value; | ||
533 | setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); | ||
534 | element = textInput_PrefsElement; | ||
535 | } | ||
536 | if (childCount_Widget(value) >= 2) { | ||
537 | if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { | ||
538 | element = textInput_PrefsElement; | ||
539 | setPadding_Widget(value, 0, 0, gap_UI, 0); | ||
540 | valueInput = child_Widget(value, 0); | ||
541 | } | ||
542 | } | ||
543 | if (valueInput) { | ||
544 | setFont_InputWidget(valueInput, labelFont_()); | ||
545 | setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); | ||
546 | } | ||
547 | /* Toggles have the button on the right. */ | ||
548 | if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { | ||
549 | element = toggle_PrefsElement; | ||
550 | addPanelChild_(owner, | ||
551 | iClob(makeValuePaddingWithHeading_(headingLabel, value)), | ||
552 | 0, | ||
553 | element, | ||
554 | prevElement); | ||
555 | } | ||
556 | else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { | ||
557 | element = heading_PrefsElement; | ||
558 | iRelease(value); | ||
559 | addPanelChild_(owner, iClob(heading), 0, element, prevElement); | ||
560 | setFont_LabelWidget(headingLabel, uiLabel_FontId); | ||
561 | } | ||
562 | else if (isMenuButton) { | ||
563 | element = dropdown_PrefsElement; | ||
564 | setFlags_Widget(value, | ||
565 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
566 | frameless_WidgetFlag, iTrue); | ||
567 | setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); | ||
568 | iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
569 | element, prevElement); | ||
570 | pad->padding[2] = gap_UI; | ||
571 | } | ||
572 | else if (valueInput) { | ||
573 | addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
574 | element, prevElement); | ||
575 | } | ||
576 | else { | ||
577 | if (childCount_Widget(value) >= 2) { | ||
578 | element = radioButton_PrefsElement; | ||
579 | /* Always padding before radio buttons. */ | ||
580 | addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
581 | } | ||
582 | addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); | ||
583 | if (headingLabel) { | ||
584 | setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); | ||
585 | setText_LabelWidget(headingLabel, | ||
586 | collect_String(upper_String(text_LabelWidget(headingLabel)))); | ||
587 | } | ||
588 | addPanelChild_(owner, iClob(value), 0, element, prevElement); | ||
589 | /* Radio buttons expand to fill the space. */ | ||
590 | if (element == radioButton_PrefsElement) { | ||
591 | setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); | ||
592 | setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
593 | setFlags_Widget(value, arrangeWidth_WidgetFlag, iFalse); | ||
594 | setFlags_Widget(value, | ||
595 | borderBottom_WidgetFlag | | ||
596 | resizeToParentWidth_WidgetFlag | | ||
597 | resizeWidthOfChildren_WidgetFlag, | ||
598 | iTrue); | ||
599 | iForEach(ObjectList, sub, children_Widget(value)) { | ||
600 | if (isInstance_Object(sub.object, &Class_LabelWidget)) { | ||
601 | iLabelWidget *opt = sub.object; | ||
602 | setFont_LabelWidget(opt, defaultMedium_FontId); | ||
603 | setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); | ||
604 | } | ||
605 | } | ||
606 | } | ||
607 | } | ||
608 | prevElement = element; | ||
609 | } | ||
610 | addPanelChild_(owner, NULL, 0, 0, prevElement); | ||
611 | destroy_Widget(pageContent); | ||
612 | } | ||
613 | destroyPending_Root(sheet->root); | ||
614 | /* Additional elements for preferences. */ | ||
615 | if (isPrefs) { | ||
616 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(labelFont_())))); | ||
617 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, | ||
618 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), | ||
619 | chevron_WidgetFlag); | ||
620 | addChildFlags_Widget(topPanel, | ||
621 | iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), | ||
622 | borderTop_WidgetFlag); | ||
623 | /* The About panel. */ { | ||
624 | iWidget *panel = addChildPanel_(detailStack, aboutButton, NULL); | ||
625 | iString *msg = collectNew_String(); | ||
626 | setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); | ||
627 | #if defined (iPlatformAppleMobile) | ||
628 | appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); | ||
629 | #endif | ||
630 | addChildFlags_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL)), | ||
631 | frameless_WidgetFlag); | ||
632 | addChildFlags_Widget(panel, | ||
633 | iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", | ||
634 | "!open url:https://skyjake.fi/@jk")), | ||
635 | borderTop_WidgetFlag); | ||
636 | addChildFlags_Widget(panel, | ||
637 | iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", | ||
638 | "!open url:about:version")), | ||
639 | 0); | ||
640 | addChildFlags_Widget(panel, | ||
641 | iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", | ||
642 | "!open url:about:about")), | ||
643 | 0); | ||
644 | addChildFlags_Widget(panel, | ||
645 | iClob(makePanelButton_(bug_Icon " ${menu.debug}", | ||
646 | "!open url:about:debug")), | ||
647 | 0); | ||
648 | } | ||
649 | } | ||
650 | else { | ||
651 | setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); | ||
652 | /* Update heading style. */ | ||
653 | setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); | ||
654 | setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); | ||
655 | } | ||
656 | if (findChild_Widget(sheet, "valueinput.prompt")) { | ||
657 | iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); | ||
658 | setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); | ||
659 | iInputWidget *input = findChild_Widget(sheet, "input"); | ||
660 | removeChild_Widget(parent_Widget(input), input); | ||
661 | addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); | ||
662 | } | ||
663 | /* Top padding for each panel, to account for the overlaid navbar. */ { | ||
664 | setId_Widget(addChildPos_Widget(topPanel, | ||
665 | iClob(makePadding_Widget(0)), front_WidgetAddPos), | ||
666 | "panel.toppad"); | ||
667 | } | ||
668 | /* Navbar. */ { | ||
669 | iWidget *navi = new_Widget(); | ||
670 | setId_Widget(navi, "panel.navi"); | ||
671 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
672 | addChild_Widget(navi, iClob(makePadding_Widget(0))); | ||
673 | iLabelWidget *back = addChildFlags_Widget(navi, | ||
674 | iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), | ||
675 | noBackground_WidgetFlag | frameless_WidgetFlag | | ||
676 | alignLeft_WidgetFlag | extraPadding_WidgetFlag); | ||
677 | checkIcon_LabelWidget(back); | ||
678 | setId_Widget(as_Widget(back), "panel.back"); | ||
679 | setFont_LabelWidget(back, labelFont_()); | ||
680 | if (!isPrefs) { | ||
681 | /* Pick up the dialog buttons for the navbar. */ | ||
682 | iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); | ||
683 | iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); | ||
684 | // if (!cancel) { | ||
685 | // cancel = findMenuItem_Widget(buttons, "translation.cancel"); | ||
686 | // } | ||
687 | if (cancel) { | ||
688 | updateText_LabelWidget(back, text_LabelWidget(cancel)); | ||
689 | setCommand_LabelWidget(back, command_LabelWidget(cancel)); | ||
690 | } | ||
691 | iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); | ||
692 | if (def && !cancel) { | ||
693 | updateText_LabelWidget(back, text_LabelWidget(def)); | ||
694 | setCommand_LabelWidget(back, command_LabelWidget(def)); | ||
695 | setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); | ||
696 | setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); | ||
697 | setIcon_LabelWidget(back, 0); | ||
698 | setFont_LabelWidget(back, labelBoldFont_()); | ||
699 | } | ||
700 | else if (def != cancel) { | ||
701 | removeChild_Widget(buttons, def); | ||
702 | setFont_LabelWidget(def, labelBoldFont_()); | ||
703 | setFlags_Widget(as_Widget(def), | ||
704 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
705 | noBackground_WidgetFlag, iTrue); | ||
706 | addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); | ||
707 | updateSize_LabelWidget(def); | ||
708 | } | ||
709 | /* Action buttons are added in the bottom as extra buttons. */ { | ||
710 | iBool isFirstAction = iTrue; | ||
711 | iForEach(ObjectList, i, children_Widget(buttons)) { | ||
712 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
713 | i.object != cancel && i.object != def) { | ||
714 | iLabelWidget *item = i.object; | ||
715 | setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); | ||
716 | setFont_LabelWidget(item, labelFont_()); | ||
717 | removeChild_Widget(buttons, item); | ||
718 | addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | | ||
719 | (isFirstAction ? borderTop_WidgetFlag : 0)); | ||
720 | updateSize_LabelWidget(item); | ||
721 | isFirstAction = iFalse; | ||
722 | } | ||
723 | } | ||
724 | } | ||
725 | iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); | ||
726 | /* Styling for remaining elements. */ | ||
727 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
728 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
729 | isEmpty_String(command_LabelWidget(i.object)) && | ||
730 | isEmpty_String(id_Widget(i.object))) { | ||
731 | setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); | ||
732 | if (font_LabelWidget(i.object) == uiLabel_FontId) { | ||
733 | setFont_LabelWidget(i.object, uiContent_FontId); | ||
734 | } | ||
735 | } | ||
736 | } | ||
737 | } | ||
738 | addChildFlags_Widget(sheet, iClob(navi), | ||
739 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
740 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
741 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
742 | } | ||
743 | if (isPrefs && isSideBySideLayout_()) { | ||
744 | /* Show the General panel. */ | ||
745 | postCommand_Widget(at_PtrArray(panelButtons, 0), "panel.open"); | ||
746 | } | ||
747 | mainDetailSplitHandler_(mainDetailSplit, "window.resized"); /* make it resize the split */ | ||
748 | updatePanelSheetMetrics_(sheet); | ||
749 | iAssert(sheet->parent); | ||
750 | arrange_Widget(sheet->parent); | ||
751 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
752 | } | ||
753 | else { | ||
754 | arrange_Widget(sheet); | ||
755 | } | ||
756 | postRefresh_App(); | ||
757 | } | ||
758 | |||
759 | void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | ||
760 | if (!useMobileSheetLayout_()) { | ||
761 | return; | ||
762 | } | ||
763 | const iBool isSlidePanel = (flags_Widget(sheet) & horizontalOffset_WidgetFlag) != 0; | ||
764 | if (isSlidePanel && isLandscape_App()) { | ||
765 | return; | ||
766 | } | ||
767 | if (isIncoming) { | ||
768 | setVisualOffset_Widget(sheet, isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), 0, 0); | ||
769 | setVisualOffset_Widget(sheet, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | ||
770 | } | ||
771 | else { | ||
772 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset) - 0) > 1; | ||
773 | setVisualOffset_Widget(sheet, | ||
774 | isSlidePanel ? width_Widget(sheet) : height_Widget(sheet), | ||
775 | wasDragged ? 100 : 200, | ||
776 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); | ||
777 | } | ||
778 | } | ||
779 | |||
780 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | ||
781 | if (isSideBySideLayout_()) { | ||
782 | return; | ||
783 | } | ||
784 | if (isIncoming) { | ||
785 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
786 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | ||
787 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); | ||
788 | } | ||
789 | else { | ||
790 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
791 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; | ||
792 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | ||
793 | wasDragged ? 0 : easeIn_AnimFlag); | ||
794 | } | ||
795 | } | ||
diff --git a/src/ui/mobile.h b/src/ui/mobile.h new file mode 100644 index 00000000..44134389 --- /dev/null +++ b/src/ui/mobile.h | |||
@@ -0,0 +1,32 @@ | |||
1 | /* Copyright 2021 Jaakko Keränen <jaakko.keranen@iki.fi> | ||
2 | |||
3 | Redistribution and use in source and binary forms, with or without | ||
4 | modification, are permitted provided that the following conditions are met: | ||
5 | |||
6 | 1. Redistributions of source code must retain the above copyright notice, this | ||
7 | list of conditions and the following disclaimer. | ||
8 | 2. Redistributions in binary form must reproduce the above copyright notice, | ||
9 | this list of conditions and the following disclaimer in the documentation | ||
10 | and/or other materials provided with the distribution. | ||
11 | |||
12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
13 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
14 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
15 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
16 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
17 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
18 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
19 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
20 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
21 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | ||
22 | |||
23 | #pragma once | ||
24 | |||
25 | #include <the_Foundation/defs.h> | ||
26 | |||
27 | iDeclareType(Widget) | ||
28 | |||
29 | void setupMenuTransition_Mobile (iWidget *menu, iBool isIncoming); | ||
30 | void setupSheetTransition_Mobile (iWidget *sheet, iBool isIncoming); | ||
31 | |||
32 | void finalizeSheet_Mobile (iWidget *sheet); | ||
diff --git a/src/ui/paint.c b/src/ui/paint.c index faaf403d..c575d5fc 100644 --- a/src/ui/paint.c +++ b/src/ui/paint.c | |||
@@ -62,20 +62,19 @@ void endTarget_Paint(iPaint *d) { | |||
62 | } | 62 | } |
63 | 63 | ||
64 | void setClip_Paint(iPaint *d, iRect rect) { | 64 | void setClip_Paint(iPaint *d, iRect rect) { |
65 | if (rect.pos.y < 0) { | 65 | rect = intersect_Rect(rect, rect_Root(get_Root())); |
66 | const int off = rect.pos.y; | 66 | if (isEmpty_Rect(rect)) { |
67 | rect.pos.y -= off; | 67 | rect = init_Rect(0, 0, 1, 1); |
68 | rect.size.y = iMax(0, rect.size.y + off); | ||
69 | } | ||
70 | if (rect.pos.x < 0) { | ||
71 | const int off = rect.pos.x; | ||
72 | rect.pos.x -= off; | ||
73 | rect.size.x = iMax(0, rect.size.x + off); | ||
74 | } | 68 | } |
75 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | 69 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); |
76 | } | 70 | } |
77 | 71 | ||
78 | void unsetClip_Paint(iPaint *d) { | 72 | void unsetClip_Paint(iPaint *d) { |
73 | if (numRoots_Window(get_Window()) > 1) { | ||
74 | const iRect rect = rect_Root(get_Root()); | ||
75 | SDL_RenderSetClipRect(renderer_Paint_(d), (const SDL_Rect *) &rect); | ||
76 | return; | ||
77 | } | ||
79 | #if SDL_VERSION_ATLEAST(2, 0, 12) | 78 | #if SDL_VERSION_ATLEAST(2, 0, 12) |
80 | SDL_RenderSetClipRect(renderer_Paint_(d), NULL); | 79 | SDL_RenderSetClipRect(renderer_Paint_(d), NULL); |
81 | #else | 80 | #else |
diff --git a/src/ui/root.c b/src/ui/root.c index 76ef05c4..6cf3f424 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -269,6 +269,7 @@ iAnyObject *findWidget_Root(const char *id) { | |||
269 | } | 269 | } |
270 | 270 | ||
271 | void destroyPending_Root(iRoot *d) { | 271 | void destroyPending_Root(iRoot *d) { |
272 | iRoot *oldRoot = current_Root(); | ||
272 | setCurrent_Root(d); | 273 | setCurrent_Root(d); |
273 | iForEach(PtrSet, i, d->pendingDestruction) { | 274 | iForEach(PtrSet, i, d->pendingDestruction) { |
274 | iWidget *widget = *i.value; | 275 | iWidget *widget = *i.value; |
@@ -282,7 +283,7 @@ void destroyPending_Root(iRoot *d) { | |||
282 | iRelease(widget); | 283 | iRelease(widget); |
283 | remove_PtrSetIterator(&i); | 284 | remove_PtrSetIterator(&i); |
284 | } | 285 | } |
285 | setCurrent_Root(NULL); | 286 | setCurrent_Root(oldRoot); |
286 | } | 287 | } |
287 | 288 | ||
288 | void postArrange_Root(iRoot *d) { | 289 | void postArrange_Root(iRoot *d) { |
@@ -349,6 +350,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
349 | } | 350 | } |
350 | else if (equal_Command(cmd, "input.resized")) { | 351 | else if (equal_Command(cmd, "input.resized")) { |
351 | /* No parent handled this, so do a full rearrangement. */ | 352 | /* No parent handled this, so do a full rearrangement. */ |
353 | /* TODO: Defer this and do a single rearrangement later. */ | ||
352 | arrange_Widget(root); | 354 | arrange_Widget(root); |
353 | postRefresh_App(); | 355 | postRefresh_App(); |
354 | return iTrue; | 356 | return iTrue; |
@@ -414,6 +416,7 @@ static iBool handleRootCommands_(iWidget *root, const char *cmd) { | |||
414 | else { | 416 | else { |
415 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); | 417 | addChildPos_Widget(findChild_Widget(root, "stack"), iClob(sidebar), back_WidgetAddPos); |
416 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); | 418 | setWidth_SidebarWidget(sidebar, (float) width_Widget(root) / (float) gap_UI); |
419 | setWidth_SidebarWidget(sidebar2, (float) width_Widget(root) / (float) gap_UI); | ||
417 | } | 420 | } |
418 | return iFalse; | 421 | return iFalse; |
419 | } | 422 | } |
@@ -466,11 +469,11 @@ static void setReloadLabel_Root_(iRoot *d, iBool animating) { | |||
466 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; | 469 | const iBool isMobile = deviceType_App() != desktop_AppDeviceType; |
467 | iLabelWidget *label = findChild_Widget(d->widget, "reload"); | 470 | iLabelWidget *label = findChild_Widget(d->widget, "reload"); |
468 | updateTextCStr_LabelWidget( | 471 | updateTextCStr_LabelWidget( |
469 | label, animating ? loadAnimationCStr_() : (isMobile ? pageMenuCStr_ : reloadCStr_)); | 472 | label, animating ? loadAnimationCStr_() : (/*isMobile ? pageMenuCStr_ :*/ reloadCStr_)); |
470 | if (isMobile) { | 473 | // if (isMobile) { |
471 | setCommand_LabelWidget(label, | 474 | // setCommand_LabelWidget(label, |
472 | collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); | 475 | // collectNewCStr_String(animating ? "navigate.reload" : "menu.open")); |
473 | } | 476 | // } |
474 | } | 477 | } |
475 | 478 | ||
476 | static void checkLoadAnimation_Root_(iRoot *d) { | 479 | static void checkLoadAnimation_Root_(iRoot *d) { |
@@ -536,9 +539,12 @@ static iBool willPerformSearchQuery_(const iString *userInput) { | |||
536 | 539 | ||
537 | static void updateUrlInputContentPadding_(iWidget *navBar) { | 540 | static void updateUrlInputContentPadding_(iWidget *navBar) { |
538 | iInputWidget *url = findChild_Widget(navBar, "url"); | 541 | iInputWidget *url = findChild_Widget(navBar, "url"); |
539 | const iWidget *indicators = findChild_Widget(navBar, "url.rightembed"); | 542 | const int lockWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); |
540 | setContentPadding_InputWidget(url, -1, | 543 | const int indicatorsWidth = width_Widget(findChild_Widget(navBar, "url.rightembed")); |
541 | width_Widget(indicators)); | 544 | /* The indicators widget has a padding that covers the urlButtons area. */ |
545 | setContentPadding_InputWidget(url, | ||
546 | lockWidth - 2 * gap_UI, // * 0.75f, | ||
547 | indicatorsWidth); | ||
542 | } | 548 | } |
543 | 549 | ||
544 | static void showSearchQueryIndicator_(iBool show) { | 550 | static void showSearchQueryIndicator_(iBool show) { |
@@ -585,17 +591,17 @@ static void updateNavBarSize_(iWidget *navBar) { | |||
585 | updateSize_LabelWidget(label); | 591 | updateSize_LabelWidget(label); |
586 | } | 592 | } |
587 | } | 593 | } |
594 | updateUrlInputContentPadding_(navBar); | ||
588 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ | 595 | /* Note that InputWidget uses the `tight` flag to adjust its inner padding. */ |
589 | /* TODO: Is this redundant? See `updateMetrics_Window_()`. */ | 596 | // const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); |
590 | const int embedButtonWidth = width_Widget(findChild_Widget(navBar, "navbar.lock")); | 597 | // setContentPadding_InputWidget(findChild_Widget(navBar, "url"), |
591 | setContentPadding_InputWidget(findChild_Widget(navBar, "url"), | 598 | // embedButtonWidth * 0.75f, |
592 | embedButtonWidth * 0.75f, | 599 | // embedButtonWidth * 0.75f); |
593 | embedButtonWidth * 0.75f); | ||
594 | } | 600 | } |
595 | if (isPhone) { | 601 | if (isPhone) { |
596 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", | 602 | static const char *buttons[] = { "navbar.back", "navbar.forward", "navbar.sidebar", |
597 | "navbar.ident", "navbar.home", "navbar.menu" }; | 603 | "navbar.ident", "navbar.home", "navbar.menu" }; |
598 | iWidget *toolBar = findWidget_App("toolbar"); | 604 | iWidget *toolBar = findWidget_Root("toolbar"); |
599 | setVisualOffset_Widget(toolBar, 0, 0, 0); | 605 | setVisualOffset_Widget(toolBar, 0, 0, 0); |
600 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); | 606 | setFlags_Widget(toolBar, hidden_WidgetFlag, isLandscape_App()); |
601 | iForIndices(i, buttons) { | 607 | iForIndices(i, buttons) { |
@@ -901,15 +907,19 @@ void updateMetrics_Root(iRoot *d) { | |||
901 | iWidget *url = findChild_Widget(d->widget, "url"); | 907 | iWidget *url = findChild_Widget(d->widget, "url"); |
902 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); | 908 | iWidget *rightEmbed = findChild_Widget(navBar, "url.rightembed"); |
903 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); | 909 | iWidget *embedPad = findChild_Widget(navBar, "url.embedpad"); |
910 | iWidget *urlButtons = findChild_Widget(navBar, "url.buttons"); | ||
904 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); | 911 | setPadding_Widget(as_Widget(url), 0, gap_UI, 0, gap_UI); |
905 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ | 912 | navBar->rect.size.y = 0; /* recalculate height based on children (FIXME: shouldn't be needed) */ |
906 | updateSize_LabelWidget((iLabelWidget *) lock); | 913 | // updateSize_LabelWidget((iLabelWidget *) lock); |
907 | setFixedSize_Widget(embedPad, init_I2(width_Widget(lock) + gap_UI / 2, 1)); | 914 | // updateSize_LabelWidget((iLabelWidget *) findChild_Widget(navBar, "reload")); |
908 | setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | 915 | // arrange_Widget(urlButtons); |
909 | width_Widget(lock) * 0.75); | 916 | setFixedSize_Widget(embedPad, init_I2(width_Widget(urlButtons) + gap_UI / 2, 1)); |
917 | // setContentPadding_InputWidget((iInputWidget *) url, width_Widget(lock) * 0.75, | ||
918 | // width_Widget(lock) * 0.75); | ||
910 | rightEmbed->rect.pos.y = gap_UI; | 919 | rightEmbed->rect.pos.y = gap_UI; |
911 | updatePadding_Root(d); | 920 | updatePadding_Root(d); |
912 | arrange_Widget(d->widget); | 921 | arrange_Widget(d->widget); |
922 | updateUrlInputContentPadding_(navBar); | ||
913 | postRefresh_App(); | 923 | postRefresh_App(); |
914 | } | 924 | } |
915 | 925 | ||
@@ -1057,7 +1067,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1057 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); | 1067 | setNoAutoMinHeight_LabelWidget(fprog, iTrue); |
1058 | addChildFlags_Widget(rightEmbed, | 1068 | addChildFlags_Widget(rightEmbed, |
1059 | iClob(fprog), | 1069 | iClob(fprog), |
1060 | collapse_WidgetFlag | frameless_WidgetFlag | hidden_WidgetFlag); | 1070 | collapse_WidgetFlag | hidden_WidgetFlag | frameless_WidgetFlag); |
1061 | } | 1071 | } |
1062 | /* Download progress indicator is also inside the input field, but hidden normally. */ { | 1072 | /* Download progress indicator is also inside the input field, but hidden normally. */ { |
1063 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); | 1073 | iLabelWidget *progress = new_LabelWidget(uiTextCaution_ColorEscape "00.000 ${mb}", NULL); |
@@ -1066,7 +1076,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1066 | setAlignVisually_LabelWidget(progress, iTrue); | 1076 | setAlignVisually_LabelWidget(progress, iTrue); |
1067 | setNoAutoMinHeight_LabelWidget(progress, iTrue); | 1077 | setNoAutoMinHeight_LabelWidget(progress, iTrue); |
1068 | addChildFlags_Widget( | 1078 | addChildFlags_Widget( |
1069 | rightEmbed, iClob(progress), collapse_WidgetFlag); | 1079 | rightEmbed, iClob(progress), collapse_WidgetFlag | hidden_WidgetFlag); |
1070 | } | 1080 | } |
1071 | /* Pinning indicator. */ { | 1081 | /* Pinning indicator. */ { |
1072 | iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); | 1082 | iLabelWidget *pin = new_LabelWidget(uiTextAction_ColorEscape leftHalf_Icon, NULL); |
@@ -1076,39 +1086,44 @@ void createUserInterface_Root(iRoot *d) { | |||
1076 | setNoAutoMinHeight_LabelWidget(pin, iTrue); | 1086 | setNoAutoMinHeight_LabelWidget(pin, iTrue); |
1077 | addChildFlags_Widget(rightEmbed, | 1087 | addChildFlags_Widget(rightEmbed, |
1078 | iClob(pin), | 1088 | iClob(pin), |
1079 | collapse_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); | 1089 | collapse_WidgetFlag | hidden_WidgetFlag | tight_WidgetFlag | frameless_WidgetFlag); |
1090 | } | ||
1091 | iWidget *urlButtons = new_Widget(); | ||
1092 | setId_Widget(urlButtons, "url.buttons"); | ||
1093 | setFlags_Widget(urlButtons, embedFlags | arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
1094 | /* Mobile page menu. */ | ||
1095 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1096 | iLabelWidget *pageMenuButton; | ||
1097 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1098 | pageMenuButton = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1099 | (iMenuItem[]){ | ||
1100 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1101 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1102 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1103 | { "---", 0, 0, NULL }, | ||
1104 | { bookmark_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1105 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1106 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1107 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1108 | { "---", 0, 0, NULL }, | ||
1109 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1110 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1111 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1112 | 12); | ||
1113 | setId_Widget(as_Widget(pageMenuButton), "pagemenubutton"); | ||
1114 | setFont_LabelWidget(pageMenuButton, uiContentBold_FontId); | ||
1115 | setAlignVisually_LabelWidget(pageMenuButton, iTrue); | ||
1116 | addChildFlags_Widget(urlButtons, iClob(pageMenuButton), embedFlags); | ||
1117 | updateSize_LabelWidget(pageMenuButton); | ||
1080 | } | 1118 | } |
1081 | /* Reload button. */ { | 1119 | /* Reload button. */ { |
1082 | iLabelWidget *reload; | 1120 | iLabelWidget *reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); |
1083 | if (deviceType_App() == desktop_AppDeviceType) { | ||
1084 | reload = newIcon_LabelWidget(reloadCStr_, 0, 0, "navigate.reload"); | ||
1085 | } | ||
1086 | else { | ||
1087 | /* In a mobile layout, the reload button is replaced with the Page/Ellipsis menu. */ | ||
1088 | reload = makeMenuButton_LabelWidget(pageMenuCStr_, | ||
1089 | (iMenuItem[]){ | ||
1090 | { reload_Icon " ${menu.reload}", reload_KeyShortcut, "navigate.reload" }, | ||
1091 | { timer_Icon " ${menu.autoreload}", 0, 0, "document.autoreload.menu" }, | ||
1092 | { "---", 0, 0, NULL }, | ||
1093 | { upArrow_Icon " ${menu.parent}", navigateParent_KeyShortcut, "navigate.parent" }, | ||
1094 | { upArrowBar_Icon " ${menu.root}", navigateRoot_KeyShortcut, "navigate.root" }, | ||
1095 | { "---", 0, 0, NULL }, | ||
1096 | { pin_Icon " ${menu.page.bookmark}", SDLK_d, KMOD_PRIMARY, "bookmark.add" }, | ||
1097 | { star_Icon " ${menu.page.subscribe}", subscribeToPage_KeyModifier, "feeds.subscribe" }, | ||
1098 | { book_Icon " ${menu.page.import}", 0, 0, "bookmark.links confirm:1" }, | ||
1099 | { globe_Icon " ${menu.page.translate}", 0, 0, "document.translate" }, | ||
1100 | { "---", 0, 0, NULL }, | ||
1101 | { "${menu.page.copyurl}", 0, 0, "document.copylink" }, | ||
1102 | { "${menu.page.copysource}", 'c', KMOD_PRIMARY, "copy" }, | ||
1103 | { download_Icon " " saveToDownloads_Label, SDLK_s, KMOD_PRIMARY, "document.save" } }, | ||
1104 | 14); | ||
1105 | setFont_LabelWidget((iLabelWidget *) reload, uiContentBold_FontId); | ||
1106 | setAlignVisually_LabelWidget((iLabelWidget *) reload, iTrue); | ||
1107 | } | ||
1108 | setId_Widget(as_Widget(reload), "reload"); | 1121 | setId_Widget(as_Widget(reload), "reload"); |
1109 | addChildFlags_Widget(as_Widget(url), iClob(reload), embedFlags | moveToParentRightEdge_WidgetFlag); | 1122 | addChildFlags_Widget(urlButtons, iClob(reload), embedFlags); |
1110 | updateSize_LabelWidget(reload); | 1123 | updateSize_LabelWidget(reload); |
1111 | } | 1124 | } |
1125 | addChildFlags_Widget(as_Widget(url), iClob(urlButtons), moveToParentRightEdge_WidgetFlag); | ||
1126 | arrange_Widget(urlButtons); | ||
1112 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); | 1127 | setId_Widget(addChild_Widget(rightEmbed, iClob(makePadding_Widget(0))), "url.embedpad"); |
1113 | } | 1128 | } |
1114 | if (deviceType_App() != desktop_AppDeviceType) { | 1129 | if (deviceType_App() != desktop_AppDeviceType) { |
@@ -1216,7 +1231,9 @@ void createUserInterface_Root(iRoot *d) { | |||
1216 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | | 1231 | setFlags_Widget(toolBar, moveToParentBottomEdge_WidgetFlag | |
1217 | parentCannotResizeHeight_WidgetFlag | | 1232 | parentCannotResizeHeight_WidgetFlag | |
1218 | resizeWidthOfChildren_WidgetFlag | | 1233 | resizeWidthOfChildren_WidgetFlag | |
1219 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | 1234 | arrangeHeight_WidgetFlag | arrangeHorizontal_WidgetFlag | |
1235 | commandOnClick_WidgetFlag | | ||
1236 | drawBackgroundToBottom_WidgetFlag, iTrue); | ||
1220 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); | 1237 | setBackgroundColor_Widget(toolBar, tmBannerBackground_ColorId); |
1221 | setId_Widget(addChildFlags_Widget(toolBar, | 1238 | setId_Widget(addChildFlags_Widget(toolBar, |
1222 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), | 1239 | iClob(newLargeIcon_LabelWidget("\U0001f870", "navigate.back")), |
@@ -1231,7 +1248,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1231 | frameless_WidgetFlag), | 1248 | frameless_WidgetFlag), |
1232 | "toolbar.ident"); | 1249 | "toolbar.ident"); |
1233 | setId_Widget(addChildFlags_Widget(toolBar, | 1250 | setId_Widget(addChildFlags_Widget(toolBar, |
1234 | iClob(newLargeIcon_LabelWidget("\U0001f588", "toolbar.showview arg:-1")), | 1251 | iClob(newLargeIcon_LabelWidget(book_Icon, "toolbar.showview arg:-1")), |
1235 | frameless_WidgetFlag | commandOnClick_WidgetFlag), | 1252 | frameless_WidgetFlag | commandOnClick_WidgetFlag), |
1236 | "toolbar.view"); | 1253 | "toolbar.view"); |
1237 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, | 1254 | iLabelWidget *menuButton = makeMenuButton_LabelWidget("\U0001d362", phoneNavMenuItems_, |
@@ -1244,17 +1261,17 @@ void createUserInterface_Root(iRoot *d) { | |||
1244 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); | 1261 | setFlags_Widget(i.object, noBackground_WidgetFlag, iTrue); |
1245 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); | 1262 | setTextColor_LabelWidget(i.object, tmBannerIcon_ColorId); |
1246 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); | 1263 | // setBackgroundColor_Widget(i.object, tmBannerSideTitle_ColorId); |
1247 | } | 1264 | } |
1248 | const iMenuItem items[] = { | 1265 | const iMenuItem items[] = { |
1249 | { pin_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, | 1266 | { book_Icon " ${sidebar.bookmarks}", 0, 0, "toolbar.showview arg:0" }, |
1250 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, | 1267 | { star_Icon " ${sidebar.feeds}", 0, 0, "toolbar.showview arg:1" }, |
1251 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, | 1268 | { clock_Icon " ${sidebar.history}", 0, 0, "toolbar.showview arg:2" }, |
1252 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, | 1269 | { page_Icon " ${toolbar.outline}", 0, 0, "toolbar.showview arg:4" }, |
1253 | }; | 1270 | }; |
1254 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), | 1271 | iWidget *menu = makeMenu_Widget(findChild_Widget(toolBar, "toolbar.view"), |
1255 | items, iElemCount(items)); | 1272 | items, iElemCount(items)); |
1256 | setId_Widget(menu, "toolbar.menu"); /* view menu */ | 1273 | setId_Widget(menu, "toolbar.menu"); /* view menu */ |
1257 | } | 1274 | } |
1258 | #endif | 1275 | #endif |
1259 | updatePadding_Root(d); | 1276 | updatePadding_Root(d); |
1260 | /* Global context menus. */ { | 1277 | /* Global context menus. */ { |
@@ -1319,6 +1336,11 @@ void createUserInterface_Root(iRoot *d) { | |||
1319 | } | 1336 | } |
1320 | updateMetrics_Root(d); | 1337 | updateMetrics_Root(d); |
1321 | updateNavBarSize_(navBar); | 1338 | updateNavBarSize_(navBar); |
1339 | if (deviceType_App() == phone_AppDeviceType) { | ||
1340 | const float sidebarWidth = width_Widget(root) / (float) gap_UI; | ||
1341 | setWidth_SidebarWidget(findChild_Widget(root, "sidebar"), sidebarWidth); | ||
1342 | setWidth_SidebarWidget(findChild_Widget(root, "sidebar2"), sidebarWidth); | ||
1343 | } | ||
1322 | } | 1344 | } |
1323 | 1345 | ||
1324 | void showToolbars_Root(iRoot *d, iBool show) { | 1346 | void showToolbars_Root(iRoot *d, iBool show) { |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index 32b57c69..ff5144b2 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -108,7 +108,7 @@ static void unfade_ScrollWidget_(iScrollWidget *d, float opacity) { | |||
108 | d->fadeStart = SDL_GetTicks() + 1000; | 108 | d->fadeStart = SDL_GetTicks() + 1000; |
109 | if (targetValue_Anim(&d->opacity) < opacity) { | 109 | if (targetValue_Anim(&d->opacity) < opacity) { |
110 | setValue_Anim(&d->opacity, opacity, 66); | 110 | setValue_Anim(&d->opacity, opacity, 66); |
111 | addTicker_App(animateOpacity_ScrollWidget_, d); | 111 | addTickerRoot_App(animateOpacity_ScrollWidget_, as_Widget(d)->root, d); |
112 | } | 112 | } |
113 | if (!d->willCheckFade && d->fadeEnabled) { | 113 | if (!d->willCheckFade && d->fadeEnabled) { |
114 | d->willCheckFade = iTrue; | 114 | d->willCheckFade = iTrue; |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index eae0432f..cb6dab65 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -106,7 +106,8 @@ struct Impl_SidebarWidget { | |||
106 | size_t numUnreadEntries; | 106 | size_t numUnreadEntries; |
107 | iWidget * resizer; | 107 | iWidget * resizer; |
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 | }; | 111 | }; |
111 | 112 | ||
112 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) | 113 | iDefineObjectConstructionArgs(SidebarWidget, (enum iSidebarSide side), side) |
@@ -239,7 +240,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
239 | as_Widget(d), | 240 | as_Widget(d), |
240 | (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, | 241 | (iMenuItem[]){ { openTab_Icon " ${feeds.entry.newtab}", 0, 0, "feed.entry.opentab" }, |
241 | { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, | 242 | { circle_Icon " ${feeds.entry.markread}", 0, 0, "feed.entry.toggleread" }, |
242 | { pin_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, | 243 | { bookmark_Icon " ${feeds.entry.bookmark}", 0, 0, "feed.entry.bookmark" }, |
243 | { "---", 0, 0, NULL }, | 244 | { "---", 0, 0, NULL }, |
244 | { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, | 245 | { page_Icon " ${feeds.entry.openfeed}", 0, 0, "feed.entry.openfeed" }, |
245 | { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, | 246 | { edit_Icon " ${feeds.edit}", 0, 0, "feed.entry.edit" }, |
@@ -364,7 +365,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
364 | as_Widget(d), | 365 | as_Widget(d), |
365 | (iMenuItem[]){ | 366 | (iMenuItem[]){ |
366 | { "${menu.copyurl}", 0, 0, "history.copy" }, | 367 | { "${menu.copyurl}", 0, 0, "history.copy" }, |
367 | { pin_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, | 368 | { bookmark_Icon " ${sidebar.entry.bookmark}", 0, 0, "history.addbookmark" }, |
368 | { "---", 0, 0, NULL }, | 369 | { "---", 0, 0, NULL }, |
369 | { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, | 370 | { close_Icon " ${menu.forgeturl}", 0, 0, "history.delete" }, |
370 | { "---", 0, 0, NULL }, | 371 | { "---", 0, 0, NULL }, |
@@ -373,10 +374,6 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
373 | break; | 374 | break; |
374 | } | 375 | } |
375 | case identities_SidebarMode: { | 376 | case identities_SidebarMode: { |
376 | /* Actions. */ { | ||
377 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | ||
378 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); | ||
379 | } | ||
380 | const iString *tabUrl = url_DocumentWidget(document_App()); | 377 | const iString *tabUrl = url_DocumentWidget(document_App()); |
381 | isEmpty = iTrue; | 378 | isEmpty = iTrue; |
382 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | 379 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { |
@@ -413,6 +410,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
413 | iRelease(item); | 410 | iRelease(item); |
414 | isEmpty = iFalse; | 411 | isEmpty = iFalse; |
415 | } | 412 | } |
413 | /* Actions. */ | ||
414 | if (!isEmpty) { | ||
415 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | ||
416 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); | ||
417 | } | ||
416 | const iMenuItem menuItems[] = { | 418 | const iMenuItem menuItems[] = { |
417 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | 419 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, |
418 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | 420 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, |
@@ -520,15 +522,15 @@ float width_SidebarWidget(const iSidebarWidget *d) { | |||
520 | } | 522 | } |
521 | 523 | ||
522 | static const char *normalModeLabels_[max_SidebarMode] = { | 524 | static const char *normalModeLabels_[max_SidebarMode] = { |
523 | pin_Icon " ${sidebar.bookmarks}", | 525 | book_Icon " ${sidebar.bookmarks}", |
524 | star_Icon " ${sidebar.feeds}", | 526 | star_Icon " ${sidebar.feeds}", |
525 | clock_Icon " ${sidebar.history}", | 527 | clock_Icon " ${sidebar.history}", |
526 | person_Icon " ${sidebar.identities}", | 528 | person_Icon " ${sidebar.identities}", |
527 | page_Icon " ${sidebar.outline}", | 529 | page_Icon " ${sidebar.outline}", |
528 | }; | 530 | }; |
529 | 531 | ||
530 | static const char *tightModeLabels_[max_SidebarMode] = { | 532 | static const char *tightModeLabels_[max_SidebarMode] = { |
531 | pin_Icon, | 533 | book_Icon, |
532 | star_Icon, | 534 | star_Icon, |
533 | clock_Icon, | 535 | clock_Icon, |
534 | person_Icon, | 536 | person_Icon, |
@@ -576,11 +578,11 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
576 | d->itemFonts[0] = uiContent_FontId; | 578 | d->itemFonts[0] = uiContent_FontId; |
577 | d->itemFonts[1] = uiContentBold_FontId; | 579 | d->itemFonts[1] = uiContentBold_FontId; |
578 | #if defined (iPlatformAppleMobile) | 580 | #if defined (iPlatformAppleMobile) |
579 | d->widthAsGaps = 73; | ||
580 | if (deviceType_App() == phone_AppDeviceType) { | 581 | if (deviceType_App() == phone_AppDeviceType) { |
581 | d->itemFonts[0] = defaultBig_FontId; | 582 | d->itemFonts[0] = defaultBig_FontId; |
582 | d->itemFonts[1] = defaultBigBold_FontId; | 583 | d->itemFonts[1] = defaultBigBold_FontId; |
583 | } | 584 | } |
585 | d->widthAsGaps = 73; | ||
584 | #else | 586 | #else |
585 | d->widthAsGaps = 60; | 587 | d->widthAsGaps = 60; |
586 | #endif | 588 | #endif |
@@ -644,6 +646,7 @@ void init_SidebarWidget(iSidebarWidget *d, enum iSidebarSide side) { | |||
644 | "actions"); | 646 | "actions"); |
645 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); | 647 | setBackgroundColor_Widget(d->actions, uiBackgroundSidebar_ColorId); |
646 | d->contextItem = NULL; | 648 | d->contextItem = NULL; |
649 | d->contextIndex = iInvalidPos; | ||
647 | d->blank = new_Widget(); | 650 | d->blank = new_Widget(); |
648 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); | 651 | addChildFlags_Widget(content, iClob(d->blank), resizeChildren_WidgetFlag); |
649 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); | 652 | addChildFlags_Widget(vdiv, iClob(content), expand_WidgetFlag); |
@@ -770,6 +773,7 @@ static void checkModeButtonLayout_SidebarWidget_(iSidebarWidget *d) { | |||
770 | for (int i = 0; i < max_SidebarMode; i++) { | 773 | for (int i = 0; i < max_SidebarMode; i++) { |
771 | iLabelWidget *button = d->modeButtons[i]; | 774 | iLabelWidget *button = d->modeButtons[i]; |
772 | if (!button) continue; | 775 | if (!button) continue; |
776 | setAlignVisually_LabelWidget(button, isTight); | ||
773 | setFlags_Widget(as_Widget(button), tight_WidgetFlag, isTight); | 777 | setFlags_Widget(as_Widget(button), tight_WidgetFlag, isTight); |
774 | if (i == feeds_SidebarMode && d->numUnreadEntries) { | 778 | if (i == feeds_SidebarMode && d->numUnreadEntries) { |
775 | updateText_LabelWidget( | 779 | updateText_LabelWidget( |
@@ -803,7 +807,7 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) { | |||
803 | if (isVisible_Widget(w)) { | 807 | if (isVisible_Widget(w)) { |
804 | w->rect.size.x = width; | 808 | w->rect.size.x = width; |
805 | } | 809 | } |
806 | arrange_Widget(findWidget_App("stack")); | 810 | arrange_Widget(findWidget_Root("stack")); |
807 | checkModeButtonLayout_SidebarWidget_(d); | 811 | checkModeButtonLayout_SidebarWidget_(d); |
808 | updateItemHeight_SidebarWidget_(d); | 812 | updateItemHeight_SidebarWidget_(d); |
809 | if (!isFixedWidth && !isRefreshPending_App()) { | 813 | if (!isFixedWidth && !isRefreshPending_App()) { |
@@ -844,7 +848,6 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c | |||
844 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); | 848 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); |
845 | postCommand_App("bookmarks.changed"); | 849 | postCommand_App("bookmarks.changed"); |
846 | } | 850 | } |
847 | setFlags_Widget(as_Widget(d), disabled_WidgetFlag, iFalse); | ||
848 | destroy_Widget(editor); | 851 | destroy_Widget(editor); |
849 | return iTrue; | 852 | return iTrue; |
850 | } | 853 | } |
@@ -875,10 +878,33 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
875 | if (arg_Command(cmd) && isVisible_Widget(w)) { | 878 | if (arg_Command(cmd) && isVisible_Widget(w)) { |
876 | return iTrue; | 879 | return iTrue; |
877 | } | 880 | } |
881 | const iBool isAnimated = argLabel_Command(cmd, "noanim") == 0 && | ||
882 | (deviceType_App() != phone_AppDeviceType); | ||
883 | int visX = 0; | ||
884 | if (isVisible_Widget(w)) { | ||
885 | visX = left_Rect(bounds_Widget(w)) - left_Rect(w->root->widget->rect); | ||
886 | } | ||
878 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); | 887 | setFlags_Widget(w, hidden_WidgetFlag, isVisible_Widget(w)); |
879 | if (isVisible_Widget(w)) { | 888 | if (isVisible_Widget(w)) { |
889 | setFlags_Widget(w, keepOnTop_WidgetFlag, iFalse); | ||
880 | w->rect.size.x = d->widthAsGaps * gap_UI; | 890 | w->rect.size.x = d->widthAsGaps * gap_UI; |
881 | invalidate_ListWidget(d->list); | 891 | invalidate_ListWidget(d->list); |
892 | if (isAnimated) { | ||
893 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | ||
894 | setVisualOffset_Widget(w, (d->side == left_SideBarSide ? -1 : 1) * w->rect.size.x, 0, 0); | ||
895 | setVisualOffset_Widget(w, 0, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
896 | } | ||
897 | } | ||
898 | else if (isAnimated) { | ||
899 | setFlags_Widget(w, horizontalOffset_WidgetFlag, iTrue); | ||
900 | if (d->side == right_SideBarSide) { | ||
901 | setVisualOffset_Widget(w, visX, 0, 0); | ||
902 | setVisualOffset_Widget(w, visX + w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
903 | } | ||
904 | else { | ||
905 | setFlags_Widget(w, keepOnTop_WidgetFlag, iTrue); | ||
906 | setVisualOffset_Widget(w, -w->rect.size.x, 300, easeOut_AnimFlag | softer_AnimFlag); | ||
907 | } | ||
882 | } | 908 | } |
883 | arrange_Widget(w->parent); | 909 | arrange_Widget(w->parent); |
884 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ | 910 | /* BUG: Rearranging because the arrange above didn't fully resolve the height. */ |
@@ -985,11 +1011,8 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
985 | itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item")); | 1011 | itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item")); |
986 | return iTrue; | 1012 | return iTrue; |
987 | } | 1013 | } |
988 | else if (isCommand_Widget(w, ev, "menu.opened")) { | ||
989 | setFlags_Widget(as_Widget(d->list), disabled_WidgetFlag, iTrue); | ||
990 | } | ||
991 | else if (isCommand_Widget(w, ev, "menu.closed")) { | 1014 | else if (isCommand_Widget(w, ev, "menu.closed")) { |
992 | setFlags_Widget(as_Widget(d->list), disabled_WidgetFlag, iFalse); | 1015 | // invalidateItem_ListWidget(d->list, d->contextIndex); |
993 | } | 1016 | } |
994 | else if (isCommand_Widget(w, ev, "bookmark.open")) { | 1017 | else if (isCommand_Widget(w, ev, "bookmark.open")) { |
995 | const iSidebarItem *item = d->contextItem; | 1018 | const iSidebarItem *item = d->contextItem; |
@@ -1010,7 +1033,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1010 | else if (isCommand_Widget(w, ev, "bookmark.edit")) { | 1033 | else if (isCommand_Widget(w, ev, "bookmark.edit")) { |
1011 | const iSidebarItem *item = d->contextItem; | 1034 | const iSidebarItem *item = d->contextItem; |
1012 | if (d->mode == bookmarks_SidebarMode && item) { | 1035 | if (d->mode == bookmarks_SidebarMode && item) { |
1013 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1014 | iWidget *dlg = makeBookmarkEditor_Widget(); | 1036 | iWidget *dlg = makeBookmarkEditor_Widget(); |
1015 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); | 1037 | setId_Widget(dlg, format_CStr("bmed.%s", cstr_String(id_Widget(w)))); |
1016 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1038 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
@@ -1038,7 +1060,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1038 | else if (isCommand_Widget(w, ev, "bookmark.dup")) { | 1060 | else if (isCommand_Widget(w, ev, "bookmark.dup")) { |
1039 | const iSidebarItem *item = d->contextItem; | 1061 | const iSidebarItem *item = d->contextItem; |
1040 | if (d->mode == bookmarks_SidebarMode && item) { | 1062 | if (d->mode == bookmarks_SidebarMode && item) { |
1041 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1042 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); | 1063 | iBookmark *bm = get_Bookmarks(bookmarks_App(), item->id); |
1043 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); | 1064 | const iBool isRemote = hasTag_Bookmark(bm, remote_BookmarkTag); |
1044 | iChar icon = isRemote ? 0x1f588 : bm->icon; | 1065 | iChar icon = isRemote ? 0x1f588 : bm->icon; |
@@ -1131,7 +1152,6 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1131 | return iTrue; | 1152 | return iTrue; |
1132 | } | 1153 | } |
1133 | if (isCommand_Widget(w, ev, "feed.entry.edit")) { | 1154 | if (isCommand_Widget(w, ev, "feed.entry.edit")) { |
1134 | setFlags_Widget(w, disabled_WidgetFlag, iTrue); | ||
1135 | makeFeedSettings_Widget(id_Bookmark(feedBookmark)); | 1155 | makeFeedSettings_Widget(id_Bookmark(feedBookmark)); |
1136 | return iTrue; | 1156 | return iTrue; |
1137 | } | 1157 | } |
@@ -1307,6 +1327,10 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1307 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); | 1327 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); |
1308 | } | 1328 | } |
1309 | } | 1329 | } |
1330 | if (d->contextIndex != iInvalidPos) { | ||
1331 | invalidateItem_ListWidget(d->list, d->contextIndex); | ||
1332 | d->contextIndex = iInvalidPos; | ||
1333 | } | ||
1310 | } | 1334 | } |
1311 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { | 1335 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { |
1312 | if (ev->button.button == SDL_BUTTON_RIGHT) { | 1336 | if (ev->button.button == SDL_BUTTON_RIGHT) { |
@@ -1315,7 +1339,12 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1315 | updateMouseHover_ListWidget(d->list); | 1339 | updateMouseHover_ListWidget(d->list); |
1316 | } | 1340 | } |
1317 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { | 1341 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { |
1318 | d->contextItem = hoverItem_ListWidget(d->list); | 1342 | d->contextItem = hoverItem_ListWidget(d->list); |
1343 | /* Context is drawn in hover state. */ | ||
1344 | if (d->contextIndex != iInvalidPos) { | ||
1345 | invalidateItem_ListWidget(d->list, d->contextIndex); | ||
1346 | } | ||
1347 | d->contextIndex = hoverItemIndex_ListWidget(d->list); | ||
1319 | /* Update menu items. */ | 1348 | /* Update menu items. */ |
1320 | /* TODO: Some callback-based mechanism would be nice for updating menus right | 1349 | /* TODO: Some callback-based mechanism would be nice for updating menus right |
1321 | before they open? */ | 1350 | before they open? */ |
@@ -1427,18 +1456,30 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1427 | const iRect bounds = bounds_Widget(w); | 1456 | const iRect bounds = bounds_Widget(w); |
1428 | iPaint p; | 1457 | iPaint p; |
1429 | init_Paint(&p); | 1458 | init_Paint(&p); |
1459 | if (flags_Widget(w) & visualOffset_WidgetFlag && isVisible_Widget(w)) { | ||
1460 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | ||
1461 | } | ||
1430 | draw_Widget(w); | 1462 | draw_Widget(w); |
1431 | drawVLine_Paint( | 1463 | if (isVisible_Widget(w)) { |
1432 | &p, addX_I2(topRight_Rect(bounds), -1), height_Rect(bounds), uiSeparator_ColorId); | 1464 | drawVLine_Paint( |
1465 | &p, | ||
1466 | addX_I2(d->side == left_SideBarSide ? topRight_Rect(bounds) : topLeft_Rect(bounds), -1), | ||
1467 | height_Rect(bounds), | ||
1468 | uiSeparator_ColorId); | ||
1469 | } | ||
1433 | } | 1470 | } |
1434 | 1471 | ||
1435 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | 1472 | static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, |
1436 | const iListWidget *list) { | 1473 | const iListWidget *list) { |
1437 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), | 1474 | const iSidebarWidget *sidebar = findParentClass_Widget(constAs_Widget(list), |
1438 | &Class_SidebarWidget); | 1475 | &Class_SidebarWidget); |
1476 | const iBool isMenuVisible = isVisible_Widget(sidebar->menu); | ||
1439 | const iBool isPressing = isMouseDown_ListWidget(list); | 1477 | const iBool isPressing = isMouseDown_ListWidget(list); |
1440 | const iBool isHover = isHover_Widget(constAs_Widget(list)) && | 1478 | const iBool isHover = |
1441 | constHoverItem_ListWidget(list) == d; | 1479 | (!isMenuVisible && |
1480 | isHover_Widget(constAs_Widget(list)) && | ||
1481 | constHoverItem_ListWidget(list) == d) || | ||
1482 | (isMenuVisible && sidebar->contextItem == d); | ||
1442 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); | 1483 | const int scrollBarWidth = scrollBarWidth_ListWidget(list); |
1443 | #if defined (iPlatformApple) | 1484 | #if defined (iPlatformApple) |
1444 | const int blankWidth = 0; | 1485 | const int blankWidth = 0; |
@@ -1652,7 +1693,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1652 | drawRange_Text( | 1693 | drawRange_Text( |
1653 | font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); | 1694 | font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); |
1654 | deinit_String(&icon); | 1695 | deinit_String(&icon); |
1655 | drawRange_Text(d->listItem.isSelected ? uiContentBold_FontId : font, | 1696 | drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font, |
1656 | add_I2(cPos, init_I2(indent, 0)), | 1697 | add_I2(cPos, init_I2(indent, 0)), |
1657 | fg, | 1698 | fg, |
1658 | range_String(&d->label)); | 1699 | range_String(&d->label)); |
diff --git a/src/ui/text.c b/src/ui/text.c index 9838fb00..889aa2e4 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -781,6 +781,7 @@ enum iRunMode { | |||
781 | permanentColorFlag_RunMode = iBit(11), | 781 | permanentColorFlag_RunMode = iBit(11), |
782 | alwaysVariableWidthFlag_RunMode = iBit(12), | 782 | alwaysVariableWidthFlag_RunMode = iBit(12), |
783 | fillBackground_RunMode = iBit(13), | 783 | fillBackground_RunMode = iBit(13), |
784 | stopAtNewline_RunMode = iBit(14), /* don't advance past \n, consider it a wrap pos */ | ||
784 | }; | 785 | }; |
785 | 786 | ||
786 | static enum iFontId fontId_Text_(const iFont *font) { | 787 | static enum iFontId fontId_Text_(const iFont *font) { |
@@ -923,7 +924,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) { | |||
923 | } | 924 | } |
924 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ | 925 | /* TODO: Check out if `uc_wordbreak_property()` from libunistring can be used here. */ |
925 | if (ch == '\n') { | 926 | if (ch == '\n') { |
926 | if (args->xposLimit > 0 && ~mode & noWrapFlag_RunMode) { | 927 | if (args->xposLimit > 0 && mode & stopAtNewline_RunMode) { |
927 | /* Stop the line here, this is a hard warp. */ | 928 | /* Stop the line here, this is a hard warp. */ |
928 | if (args->continueFrom_out) { | 929 | if (args->continueFrom_out) { |
929 | *args->continueFrom_out = chPos; | 930 | *args->continueFrom_out = chPos; |
@@ -1148,7 +1149,8 @@ iInt2 advanceRange_Text(int fontId, iRangecc text) { | |||
1148 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { | 1149 | iInt2 tryAdvance_Text(int fontId, iRangecc text, int width, const char **endPos) { |
1149 | int advance; | 1150 | int advance; |
1150 | const int height = run_Font_(font_Text_(fontId), | 1151 | const int height = run_Font_(font_Text_(fontId), |
1151 | &(iRunArgs){ .mode = measure_RunMode | runFlagsFromId_(fontId), | 1152 | &(iRunArgs){ .mode = measure_RunMode | stopAtNewline_RunMode | |
1153 | runFlagsFromId_(fontId), | ||
1152 | .text = text, | 1154 | .text = text, |
1153 | .xposLimit = width, | 1155 | .xposLimit = width, |
1154 | .continueFrom_out = endPos, | 1156 | .continueFrom_out = endPos, |
@@ -1161,6 +1163,7 @@ iInt2 tryAdvanceNoWrap_Text(int fontId, iRangecc text, int width, const char **e | |||
1161 | int advance; | 1163 | int advance; |
1162 | const int height = run_Font_(font_Text_(fontId), | 1164 | const int height = run_Font_(font_Text_(fontId), |
1163 | &(iRunArgs){ .mode = measure_RunMode | noWrapFlag_RunMode | | 1165 | &(iRunArgs){ .mode = measure_RunMode | noWrapFlag_RunMode | |
1166 | stopAtNewline_RunMode | | ||
1164 | runFlagsFromId_(fontId), | 1167 | runFlagsFromId_(fontId), |
1165 | .text = text, | 1168 | .text = text, |
1166 | .xposLimit = width, | 1169 | .xposLimit = width, |
@@ -1269,7 +1272,7 @@ int drawWrapRange_Text(int fontId, iInt2 pos, int maxWidth, int color, iRangecc | |||
1269 | const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); | 1272 | const iInt2 adv = tryAdvance_Text(fontId, text, maxWidth, &endp); |
1270 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); | 1273 | drawRange_Text(fontId, pos, color, (iRangecc){ text.start, endp }); |
1271 | text.start = endp; | 1274 | text.start = endp; |
1272 | pos.y += adv.y; | 1275 | pos.y += iMax(adv.y, lineHeight_Text(fontId)); |
1273 | } | 1276 | } |
1274 | return pos.y; | 1277 | return pos.y; |
1275 | } | 1278 | } |
@@ -1404,9 +1407,21 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo | |||
1404 | 1407 | ||
1405 | iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), font, color, text) | 1408 | iDefineTypeConstructionArgs(TextBuf, (int font, int color, const char *text), font, color, text) |
1406 | 1409 | ||
1407 | void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | 1410 | static void initWrap_TextBuf_(iTextBuf *d, int font, int color, int maxWidth, iBool doWrap, const char *text) { |
1408 | SDL_Renderer *render = text_.render; | 1411 | SDL_Renderer *render = text_.render; |
1409 | d->size = advance_Text(font, text); | 1412 | if (maxWidth == 0) { |
1413 | d->size = advance_Text(font, text); | ||
1414 | } | ||
1415 | else { | ||
1416 | d->size = zero_I2(); | ||
1417 | iRangecc content = range_CStr(text); | ||
1418 | while (!isEmpty_Range(&content)) { | ||
1419 | const iInt2 size = (doWrap ? tryAdvance_Text(font, content, maxWidth, &content.start) | ||
1420 | : tryAdvanceNoWrap_Text(font, content, maxWidth, &content.start)); | ||
1421 | d->size.x = iMax(d->size.x, size.x); | ||
1422 | d->size.y += iMax(size.y, lineHeight_Text(font)); | ||
1423 | } | ||
1424 | } | ||
1410 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); | 1425 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); |
1411 | if (d->size.x * d->size.y) { | 1426 | if (d->size.x * d->size.y) { |
1412 | d->texture = SDL_CreateTexture(render, | 1427 | d->texture = SDL_CreateTexture(render, |
@@ -1424,17 +1439,50 @@ void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | |||
1424 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ | 1439 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ |
1425 | SDL_SetRenderDrawColor(text_.render, 0, 0, 0, 0); | 1440 | SDL_SetRenderDrawColor(text_.render, 0, 0, 0, 0); |
1426 | SDL_RenderClear(text_.render); | 1441 | SDL_RenderClear(text_.render); |
1427 | draw_Text_(font, zero_I2(), color | fillBackground_ColorId, range_CStr(text)); | 1442 | const int fg = color | fillBackground_ColorId; |
1443 | iRangecc range = range_CStr(text); | ||
1444 | if (maxWidth == 0) { | ||
1445 | draw_Text_(font, zero_I2(), fg, range); | ||
1446 | } | ||
1447 | else if (doWrap) { | ||
1448 | drawWrapRange_Text(font, zero_I2(), maxWidth, fg, range); | ||
1449 | } | ||
1450 | else { | ||
1451 | iInt2 pos = zero_I2(); | ||
1452 | while (!isEmpty_Range(&range)) { | ||
1453 | const char *endp; | ||
1454 | tryAdvanceNoWrap_Text(font, range, maxWidth, &endp); | ||
1455 | draw_Text_(font, pos, fg, (iRangecc){ range.start, endp }); | ||
1456 | range.start = endp; | ||
1457 | pos.y += lineHeight_Text(font); | ||
1458 | } | ||
1459 | } | ||
1428 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); | 1460 | SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); |
1429 | SDL_SetRenderTarget(render, oldTarget); | 1461 | SDL_SetRenderTarget(render, oldTarget); |
1430 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); | 1462 | SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); |
1431 | } | 1463 | } |
1432 | } | 1464 | } |
1433 | 1465 | ||
1466 | void init_TextBuf(iTextBuf *d, int font, int color, const char *text) { | ||
1467 | initWrap_TextBuf_(d, font, color, 0, iFalse, text); | ||
1468 | } | ||
1469 | |||
1434 | void deinit_TextBuf(iTextBuf *d) { | 1470 | void deinit_TextBuf(iTextBuf *d) { |
1435 | SDL_DestroyTexture(d->texture); | 1471 | SDL_DestroyTexture(d->texture); |
1436 | } | 1472 | } |
1437 | 1473 | ||
1474 | iTextBuf *newBound_TextBuf(int font, int color, int boundWidth, const char *text) { | ||
1475 | iTextBuf *d = iMalloc(TextBuf); | ||
1476 | initWrap_TextBuf_(d, font, color, boundWidth, iFalse, text); | ||
1477 | return d; | ||
1478 | } | ||
1479 | |||
1480 | iTextBuf *newWrap_TextBuf(int font, int color, int wrapWidth, const char *text) { | ||
1481 | iTextBuf *d = iMalloc(TextBuf); | ||
1482 | initWrap_TextBuf_(d, font, color, wrapWidth, iTrue, text); | ||
1483 | return d; | ||
1484 | } | ||
1485 | |||
1438 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { | 1486 | void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) { |
1439 | const iColor clr = get_Color(color); | 1487 | const iColor clr = get_Color(color); |
1440 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); | 1488 | SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); |
diff --git a/src/ui/text.h b/src/ui/text.h index c6091599..044ddd32 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -170,10 +170,13 @@ iString * renderBlockChars_Text (const iBlock *fontData, int height, enum iT | |||
170 | 170 | ||
171 | iDeclareType(TextBuf) | 171 | iDeclareType(TextBuf) |
172 | iDeclareTypeConstructionArgs(TextBuf, int font, int color, const char *text) | 172 | iDeclareTypeConstructionArgs(TextBuf, int font, int color, const char *text) |
173 | 173 | ||
174 | struct Impl_TextBuf { | 174 | struct Impl_TextBuf { |
175 | SDL_Texture *texture; | 175 | SDL_Texture *texture; |
176 | iInt2 size; | 176 | iInt2 size; |
177 | }; | 177 | }; |
178 | 178 | ||
179 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); | 179 | iTextBuf * newBound_TextBuf(int font, int color, int boundWidth, const char *text); /* does not word wrap */ |
180 | iTextBuf * newWrap_TextBuf (int font, int color, int wrapWidth, const char *text); | ||
181 | |||
182 | void draw_TextBuf (const iTextBuf *, iInt2 pos, int color); | ||
diff --git a/src/ui/touch.c b/src/ui/touch.c index b2c52526..74a22baf 100644 --- a/src/ui/touch.c +++ b/src/ui/touch.c | |||
@@ -227,7 +227,7 @@ static void dispatchButtonUp_Touch_(iFloat3 pos) { | |||
227 | 227 | ||
228 | static void dispatchNotification_Touch_(const iTouch *d, int code) { | 228 | static void dispatchNotification_Touch_(const iTouch *d, int code) { |
229 | if (d->affinity) { | 229 | if (d->affinity) { |
230 | iRoot *oldRoot = get_Root(); | 230 | iRoot *oldRoot = current_Root(); |
231 | setCurrent_Root(d->affinity->root); | 231 | setCurrent_Root(d->affinity->root); |
232 | dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ | 232 | dispatchEvent_Widget(d->affinity, (SDL_Event *) &(SDL_UserEvent){ |
233 | .type = SDL_USEREVENT, | 233 | .type = SDL_USEREVENT, |
@@ -318,6 +318,8 @@ static void update_TouchState_(void *ptr) { | |||
318 | if (pixels.x || pixels.y) { | 318 | if (pixels.x || pixels.y) { |
319 | subv_F3(&mom->accum, initI2_F3(pixels)); | 319 | subv_F3(&mom->accum, initI2_F3(pixels)); |
320 | dispatchMotion_Touch_(mom->pos, 0); | 320 | dispatchMotion_Touch_(mom->pos, 0); |
321 | iAssert(mom->affinity); | ||
322 | setCurrent_Root(mom->affinity->root); | ||
321 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 323 | dispatchEvent_Widget(mom->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
322 | .type = SDL_MOUSEWHEEL, | 324 | .type = SDL_MOUSEWHEEL, |
323 | .timestamp = nowTime, | 325 | .timestamp = nowTime, |
@@ -334,26 +336,13 @@ static void update_TouchState_(void *ptr) { | |||
334 | } | 336 | } |
335 | /* Keep updating if interaction is still ongoing. */ | 337 | /* Keep updating if interaction is still ongoing. */ |
336 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { | 338 | if (!isEmpty_Array(d->touches) || !isEmpty_Array(d->moms)) { |
337 | addTicker_App(update_TouchState_, ptr); | 339 | addTickerRoot_App(update_TouchState_, NULL, ptr); |
338 | } | 340 | } |
339 | } | 341 | } |
340 | 342 | ||
341 | static iWidget *findOverflowScrollable_Widget_(iWidget *d) { | ||
342 | const iInt2 rootSize = size_Root(d->root); | ||
343 | for (iWidget *w = d; w; w = parent_Widget(w)) { | ||
344 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { | ||
345 | if (height_Widget(w) > rootSize.y && !hasVisibleChildOnTop_Widget(w)) { | ||
346 | return w; | ||
347 | } | ||
348 | return NULL; | ||
349 | } | ||
350 | } | ||
351 | return NULL; | ||
352 | } | ||
353 | |||
354 | static iWidget *findSlidePanel_Widget_(iWidget *d) { | 343 | static iWidget *findSlidePanel_Widget_(iWidget *d) { |
355 | for (iWidget *w = d; w; w = parent_Widget(w)) { | 344 | for (iWidget *w = d; w; w = parent_Widget(w)) { |
356 | if (isVisible_Widget(w) && flags_Widget(w) & horizontalOffset_WidgetFlag) { | 345 | if (isVisible_Widget(w) && flags_Widget(w) & edgeDraggable_WidgetFlag) { |
357 | return w; | 346 | return w; |
358 | } | 347 | } |
359 | } | 348 | } |
@@ -466,6 +455,8 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
466 | if (edge == left_TouchEdge) { | 455 | if (edge == left_TouchEdge) { |
467 | dragging = findSlidePanel_Widget_(aff); | 456 | dragging = findSlidePanel_Widget_(aff); |
468 | if (dragging) { | 457 | if (dragging) { |
458 | // printf("Selected for dragging: "); | ||
459 | // identify_Widget(dragging); | ||
469 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); | 460 | setFlags_Widget(dragging, dragged_WidgetFlag, iTrue); |
470 | } | 461 | } |
471 | } | 462 | } |
@@ -492,7 +483,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
492 | } | 483 | } |
493 | /* This may begin a pinch. */ | 484 | /* This may begin a pinch. */ |
494 | checkNewPinch_TouchState_(d, back_Array(d->touches)); | 485 | checkNewPinch_TouchState_(d, back_Array(d->touches)); |
495 | addTicker_App(update_TouchState_, d); | 486 | addTickerRoot_App(update_TouchState_, NULL, d); |
496 | } | 487 | } |
497 | else if (ev->type == SDL_FINGERMOTION) { | 488 | else if (ev->type == SDL_FINGERMOTION) { |
498 | iTouch *touch = find_TouchState_(d, fing->fingerId); | 489 | iTouch *touch = find_TouchState_(d, fing->fingerId); |
@@ -544,7 +535,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
544 | divvf_F3(&touch->accum, 6); | 535 | divvf_F3(&touch->accum, 6); |
545 | divfv_I2(&pixels, 6); | 536 | divfv_I2(&pixels, 6); |
546 | /* Allow scrolling a scrollable widget. */ | 537 | /* Allow scrolling a scrollable widget. */ |
547 | iWidget *flow = findOverflowScrollable_Widget_(touch->affinity); | 538 | iWidget *flow = findOverflowScrollable_Widget(touch->affinity); |
548 | if (flow) { | 539 | if (flow) { |
549 | touch->affinity = flow; | 540 | touch->affinity = flow; |
550 | } | 541 | } |
@@ -567,7 +558,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
567 | } | 558 | } |
568 | /* Edge swipe aborted? */ | 559 | /* Edge swipe aborted? */ |
569 | if (touch->edge == left_TouchEdge) { | 560 | if (touch->edge == left_TouchEdge) { |
570 | if (fing->dx < 0) { | 561 | if (fing->dx < 0 && x_F3(touch->pos[0]) < tapRadiusPt_ * window->pixelRatio) { |
571 | touch->edge = none_TouchEdge; | 562 | touch->edge = none_TouchEdge; |
572 | if (touch->edgeDragging) { | 563 | if (touch->edgeDragging) { |
573 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); | 564 | setFlags_Widget(touch->edgeDragging, dragged_WidgetFlag, iFalse); |
@@ -598,6 +589,7 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
598 | if (pixels.x || pixels.y) { | 589 | if (pixels.x || pixels.y) { |
599 | setFocus_Widget(NULL); | 590 | setFocus_Widget(NULL); |
600 | dispatchMotion_Touch_(touch->pos[0], 0); | 591 | dispatchMotion_Touch_(touch->pos[0], 0); |
592 | setCurrent_Root(touch->affinity->root); | ||
601 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ | 593 | dispatchEvent_Widget(touch->affinity, (SDL_Event *) &(SDL_MouseWheelEvent){ |
602 | .type = SDL_MOUSEWHEEL, | 594 | .type = SDL_MOUSEWHEEL, |
603 | .timestamp = SDL_GetTicks(), | 595 | .timestamp = SDL_GetTicks(), |
@@ -642,17 +634,19 @@ iBool processEvent_Touch(const SDL_Event *ev) { | |||
642 | continue; | 634 | continue; |
643 | } | 635 | } |
644 | /* Edge swipes do not generate momentum. */ | 636 | /* Edge swipes do not generate momentum. */ |
637 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); | ||
645 | const uint32_t duration = nowTime - touch->startTime; | 638 | const uint32_t duration = nowTime - touch->startTime; |
646 | const iFloat3 gestureVector = sub_F3(pos, touch->startPos); | 639 | const iFloat3 gestureVector = sub_F3(pos, touch->pos[lastIndex]); |
647 | iFloat3 velocity = zero_F3(); | 640 | iFloat3 velocity = zero_F3(); |
648 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && | 641 | if (touch->edge && fabsf(2 * x_F3(gestureVector)) > fabsf(y_F3(gestureVector)) && |
649 | !isStationary_Touch_(touch)) { | 642 | !isStationary_Touch_(touch)) { |
650 | dispatchClick_Touch_(touch, touch->edge == left_TouchEdge ? SDL_BUTTON_X1 | 643 | const int swipeDir = x_F3(gestureVector) > 0 ? +1 : -1; |
651 | : SDL_BUTTON_X2); | 644 | dispatchClick_Touch_(touch, |
645 | touch->edge == left_TouchEdge && swipeDir > 0 ? SDL_BUTTON_X1 : | ||
646 | touch->edge == right_TouchEdge && swipeDir < 0 ? SDL_BUTTON_X2 : 0); | ||
652 | setHover_Widget(NULL); | 647 | setHover_Widget(NULL); |
653 | } | 648 | } |
654 | else { | 649 | else { |
655 | const size_t lastIndex = iMin(touch->posCount - 1, lastIndex_Touch_); | ||
656 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; | 650 | const uint32_t elapsed = fing->timestamp - touch->posTime[lastIndex]; |
657 | const float minVelocity = 400.0f; | 651 | const float minVelocity = 400.0f; |
658 | if (elapsed < 150) { | 652 | if (elapsed < 150) { |
@@ -758,6 +752,20 @@ iInt2 latestPosition_Touch(void) { | |||
758 | return touchState_()->currentTouchPos; | 752 | return touchState_()->currentTouchPos; |
759 | } | 753 | } |
760 | 754 | ||
755 | iBool isHovering_Touch(void) { | ||
756 | iTouchState *d = touchState_(); | ||
757 | if (numFingers_Touch() == 1) { | ||
758 | const iTouch *touch = constFront_Array(d->touches); | ||
759 | if (touch->isTapBegun && isStationary_Touch_(touch)) { | ||
760 | return iTrue; | ||
761 | } | ||
762 | if (touch->isTapAndHold) { | ||
763 | return iTrue; | ||
764 | } | ||
765 | } | ||
766 | return iFalse; | ||
767 | } | ||
768 | |||
761 | size_t numFingers_Touch(void) { | 769 | size_t numFingers_Touch(void) { |
762 | return size_Array(touchState_()->touches); | 770 | return size_Array(touchState_()->touches); |
763 | } | 771 | } |
diff --git a/src/ui/touch.h b/src/ui/touch.h index 1a6fb350..e048224a 100644 --- a/src/ui/touch.h +++ b/src/ui/touch.h | |||
@@ -41,4 +41,5 @@ enum iWidgetTouchMode widgetMode_Touch (const iWidget *widget); | |||
41 | void widgetDestroyed_Touch (iWidget *widget); | 41 | void widgetDestroyed_Touch (iWidget *widget); |
42 | 42 | ||
43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ | 43 | iInt2 latestPosition_Touch (void); /* valid during processing of current event */ |
44 | iBool isHovering_Touch (void); /* stationary touch or a long-press drag ongoing */ | ||
44 | size_t numFingers_Touch (void); | 45 | size_t numFingers_Touch (void); |
diff --git a/src/ui/util.c b/src/ui/util.c index 04cdf27f..4b35f8f7 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -80,7 +80,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
80 | appendChar_String(str, 0x2325); | 80 | appendChar_String(str, 0x2325); |
81 | } | 81 | } |
82 | if (kmods & KMOD_SHIFT) { | 82 | if (kmods & KMOD_SHIFT) { |
83 | appendChar_String(str, 0x21e7); | 83 | appendCStr_String(str, shift_Icon); |
84 | } | 84 | } |
85 | if (kmods & KMOD_GUI) { | 85 | if (kmods & KMOD_GUI) { |
86 | appendChar_String(str, 0x2318); | 86 | appendChar_String(str, 0x2318); |
@@ -93,7 +93,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
93 | appendCStr_String(str, "Alt+"); | 93 | appendCStr_String(str, "Alt+"); |
94 | } | 94 | } |
95 | if (kmods & KMOD_SHIFT) { | 95 | if (kmods & KMOD_SHIFT) { |
96 | appendCStr_String(str, "Shift+"); | 96 | appendCStr_String(str, shift_Icon "+"); |
97 | } | 97 | } |
98 | if (kmods & KMOD_GUI) { | 98 | if (kmods & KMOD_GUI) { |
99 | appendCStr_String(str, "Meta+"); | 99 | appendCStr_String(str, "Meta+"); |
@@ -138,7 +138,7 @@ void toString_Sym(int key, int kmods, iString *str) { | |||
138 | } | 138 | } |
139 | else if (key == SDLK_RETURN) { | 139 | else if (key == SDLK_RETURN) { |
140 | removePlus_(str); | 140 | removePlus_(str); |
141 | appendChar_String(str, 0x21a9); /* Leftwards arrow with a hook */ | 141 | appendCStr_String(str, return_Icon); /* Leftwards arrow with a hook */ |
142 | } | 142 | } |
143 | else { | 143 | else { |
144 | appendCStr_String(str, SDL_GetKeyName(key)); | 144 | appendCStr_String(str, SDL_GetKeyName(key)); |
@@ -837,10 +837,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { | |||
837 | if (postCommands) { | 837 | if (postCommands) { |
838 | postCommand_Widget(d, "menu.opened"); | 838 | postCommand_Widget(d, "menu.opened"); |
839 | } | 839 | } |
840 | if (isPortraitPhone) { | 840 | setupMenuTransition_Mobile(d, iTrue); |
841 | setVisualOffset_Widget(d, isSlidePanel ? width_Widget(d) : height_Widget(d), 0, 0); | ||
842 | setVisualOffset_Widget(d, 0, 330, easeOut_AnimFlag | softer_AnimFlag); | ||
843 | } | ||
844 | } | 841 | } |
845 | 842 | ||
846 | void closeMenu_Widget(iWidget *d) { | 843 | void closeMenu_Widget(iWidget *d) { |
@@ -851,14 +848,7 @@ void closeMenu_Widget(iWidget *d) { | |||
851 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); | 848 | setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); |
852 | postRefresh_App(); | 849 | postRefresh_App(); |
853 | postCommand_Widget(d, "menu.closed"); | 850 | postCommand_Widget(d, "menu.closed"); |
854 | if (isPortrait_App() && deviceType_App() == phone_AppDeviceType) { | 851 | setupMenuTransition_Mobile(d, iFalse); |
855 | const iBool wasDragged = iAbs(value_Anim(&d->visualOffset) - 0) > 1; | ||
856 | setVisualOffset_Widget(d, | ||
857 | flags_Widget(d) & horizontalOffset_WidgetFlag ? | ||
858 | width_Widget(d) : height_Widget(d), | ||
859 | wasDragged ? 100 : 200, | ||
860 | wasDragged ? 0 : easeIn_AnimFlag | softer_AnimFlag); | ||
861 | } | ||
862 | } | 852 | } |
863 | 853 | ||
864 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { | 854 | iLabelWidget *findMenuItem_Widget(iWidget *menu, const char *command) { |
@@ -904,7 +894,7 @@ static iBool isTabPage_Widget_(const iWidget *tabs, const iWidget *page) { | |||
904 | static void unfocusFocusInsideTabPage_(const iWidget *page) { | 894 | static void unfocusFocusInsideTabPage_(const iWidget *page) { |
905 | iWidget *focus = focus_Widget(); | 895 | iWidget *focus = focus_Widget(); |
906 | if (page && focus && hasParent_Widget(focus, page)) { | 896 | if (page && focus && hasParent_Widget(focus, page)) { |
907 | printf("unfocus inside page: %p\n", focus); | 897 | // printf("unfocus inside page: %p\n", focus); |
908 | setFocus_Widget(NULL); | 898 | setFocus_Widget(NULL); |
909 | } | 899 | } |
910 | } | 900 | } |
@@ -1127,46 +1117,6 @@ size_t tabCount_Widget(const iWidget *tabs) { | |||
1127 | 1117 | ||
1128 | /*-----------------------------------------------------------------------------------------------*/ | 1118 | /*-----------------------------------------------------------------------------------------------*/ |
1129 | 1119 | ||
1130 | static void acceptFilePath_(iWidget *dlg) { | ||
1131 | iInputWidget *input = findChild_Widget(dlg, "input"); | ||
1132 | iString *path = makeAbsolute_Path(text_InputWidget(input)); | ||
1133 | postCommandf_App("%s path:%s", cstr_String(id_Widget(dlg)), cstr_String(path)); | ||
1134 | destroy_Widget(dlg); | ||
1135 | delete_String(path); | ||
1136 | } | ||
1137 | |||
1138 | iBool filePathHandler_(iWidget *dlg, const char *cmd) { | ||
1139 | iWidget *ptr = as_Widget(pointer_Command(cmd)); | ||
1140 | if (equal_Command(cmd, "input.ended")) { | ||
1141 | if (hasParent_Widget(ptr, dlg)) { | ||
1142 | if (arg_Command(cmd)) { | ||
1143 | acceptFilePath_(dlg); | ||
1144 | } | ||
1145 | else { | ||
1146 | destroy_Widget(dlg); | ||
1147 | } | ||
1148 | return iTrue; | ||
1149 | } | ||
1150 | return iFalse; | ||
1151 | } | ||
1152 | else if (ptr && !hasParent_Widget(ptr, dlg)) { | ||
1153 | /* Command from outside the dialog, so dismiss the dialog. */ | ||
1154 | if (!equal_Command(cmd, "focus.lost")) { | ||
1155 | destroy_Widget(dlg); | ||
1156 | } | ||
1157 | return iFalse; | ||
1158 | } | ||
1159 | else if (equal_Command(cmd, "filepath.cancel")) { | ||
1160 | end_InputWidget(findChild_Widget(dlg, "input"), iFalse); | ||
1161 | destroy_Widget(dlg); | ||
1162 | return iTrue; | ||
1163 | } | ||
1164 | else if (equal_Command(cmd, "filepath.accept")) { | ||
1165 | acceptFilePath_(dlg); | ||
1166 | return iTrue; | ||
1167 | } | ||
1168 | return iFalse; | ||
1169 | } | ||
1170 | 1120 | ||
1171 | iWidget *makeSheet_Widget(const char *id) { | 1121 | iWidget *makeSheet_Widget(const char *id) { |
1172 | iWidget *sheet = new_Widget(); | 1122 | iWidget *sheet = new_Widget(); |
@@ -1183,609 +1133,6 @@ iWidget *makeSheet_Widget(const char *id) { | |||
1183 | return sheet; | 1133 | return sheet; |
1184 | } | 1134 | } |
1185 | 1135 | ||
1186 | static void updateSheetPanelMetrics_(iWidget *sheet) { | ||
1187 | iWidget *navi = findChild_Widget(sheet, "panel.navi"); | ||
1188 | iWidget *naviPad = child_Widget(navi, 0); | ||
1189 | int naviHeight = lineHeight_Text(defaultBig_FontId) + 4 * gap_UI; | ||
1190 | #if defined (iPlatformAppleMobile) | ||
1191 | float left, right, top, bottom; | ||
1192 | safeAreaInsets_iOS(&left, &top, &right, &bottom); | ||
1193 | setPadding_Widget(sheet, left, 0, right, 0); | ||
1194 | navi->rect.pos = init_I2(left, top); | ||
1195 | iConstForEach(PtrArray, i, findChildren_Widget(sheet, "panel.toppad")) { | ||
1196 | iWidget *pad = *i.value; | ||
1197 | setFixedSize_Widget(pad, init1_I2(naviHeight)); | ||
1198 | } | ||
1199 | #endif | ||
1200 | setFixedSize_Widget(navi, init_I2(-1, naviHeight)); | ||
1201 | } | ||
1202 | |||
1203 | static iBool slidePanelHandler_(iWidget *d, const char *cmd) { | ||
1204 | if (equal_Command(cmd, "panel.open")) { | ||
1205 | iWidget *button = pointer_Command(cmd); | ||
1206 | iWidget *panel = userData_Object(button); | ||
1207 | openMenu_Widget(panel, innerToWindow_Widget(panel, zero_I2())); | ||
1208 | setFlags_Widget(panel, disabled_WidgetFlag, iFalse); | ||
1209 | // updateTextCStr_LabelWidget(findWidget_App("panel.back"), ); | ||
1210 | return iTrue; | ||
1211 | } | ||
1212 | if (equal_Command(cmd, "mouse.clicked") && arg_Command(cmd) && | ||
1213 | argLabel_Command(cmd, "button") == SDL_BUTTON_X1) { | ||
1214 | postCommand_App("panel.close"); | ||
1215 | return iTrue; | ||
1216 | } | ||
1217 | if (equal_Command(cmd, "panel.close")) { | ||
1218 | iBool wasClosed = iFalse; | ||
1219 | iForEach(ObjectList, i, children_Widget(parent_Widget(d))) { | ||
1220 | iWidget *child = i.object; | ||
1221 | if (!cmp_String(id_Widget(child), "panel") && isVisible_Widget(child)) { | ||
1222 | closeMenu_Widget(child); | ||
1223 | setFlags_Widget(child, disabled_WidgetFlag, iTrue); | ||
1224 | setFocus_Widget(NULL); | ||
1225 | updateTextCStr_LabelWidget(findWidget_App("panel.back"), "Back"); | ||
1226 | wasClosed = iTrue; | ||
1227 | } | ||
1228 | } | ||
1229 | if (!wasClosed) { | ||
1230 | postCommand_App("prefs.dismiss"); | ||
1231 | } | ||
1232 | return iTrue; | ||
1233 | } | ||
1234 | if (equal_Command(cmd, "document.changed")) { | ||
1235 | postCommand_App("prefs.dismiss"); | ||
1236 | return iFalse; | ||
1237 | } | ||
1238 | if (equal_Command(cmd, "window.resized")) { | ||
1239 | updateSheetPanelMetrics_(parent_Widget(d)); | ||
1240 | } | ||
1241 | return iFalse; | ||
1242 | } | ||
1243 | |||
1244 | static iBool isTwoColumnPage_(iWidget *d) { | ||
1245 | if (cmp_String(id_Widget(d), "dialogbuttons") == 0 || | ||
1246 | cmp_String(id_Widget(d), "prefs.tabs") == 0) { | ||
1247 | return iFalse; | ||
1248 | } | ||
1249 | if (class_Widget(d) == &Class_Widget && childCount_Widget(d) == 2) { | ||
1250 | return class_Widget(child_Widget(d, 0)) == &Class_Widget && | ||
1251 | class_Widget(child_Widget(d, 1)) == &Class_Widget; | ||
1252 | } | ||
1253 | return iFalse; | ||
1254 | } | ||
1255 | |||
1256 | static iBool isOmittedPref_(const iString *id) { | ||
1257 | static const char *omittedPrefs[] = { | ||
1258 | "prefs.smoothscroll", | ||
1259 | "prefs.imageloadscroll", | ||
1260 | "prefs.retainwindow", | ||
1261 | "prefs.ca.file", | ||
1262 | "prefs.ca.path", | ||
1263 | }; | ||
1264 | iForIndices(i, omittedPrefs) { | ||
1265 | if (cmp_String(id, omittedPrefs[i]) == 0) { | ||
1266 | return iTrue; | ||
1267 | } | ||
1268 | } | ||
1269 | return iFalse; | ||
1270 | } | ||
1271 | |||
1272 | enum iPrefsElement { | ||
1273 | panelTitle_PrefsElement, | ||
1274 | heading_PrefsElement, | ||
1275 | toggle_PrefsElement, | ||
1276 | dropdown_PrefsElement, | ||
1277 | radioButton_PrefsElement, | ||
1278 | textInput_PrefsElement, | ||
1279 | }; | ||
1280 | |||
1281 | static iAnyObject *addPanelChild_(iWidget *panel, iAnyObject *child, int64_t flags, | ||
1282 | enum iPrefsElement elementType, | ||
1283 | enum iPrefsElement precedingElementType) { | ||
1284 | /* Erase redundant/unused headings. */ | ||
1285 | if (precedingElementType == heading_PrefsElement && | ||
1286 | (!child || (elementType == heading_PrefsElement || elementType == radioButton_PrefsElement))) { | ||
1287 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
1288 | if (!cmp_String(id_Widget(constAs_Widget(lastChild_Widget(panel))), "padding")) { | ||
1289 | iRelease(removeChild_Widget(panel, lastChild_Widget(panel))); | ||
1290 | } | ||
1291 | } | ||
1292 | if (child) { | ||
1293 | /* Insert padding between different element types. */ | ||
1294 | if (precedingElementType != panelTitle_PrefsElement) { | ||
1295 | if (elementType == heading_PrefsElement || | ||
1296 | (elementType == toggle_PrefsElement && | ||
1297 | precedingElementType != toggle_PrefsElement && | ||
1298 | precedingElementType != heading_PrefsElement) || | ||
1299 | (elementType == dropdown_PrefsElement && | ||
1300 | precedingElementType != dropdown_PrefsElement && | ||
1301 | precedingElementType != heading_PrefsElement) || | ||
1302 | (elementType == textInput_PrefsElement && | ||
1303 | precedingElementType != textInput_PrefsElement && | ||
1304 | precedingElementType != heading_PrefsElement)) { | ||
1305 | addChild_Widget(panel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1306 | } | ||
1307 | } | ||
1308 | if ((elementType == toggle_PrefsElement && precedingElementType != toggle_PrefsElement) || | ||
1309 | (elementType == textInput_PrefsElement && precedingElementType != textInput_PrefsElement)) { | ||
1310 | flags |= borderTop_WidgetFlag; | ||
1311 | } | ||
1312 | return addChildFlags_Widget(panel, child, flags); | ||
1313 | } | ||
1314 | return NULL; | ||
1315 | } | ||
1316 | |||
1317 | static void stripTrailingColon_(iLabelWidget *label) { | ||
1318 | const iString *text = text_LabelWidget(label); | ||
1319 | if (endsWith_String(text, ":")) { | ||
1320 | iString *mod = copy_String(text); | ||
1321 | removeEnd_String(mod, 1); | ||
1322 | updateText_LabelWidget(label, mod); | ||
1323 | delete_String(mod); | ||
1324 | } | ||
1325 | } | ||
1326 | |||
1327 | static iLabelWidget *makePanelButton_(const char *text, const char *command) { | ||
1328 | iLabelWidget *btn = new_LabelWidget(text, command); | ||
1329 | setFlags_Widget(as_Widget(btn), | ||
1330 | borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
1331 | frameless_WidgetFlag | extraPadding_WidgetFlag, | ||
1332 | iTrue); | ||
1333 | checkIcon_LabelWidget(btn); | ||
1334 | setFont_LabelWidget(btn, defaultBig_FontId); | ||
1335 | setTextColor_LabelWidget(btn, uiTextStrong_ColorId); | ||
1336 | setBackgroundColor_Widget(as_Widget(btn), uiBackgroundSidebar_ColorId); | ||
1337 | return btn; | ||
1338 | } | ||
1339 | |||
1340 | static iWidget *makeValuePadding_(iWidget *value) { | ||
1341 | iInputWidget *input = isInstance_Object(value, &Class_InputWidget) ? (iInputWidget *) value : NULL; | ||
1342 | if (input) { | ||
1343 | setFont_InputWidget(input, defaultBig_FontId); | ||
1344 | setContentPadding_InputWidget(input, 3 * gap_UI, 3 * gap_UI); | ||
1345 | } | ||
1346 | iWidget *pad = new_Widget(); | ||
1347 | setBackgroundColor_Widget(pad, uiBackgroundSidebar_ColorId); | ||
1348 | setPadding_Widget(pad, 0, 1 * gap_UI, 0, 1 * gap_UI); | ||
1349 | addChild_Widget(pad, iClob(value)); | ||
1350 | setFlags_Widget(pad, | ||
1351 | borderBottom_WidgetFlag | | ||
1352 | arrangeVertical_WidgetFlag | | ||
1353 | resizeToParentWidth_WidgetFlag | | ||
1354 | resizeWidthOfChildren_WidgetFlag | | ||
1355 | arrangeHeight_WidgetFlag, | ||
1356 | iTrue); | ||
1357 | return pad; | ||
1358 | } | ||
1359 | |||
1360 | static iWidget *makeValuePaddingWithHeading_(iLabelWidget *heading, iWidget *value) { | ||
1361 | iWidget *div = new_Widget(); | ||
1362 | setFlags_Widget(div, | ||
1363 | borderBottom_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1364 | resizeWidthOfChildren_WidgetFlag | | ||
1365 | arrangeHorizontal_WidgetFlag, iTrue); | ||
1366 | setBackgroundColor_Widget(div, uiBackgroundSidebar_ColorId); | ||
1367 | setPadding_Widget(div, gap_UI, gap_UI, 4 * gap_UI, gap_UI); | ||
1368 | addChildFlags_Widget(div, iClob(heading), 0); | ||
1369 | //setFixedSize_Widget(as_Widget(heading), init_I2(-1, height_Widget(value))); | ||
1370 | setFont_LabelWidget(heading, defaultBig_FontId); | ||
1371 | setTextColor_LabelWidget(heading, uiTextStrong_ColorId); | ||
1372 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
1373 | addChildFlags_Widget(div, iClob(value), expand_WidgetFlag); | ||
1374 | } | ||
1375 | else { | ||
1376 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | ||
1377 | addChild_Widget(div, iClob(value)); | ||
1378 | } | ||
1379 | return div; | ||
1380 | } | ||
1381 | |||
1382 | static iWidget *addChildPanel_(iWidget *sheet, iLabelWidget *panelButton, | ||
1383 | const iString *titleText) { | ||
1384 | iWidget *owner = new_Widget(); | ||
1385 | setId_Widget(owner, "panel"); | ||
1386 | setUserData_Object(panelButton, owner); | ||
1387 | setBackgroundColor_Widget(owner, uiBackground_ColorId); | ||
1388 | setId_Widget(addChild_Widget(owner, iClob(makePadding_Widget(0))), "panel.toppad"); | ||
1389 | if (titleText) { | ||
1390 | iLabelWidget *title = | ||
1391 | addChildFlags_Widget(owner, | ||
1392 | iClob(new_LabelWidget(cstr_String(titleText), NULL)), | ||
1393 | alignLeft_WidgetFlag | frameless_WidgetFlag); | ||
1394 | setFont_LabelWidget(title, uiLabelLargeBold_FontId); | ||
1395 | setTextColor_LabelWidget(title, uiHeading_ColorId); | ||
1396 | } | ||
1397 | addChildFlags_Widget(sheet, | ||
1398 | iClob(owner), | ||
1399 | focusRoot_WidgetFlag | hidden_WidgetFlag | disabled_WidgetFlag | | ||
1400 | arrangeVertical_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
1401 | arrangeHeight_WidgetFlag | overflowScrollable_WidgetFlag | | ||
1402 | horizontalOffset_WidgetFlag | commandOnClick_WidgetFlag); | ||
1403 | return owner; | ||
1404 | } | ||
1405 | |||
1406 | void finalizeSheet_Widget(iWidget *sheet) { | ||
1407 | /* The sheet contents are completely rearranged and restyled on a phone. | ||
1408 | We'll set up a linear fullscreen arrangement of the widgets. Sheets are already | ||
1409 | scrollable so they can be taller than the display. In hindsight, it may have been | ||
1410 | easier to create phone versions of each dialog, but at least this works with any | ||
1411 | future changes to the UI (..."works"). At least this way it is possible to enforce | ||
1412 | a consistent styling. */ | ||
1413 | if (deviceType_App() == phone_AppDeviceType && parent_Widget(sheet) == root_Widget(sheet)) { | ||
1414 | if (~flags_Widget(sheet) & keepOnTop_WidgetFlag) { | ||
1415 | /* Already finalized. */ | ||
1416 | arrange_Widget(sheet); | ||
1417 | postRefresh_App(); | ||
1418 | return; | ||
1419 | } | ||
1420 | /* Modify the top sheet to act as a fullscreen background. */ | ||
1421 | setPadding1_Widget(sheet, 0); | ||
1422 | setBackgroundColor_Widget(sheet, uiBackground_ColorId); | ||
1423 | setFlags_Widget(sheet, | ||
1424 | keepOnTop_WidgetFlag | | ||
1425 | parentCannotResize_WidgetFlag | | ||
1426 | arrangeSize_WidgetFlag | | ||
1427 | centerHorizontal_WidgetFlag | | ||
1428 | arrangeVertical_WidgetFlag | | ||
1429 | arrangeHorizontal_WidgetFlag | | ||
1430 | overflowScrollable_WidgetFlag, | ||
1431 | iFalse); | ||
1432 | setFlags_Widget(sheet, | ||
1433 | commandOnClick_WidgetFlag | | ||
1434 | frameless_WidgetFlag | | ||
1435 | resizeWidthOfChildren_WidgetFlag, | ||
1436 | iTrue); | ||
1437 | iPtrArray * contents = collect_PtrArray(new_PtrArray()); /* two-column pages */ | ||
1438 | iPtrArray * panelButtons = collect_PtrArray(new_PtrArray()); | ||
1439 | iWidget * prefsTabs = findChild_Widget(sheet, "prefs.tabs"); | ||
1440 | iWidget * dialogHeading = (prefsTabs ? NULL : child_Widget(sheet, 0)); | ||
1441 | const iBool isPrefs = (prefsTabs != NULL); | ||
1442 | const int64_t panelButtonFlags = borderBottom_WidgetFlag | alignLeft_WidgetFlag | | ||
1443 | frameless_WidgetFlag | extraPadding_WidgetFlag; | ||
1444 | iWidget *topPanel = new_Widget(); | ||
1445 | setId_Widget(topPanel, "panel.top"); | ||
1446 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1447 | if (prefsTabs) { | ||
1448 | iRelease(removeChild_Widget(sheet, child_Widget(sheet, 0))); /* heading */ | ||
1449 | iRelease(removeChild_Widget(sheet, findChild_Widget(sheet, "dialogbuttons"))); | ||
1450 | /* Pull out the pages and make them panels. */ | ||
1451 | iWidget *pages = findChild_Widget(prefsTabs, "tabs.pages"); | ||
1452 | size_t pageCount = tabCount_Widget(prefsTabs); | ||
1453 | for (size_t i = 0; i < pageCount; i++) { | ||
1454 | iString *text = copy_String(text_LabelWidget(tabPageButton_Widget(prefsTabs, tabPage_Widget(prefsTabs, 0)))); | ||
1455 | iWidget *page = removeTabPage_Widget(prefsTabs, 0); | ||
1456 | iWidget *pageContent = child_Widget(page, 1); /* surrounded by padding widgets */ | ||
1457 | pushBack_PtrArray(contents, ref_Object(pageContent)); | ||
1458 | iLabelWidget *panelButton; | ||
1459 | pushBack_PtrArray(panelButtons, | ||
1460 | addChildFlags_Widget(topPanel, | ||
1461 | iClob(panelButton = makePanelButton_( | ||
1462 | i == 1 ? "${heading.prefs.userinterface}" : cstr_String(text), | ||
1463 | "panel.open")), | ||
1464 | (i == 0 ? borderTop_WidgetFlag : 0) | | ||
1465 | chevron_WidgetFlag)); | ||
1466 | const iChar icons[] = { | ||
1467 | 0x02699, /* gear */ | ||
1468 | 0x1f4f1, /* mobile phone */ | ||
1469 | 0x1f3a8, /* palette */ | ||
1470 | 0x1f523, | ||
1471 | 0x1f5a7, /* computer network */ | ||
1472 | }; | ||
1473 | setIcon_LabelWidget(panelButton, icons[i]); | ||
1474 | // setFont_LabelWidget(panelButton, defaultBig_FontId); | ||
1475 | // setBackgroundColor_Widget(as_Widget(panelButton), uiBackgroundSidebar_ColorId); | ||
1476 | iRelease(page); | ||
1477 | delete_String(text); | ||
1478 | } | ||
1479 | destroy_Widget(prefsTabs); | ||
1480 | } | ||
1481 | iForEach(ObjectList, i, children_Widget(sheet)) { | ||
1482 | iWidget *child = i.object; | ||
1483 | if (isTwoColumnPage_(child)) { | ||
1484 | pushBack_PtrArray(contents, removeChild_Widget(sheet, child)); | ||
1485 | } | ||
1486 | else { | ||
1487 | removeChild_Widget(sheet, child); | ||
1488 | addChild_Widget(topPanel, child); | ||
1489 | iRelease(child); | ||
1490 | } | ||
1491 | } | ||
1492 | const iBool useSlidePanels = (size_PtrArray(contents) == size_PtrArray(panelButtons)); | ||
1493 | addChildFlags_Widget(sheet, iClob(topPanel), | ||
1494 | arrangeVertical_WidgetFlag | | ||
1495 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1496 | overflowScrollable_WidgetFlag | | ||
1497 | commandOnClick_WidgetFlag); | ||
1498 | setCommandHandler_Widget(topPanel, slidePanelHandler_); | ||
1499 | iForEach(PtrArray, j, contents) { | ||
1500 | iWidget *owner = topPanel; | ||
1501 | if (useSlidePanels) { | ||
1502 | /* Create a new child panel. */ | ||
1503 | iLabelWidget *button = at_PtrArray(panelButtons, index_PtrArrayIterator(&j)); | ||
1504 | owner = addChildPanel_(sheet, button, | ||
1505 | collect_String(upper_String(text_LabelWidget(button)))); | ||
1506 | } | ||
1507 | iWidget *pageContent = j.ptr; | ||
1508 | iWidget *headings = child_Widget(pageContent, 0); | ||
1509 | iWidget *values = child_Widget(pageContent, 1); | ||
1510 | enum iPrefsElement prevElement = panelTitle_PrefsElement; | ||
1511 | /* Identify the types of controls in the dialog and restyle/organize them. */ | ||
1512 | while (!isEmpty_ObjectList(children_Widget(headings))) { | ||
1513 | iWidget *heading = child_Widget(headings, 0); | ||
1514 | iWidget *value = child_Widget(values, 0); | ||
1515 | removeChild_Widget(headings, heading); | ||
1516 | removeChild_Widget(values, value); | ||
1517 | /* Can we ignore these widgets? */ | ||
1518 | if (isOmittedPref_(id_Widget(value)) || | ||
1519 | (class_Widget(heading) == &Class_Widget && | ||
1520 | class_Widget(value) == &Class_Widget) /* just padding */) { | ||
1521 | iRelease(heading); | ||
1522 | iRelease(value); | ||
1523 | continue; | ||
1524 | } | ||
1525 | enum iPrefsElement element = toggle_PrefsElement; | ||
1526 | iLabelWidget *headingLabel = NULL; | ||
1527 | iLabelWidget *valueLabel = NULL; | ||
1528 | iInputWidget *valueInput = NULL; | ||
1529 | const iBool isMenuButton = findChild_Widget(value, "menu") != NULL; | ||
1530 | if (isInstance_Object(heading, &Class_LabelWidget)) { | ||
1531 | headingLabel = (iLabelWidget *) heading; | ||
1532 | stripTrailingColon_(headingLabel); | ||
1533 | } | ||
1534 | if (isInstance_Object(value, &Class_LabelWidget)) { | ||
1535 | valueLabel = (iLabelWidget *) value; | ||
1536 | setFont_LabelWidget(valueLabel, defaultBig_FontId); | ||
1537 | } | ||
1538 | if (isInstance_Object(value, &Class_InputWidget)) { | ||
1539 | valueInput = (iInputWidget *) value; | ||
1540 | setFlags_Widget(value, borderBottom_WidgetFlag, iFalse); | ||
1541 | element = textInput_PrefsElement; | ||
1542 | } | ||
1543 | if (childCount_Widget(value) >= 2) { | ||
1544 | if (isInstance_Object(child_Widget(value, 0), &Class_InputWidget)) { | ||
1545 | element = textInput_PrefsElement; | ||
1546 | setPadding_Widget(value, 0, 0, gap_UI, 0); | ||
1547 | valueInput = child_Widget(value, 0); | ||
1548 | } | ||
1549 | } | ||
1550 | if (valueInput) { | ||
1551 | setFont_InputWidget(valueInput, defaultBig_FontId); | ||
1552 | setContentPadding_InputWidget(valueInput, 3 * gap_UI, 0); | ||
1553 | } | ||
1554 | /* Toggles have the button on the right. */ | ||
1555 | if (valueLabel && cmp_String(command_LabelWidget(valueLabel), "toggle") == 0) { | ||
1556 | element = toggle_PrefsElement; | ||
1557 | addPanelChild_(owner, | ||
1558 | iClob(makeValuePaddingWithHeading_(headingLabel, value)), | ||
1559 | 0, | ||
1560 | element, | ||
1561 | prevElement); | ||
1562 | } | ||
1563 | else if (valueLabel && isEmpty_String(text_LabelWidget(valueLabel))) { | ||
1564 | element = heading_PrefsElement; | ||
1565 | iRelease(value); | ||
1566 | addPanelChild_(owner, iClob(heading), 0, element, prevElement); | ||
1567 | setFont_LabelWidget(headingLabel, uiLabel_FontId); | ||
1568 | } | ||
1569 | else if (isMenuButton) { | ||
1570 | element = dropdown_PrefsElement; | ||
1571 | setFlags_Widget(value, | ||
1572 | alignRight_WidgetFlag | noBackground_WidgetFlag | | ||
1573 | frameless_WidgetFlag, iTrue); | ||
1574 | setFlags_Widget(value, alignLeft_WidgetFlag, iFalse); | ||
1575 | iWidget *pad = addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
1576 | element, prevElement); | ||
1577 | pad->padding[2] = gap_UI; | ||
1578 | } | ||
1579 | else if (valueInput) { | ||
1580 | addPanelChild_(owner, iClob(makeValuePaddingWithHeading_(headingLabel, value)), 0, | ||
1581 | element, prevElement); | ||
1582 | } | ||
1583 | else { | ||
1584 | if (childCount_Widget(value) >= 2) { | ||
1585 | element = radioButton_PrefsElement; | ||
1586 | /* Always padding before radio buttons. */ | ||
1587 | addChild_Widget(owner, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1588 | } | ||
1589 | addChildFlags_Widget(owner, iClob(heading), borderBottom_WidgetFlag); | ||
1590 | if (headingLabel) { | ||
1591 | setTextColor_LabelWidget(headingLabel, uiSubheading_ColorId); | ||
1592 | setText_LabelWidget(headingLabel, | ||
1593 | collect_String(upper_String(text_LabelWidget(headingLabel)))); | ||
1594 | } | ||
1595 | addPanelChild_(owner, iClob(value), 0, element, prevElement); | ||
1596 | /* Radio buttons expand to fill the space. */ | ||
1597 | if (element == radioButton_PrefsElement) { | ||
1598 | setBackgroundColor_Widget(value, uiBackgroundSidebar_ColorId); | ||
1599 | setPadding_Widget(value, 4 * gap_UI, 2 * gap_UI, 4 * gap_UI, 2 * gap_UI); | ||
1600 | setFlags_Widget(value, | ||
1601 | borderBottom_WidgetFlag | | ||
1602 | resizeToParentWidth_WidgetFlag | | ||
1603 | resizeWidthOfChildren_WidgetFlag, | ||
1604 | iTrue); | ||
1605 | iForEach(ObjectList, sub, children_Widget(value)) { | ||
1606 | if (isInstance_Object(sub.object, &Class_LabelWidget)) { | ||
1607 | iLabelWidget *opt = sub.object; | ||
1608 | setFont_LabelWidget(opt, defaultMedium_FontId); | ||
1609 | setFlags_Widget(as_Widget(opt), noBackground_WidgetFlag, iTrue); | ||
1610 | } | ||
1611 | } | ||
1612 | } | ||
1613 | } | ||
1614 | prevElement = element; | ||
1615 | } | ||
1616 | addPanelChild_(owner, NULL, 0, 0, prevElement); | ||
1617 | destroy_Widget(pageContent); | ||
1618 | setFlags_Widget(owner, drawBackgroundToBottom_WidgetFlag, iTrue); | ||
1619 | } | ||
1620 | destroyPending_Root(sheet->root); | ||
1621 | /* Additional elements for preferences. */ | ||
1622 | if (isPrefs) { | ||
1623 | addChild_Widget(topPanel, iClob(makePadding_Widget(lineHeight_Text(defaultBig_FontId)))); | ||
1624 | addChildFlags_Widget(topPanel, | ||
1625 | iClob(makePanelButton_(info_Icon " ${menu.help}", "!open url:about:help")), | ||
1626 | borderTop_WidgetFlag); | ||
1627 | iLabelWidget *aboutButton = addChildFlags_Widget(topPanel, | ||
1628 | iClob(makePanelButton_(planet_Icon " ${menu.about}", "panel.open")), | ||
1629 | chevron_WidgetFlag); | ||
1630 | /* The About panel. */ { | ||
1631 | iWidget *panel = addChildPanel_(sheet, aboutButton, NULL); | ||
1632 | iString *msg = collectNew_String(); | ||
1633 | setCStr_String(msg, "Lagrange " LAGRANGE_APP_VERSION); | ||
1634 | #if defined (iPlatformAppleMobile) | ||
1635 | appendCStr_String(msg, " (" LAGRANGE_IOS_VERSION ")"); | ||
1636 | #endif | ||
1637 | addChild_Widget(panel, iClob(new_LabelWidget(cstr_String(msg), NULL))); | ||
1638 | addChildFlags_Widget(panel, | ||
1639 | iClob(makePanelButton_(globe_Icon " By @jk@skyjake.fi", | ||
1640 | "!open url:https://skyjake.fi/@jk")), | ||
1641 | borderTop_WidgetFlag); | ||
1642 | addChildFlags_Widget(panel, | ||
1643 | iClob(makePanelButton_(clock_Icon " ${menu.releasenotes}", | ||
1644 | "!open url:about:version")), | ||
1645 | 0); | ||
1646 | addChildFlags_Widget(panel, | ||
1647 | iClob(makePanelButton_(info_Icon " ${menu.aboutpages}", | ||
1648 | "!open url:about:about")), | ||
1649 | 0); | ||
1650 | addChildFlags_Widget(panel, | ||
1651 | iClob(makePanelButton_(bug_Icon " ${menu.debug}", | ||
1652 | "!open url:about:debug")), | ||
1653 | 0); | ||
1654 | } | ||
1655 | } | ||
1656 | else { | ||
1657 | setFlags_Widget(topPanel, overflowScrollable_WidgetFlag, iTrue); | ||
1658 | /* Update heading style. */ | ||
1659 | setFont_LabelWidget((iLabelWidget *) dialogHeading, uiLabelLargeBold_FontId); | ||
1660 | setFlags_Widget(dialogHeading, alignLeft_WidgetFlag, iTrue); | ||
1661 | } | ||
1662 | if (findChild_Widget(sheet, "valueinput.prompt")) { | ||
1663 | iWidget *prompt = findChild_Widget(sheet, "valueinput.prompt"); | ||
1664 | setFlags_Widget(prompt, alignLeft_WidgetFlag, iTrue); | ||
1665 | iInputWidget *input = findChild_Widget(sheet, "input"); | ||
1666 | removeChild_Widget(parent_Widget(input), input); | ||
1667 | addChild_Widget(topPanel, iClob(makeValuePadding_(as_Widget(input)))); | ||
1668 | } | ||
1669 | /* Top padding for each panel, to account for the overlaid navbar. */ { | ||
1670 | setId_Widget(addChildPos_Widget(topPanel, | ||
1671 | iClob(makePadding_Widget(0)), front_WidgetAddPos), | ||
1672 | "panel.toppad"); | ||
1673 | } | ||
1674 | /* Navbar. */ { | ||
1675 | iWidget *navi = new_Widget(); | ||
1676 | setId_Widget(navi, "panel.navi"); | ||
1677 | setBackgroundColor_Widget(navi, uiBackground_ColorId); | ||
1678 | addChild_Widget(navi, iClob(makePadding_Widget(0))); | ||
1679 | iLabelWidget *back = addChildFlags_Widget(navi, | ||
1680 | iClob(new_LabelWidget(leftAngle_Icon " ${panel.back}", "panel.close")), | ||
1681 | noBackground_WidgetFlag | frameless_WidgetFlag | | ||
1682 | alignLeft_WidgetFlag | extraPadding_WidgetFlag); | ||
1683 | checkIcon_LabelWidget(back); | ||
1684 | setId_Widget(as_Widget(back), "panel.back"); | ||
1685 | setFont_LabelWidget(back, defaultBig_FontId); | ||
1686 | if (!isPrefs) { | ||
1687 | /* Pick up the dialog buttons for the navbar. */ | ||
1688 | iWidget *buttons = findChild_Widget(sheet, "dialogbuttons"); | ||
1689 | iLabelWidget *cancel = findMenuItem_Widget(buttons, "cancel"); | ||
1690 | // if (!cancel) { | ||
1691 | // cancel = findMenuItem_Widget(buttons, "translation.cancel"); | ||
1692 | // } | ||
1693 | if (cancel) { | ||
1694 | updateText_LabelWidget(back, text_LabelWidget(cancel)); | ||
1695 | setCommand_LabelWidget(back, command_LabelWidget(cancel)); | ||
1696 | } | ||
1697 | iLabelWidget *def = (iLabelWidget *) lastChild_Widget(buttons); | ||
1698 | if (def && !cancel) { | ||
1699 | updateText_LabelWidget(back, text_LabelWidget(def)); | ||
1700 | setCommand_LabelWidget(back, command_LabelWidget(def)); | ||
1701 | setFlags_Widget(as_Widget(back), alignLeft_WidgetFlag, iFalse); | ||
1702 | setFlags_Widget(as_Widget(back), alignRight_WidgetFlag, iTrue); | ||
1703 | setIcon_LabelWidget(back, 0); | ||
1704 | setFont_LabelWidget(back, defaultBigBold_FontId); | ||
1705 | } | ||
1706 | else if (def != cancel) { | ||
1707 | removeChild_Widget(buttons, def); | ||
1708 | setFont_LabelWidget(def, defaultBigBold_FontId); | ||
1709 | setFlags_Widget(as_Widget(def), | ||
1710 | frameless_WidgetFlag | extraPadding_WidgetFlag | | ||
1711 | noBackground_WidgetFlag, iTrue); | ||
1712 | addChildFlags_Widget(as_Widget(back), iClob(def), moveToParentRightEdge_WidgetFlag); | ||
1713 | updateSize_LabelWidget(def); | ||
1714 | } | ||
1715 | /* Action buttons are added in the bottom as extra buttons. */ { | ||
1716 | iBool isFirstAction = iTrue; | ||
1717 | iForEach(ObjectList, i, children_Widget(buttons)) { | ||
1718 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
1719 | i.object != cancel && i.object != def) { | ||
1720 | iLabelWidget *item = i.object; | ||
1721 | setBackgroundColor_Widget(i.object, uiBackgroundSidebar_ColorId); | ||
1722 | setFont_LabelWidget(item, defaultBig_FontId); | ||
1723 | removeChild_Widget(buttons, item); | ||
1724 | addChildFlags_Widget(topPanel, iClob(item), panelButtonFlags | | ||
1725 | (isFirstAction ? borderTop_WidgetFlag : 0)); | ||
1726 | updateSize_LabelWidget(item); | ||
1727 | isFirstAction = iFalse; | ||
1728 | } | ||
1729 | } | ||
1730 | } | ||
1731 | iRelease(removeChild_Widget(parent_Widget(buttons), buttons)); | ||
1732 | /* Styling for remaining elements. */ | ||
1733 | iForEach(ObjectList, i, children_Widget(topPanel)) { | ||
1734 | if (isInstance_Object(i.object, &Class_LabelWidget) && | ||
1735 | isEmpty_String(command_LabelWidget(i.object)) && | ||
1736 | isEmpty_String(id_Widget(i.object))) { | ||
1737 | setFlags_Widget(i.object, alignLeft_WidgetFlag, iTrue); | ||
1738 | if (font_LabelWidget(i.object) == uiLabel_FontId) { | ||
1739 | setFont_LabelWidget(i.object, uiContent_FontId); | ||
1740 | } | ||
1741 | } | ||
1742 | } | ||
1743 | } | ||
1744 | addChildFlags_Widget(sheet, iClob(navi), | ||
1745 | drawBackgroundToVerticalSafeArea_WidgetFlag | | ||
1746 | arrangeHeight_WidgetFlag | resizeWidthOfChildren_WidgetFlag | | ||
1747 | resizeToParentWidth_WidgetFlag | arrangeVertical_WidgetFlag); | ||
1748 | } | ||
1749 | updateSheetPanelMetrics_(sheet); | ||
1750 | iAssert(sheet->parent); | ||
1751 | arrange_Widget(sheet->parent); | ||
1752 | postCommand_App("widget.overflow"); /* with the correct dimensions */ | ||
1753 | // printTree_Widget(sheet); | ||
1754 | } | ||
1755 | else { | ||
1756 | arrange_Widget(sheet); | ||
1757 | } | ||
1758 | postRefresh_App(); | ||
1759 | } | ||
1760 | |||
1761 | void makeFilePath_Widget(iWidget * parent, | ||
1762 | const iString *initialPath, | ||
1763 | const char * title, | ||
1764 | const char * acceptLabel, | ||
1765 | const char * command) { | ||
1766 | setFocus_Widget(NULL); | ||
1767 | // processEvents_App(postedEventsOnly_AppEventMode); | ||
1768 | iWidget *dlg = makeSheet_Widget(command); | ||
1769 | setCommandHandler_Widget(dlg, filePathHandler_); | ||
1770 | addChild_Widget(parent, iClob(dlg)); | ||
1771 | addChildFlags_Widget(dlg, iClob(new_LabelWidget(title, NULL)), frameless_WidgetFlag); | ||
1772 | iInputWidget *input = addChild_Widget(dlg, iClob(new_InputWidget(0))); | ||
1773 | if (initialPath) { | ||
1774 | setText_InputWidget(input, collect_String(makeRelative_Path(initialPath))); | ||
1775 | } | ||
1776 | setId_Widget(as_Widget(input), "input"); | ||
1777 | as_Widget(input)->rect.size.x = dlg->rect.size.x; | ||
1778 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | ||
1779 | iWidget *div = new_Widget(); { | ||
1780 | setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); | ||
1781 | addChild_Widget(div, iClob(newKeyMods_LabelWidget("${cancel}", SDLK_ESCAPE, 0, "filepath.cancel"))); | ||
1782 | addChild_Widget(div, iClob(newKeyMods_LabelWidget(acceptLabel, SDLK_RETURN, 0, "filepath.accept"))); | ||
1783 | } | ||
1784 | addChild_Widget(dlg, iClob(div)); | ||
1785 | finalizeSheet_Widget(dlg); | ||
1786 | setFocus_Widget(as_Widget(input)); | ||
1787 | } | ||
1788 | |||
1789 | static void acceptValueInput_(iWidget *dlg) { | 1136 | static void acceptValueInput_(iWidget *dlg) { |
1790 | const iInputWidget *input = findChild_Widget(dlg, "input"); | 1137 | const iInputWidget *input = findChild_Widget(dlg, "input"); |
1791 | if (!isEmpty_String(id_Widget(dlg))) { | 1138 | if (!isEmpty_String(id_Widget(dlg))) { |
@@ -1806,7 +1153,8 @@ static void updateValueInputWidth_(iWidget *dlg) { | |||
1806 | dlg->rect.size.x = rootSize.x; | 1153 | dlg->rect.size.x = rootSize.x; |
1807 | } | 1154 | } |
1808 | else { | 1155 | else { |
1809 | dlg->rect.size.x = iMaxi(iMaxi(rootSize.x / 2, title->rect.size.x), prompt->rect.size.x); | 1156 | dlg->rect.size.x = |
1157 | iMin(rootSize.x, iMaxi(iMaxi(100 * gap_UI, title->rect.size.x), prompt->rect.size.x)); | ||
1810 | } | 1158 | } |
1811 | } | 1159 | } |
1812 | 1160 | ||
@@ -1880,6 +1228,12 @@ iWidget *makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions) { | |||
1880 | if (*label == '*' || *label == '&') { | 1228 | if (*label == '*' || *label == '&') { |
1881 | continue; /* Special value selection items for a Question dialog. */ | 1229 | continue; /* Special value selection items for a Question dialog. */ |
1882 | } | 1230 | } |
1231 | if (startsWith_CStr(label, "```")) { | ||
1232 | /* Annotation. */ | ||
1233 | iLabelWidget *annotation = addChild_Widget(div, iClob(new_LabelWidget(label + 3, NULL))); | ||
1234 | setTextColor_LabelWidget(annotation, uiTextAction_ColorId); | ||
1235 | continue; | ||
1236 | } | ||
1883 | if (!iCmpStr(label, "---")) { | 1237 | if (!iCmpStr(label, "---")) { |
1884 | /* Separator.*/ | 1238 | /* Separator.*/ |
1885 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); | 1239 | addChildFlags_Widget(div, iClob(new_Widget()), expand_WidgetFlag); |
@@ -1945,7 +1299,7 @@ iWidget *makeValueInput_Widget(iWidget *parent, const iString *initialValue, con | |||
1945 | iClob(makeDialogButtons_Widget( | 1299 | iClob(makeDialogButtons_Widget( |
1946 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, | 1300 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, { acceptLabel, 0, 0, "valueinput.accept" } }, |
1947 | 2))); | 1301 | 2))); |
1948 | finalizeSheet_Widget(dlg); | 1302 | finalizeSheet_Mobile(dlg); |
1949 | if (parent) { | 1303 | if (parent) { |
1950 | setFocus_Widget(as_Widget(input)); | 1304 | setFocus_Widget(as_Widget(input)); |
1951 | } | 1305 | } |
@@ -2002,13 +1356,17 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
2002 | const iMenuItem *item = &items[i]; | 1356 | const iMenuItem *item = &items[i]; |
2003 | const char first = item->label[0]; | 1357 | const char first = item->label[0]; |
2004 | if (first == '*' || first == '&') { | 1358 | if (first == '*' || first == '&') { |
2005 | addChildFlags_Widget(dlg, | 1359 | iLabelWidget *option = |
1360 | addChildFlags_Widget(dlg, | ||
2006 | iClob(newKeyMods_LabelWidget(item->label + 1, | 1361 | iClob(newKeyMods_LabelWidget(item->label + 1, |
2007 | item->key, | 1362 | item->key, |
2008 | item->kmods, | 1363 | item->kmods, |
2009 | item->command)), | 1364 | item->command)), |
2010 | resizeToParentWidth_WidgetFlag | | 1365 | resizeToParentWidth_WidgetFlag | |
2011 | (first == '&' ? selected_WidgetFlag : 0)); | 1366 | (first == '&' ? selected_WidgetFlag : 0)); |
1367 | if (deviceType_App() != desktop_AppDeviceType) { | ||
1368 | setFont_LabelWidget(option, defaultBig_FontId); | ||
1369 | } | ||
2012 | } | 1370 | } |
2013 | } | 1371 | } |
2014 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); | 1372 | addChild_Widget(dlg, iClob(makePadding_Widget(gap_UI))); |
@@ -2016,7 +1374,7 @@ iWidget *makeQuestion_Widget(const char *title, const char *msg, | |||
2016 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1374 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
2017 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't | 1375 | arrange_Widget(dlg); /* BUG: This extra arrange shouldn't be needed but the dialog won't |
2018 | be arranged correctly unless it's here. */ | 1376 | be arranged correctly unless it's here. */ |
2019 | finalizeSheet_Widget(dlg); | 1377 | finalizeSheet_Mobile(dlg); |
2020 | return dlg; | 1378 | return dlg; |
2021 | } | 1379 | } |
2022 | 1380 | ||
@@ -2155,7 +1513,7 @@ static int cmp_MenuItem_(const void *e1, const void *e2) { | |||
2155 | #endif | 1513 | #endif |
2156 | 1514 | ||
2157 | void updatePreferencesLayout_Widget(iWidget *prefs) { | 1515 | void updatePreferencesLayout_Widget(iWidget *prefs) { |
2158 | if (!prefs || deviceType_App() == phone_AppDeviceType) { | 1516 | if (!prefs || deviceType_App() != desktop_AppDeviceType) { |
2159 | return; | 1517 | return; |
2160 | } | 1518 | } |
2161 | /* Doing manual layout here because the widget arranging logic isn't sophisticated enough. */ | 1519 | /* Doing manual layout here because the widget arranging logic isn't sophisticated enough. */ |
@@ -2248,14 +1606,16 @@ iWidget *makePreferences_Widget(void) { | |||
2248 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); | 1606 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.hoverlink"))); |
2249 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); | 1607 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.archive.openindex}"))); |
2250 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.archive.openindex"))); | 1608 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.archive.openindex"))); |
2251 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); | 1609 | if (deviceType_App() != phone_AppDeviceType) { |
2252 | iWidget *pinSplit = new_Widget(); | 1610 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.pinsplit}"))); |
2253 | /* Split mode document pinning. */ { | 1611 | iWidget *pinSplit = new_Widget(); |
2254 | addRadioButton_(pinSplit, "prefs.pinsplit.0", "${prefs.pinsplit.none}", "pinsplit.set arg:0"); | 1612 | /* Split mode document pinning. */ { |
2255 | addRadioButton_(pinSplit, "prefs.pinsplit.1", "${prefs.pinsplit.left}", "pinsplit.set arg:1"); | 1613 | addRadioButton_(pinSplit, "prefs.pinsplit.0", "${prefs.pinsplit.none}", "pinsplit.set arg:0"); |
2256 | addRadioButton_(pinSplit, "prefs.pinsplit.2", "${prefs.pinsplit.right}", "pinsplit.set arg:2"); | 1614 | addRadioButton_(pinSplit, "prefs.pinsplit.1", "${prefs.pinsplit.left}", "pinsplit.set arg:1"); |
2257 | } | 1615 | addRadioButton_(pinSplit, "prefs.pinsplit.2", "${prefs.pinsplit.right}", "pinsplit.set arg:2"); |
2258 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1616 | } |
1617 | addChildFlags_Widget(values, iClob(pinSplit), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | ||
1618 | } | ||
2259 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); | 1619 | addChild_Widget(headings, iClob(makePadding_Widget(bigGap))); |
2260 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); | 1620 | addChild_Widget(values, iClob(makePadding_Widget(bigGap))); |
2261 | /* UI languages. */ { | 1621 | /* UI languages. */ { |
@@ -2268,6 +1628,7 @@ iWidget *makePreferences_Widget(void) { | |||
2268 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, | 1628 | { "${lang.fr} - fr", 0, 0, "uilang id:fr" }, |
2269 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, | 1629 | { "${lang.ia} - ia", 0, 0, "uilang id:ia" }, |
2270 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, | 1630 | { "${lang.ie} - ie", 0, 0, "uilang id:ie" }, |
1631 | { "${lang.pl} - pl", 0, 0, "uilang id:pl" }, | ||
2271 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, | 1632 | { "${lang.ru} - ru", 0, 0, "uilang id:ru" }, |
2272 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, | 1633 | { "${lang.sr} - sr", 0, 0, "uilang id:sr" }, |
2273 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, | 1634 | { "${lang.tok} - tok", 0, 0, "uilang id:tok" }, |
@@ -2475,7 +1836,8 @@ iWidget *makePreferences_Widget(void) { | |||
2475 | iClob(makeDialogButtons_Widget( | 1836 | iClob(makeDialogButtons_Widget( |
2476 | (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); | 1837 | (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); |
2477 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1838 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
2478 | finalizeSheet_Widget(dlg); | 1839 | finalizeSheet_Mobile(dlg); |
1840 | setupSheetTransition_Mobile(dlg, iTrue); | ||
2479 | // printTree_Widget(dlg); | 1841 | // printTree_Widget(dlg); |
2480 | return dlg; | 1842 | return dlg; |
2481 | } | 1843 | } |
@@ -2519,15 +1881,10 @@ iWidget *makeBookmarkEditor_Widget(void) { | |||
2519 | "bmed.accept" } }, | 1881 | "bmed.accept" } }, |
2520 | 2))); | 1882 | 2))); |
2521 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 1883 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2522 | finalizeSheet_Widget(dlg); | 1884 | finalizeSheet_Mobile(dlg); |
2523 | return dlg; | 1885 | return dlg; |
2524 | } | 1886 | } |
2525 | 1887 | ||
2526 | static void enableSidebars_(void) { | ||
2527 | setFlags_Widget(findWidget_App("sidebar"), disabled_WidgetFlag, iFalse); | ||
2528 | setFlags_Widget(findWidget_App("sidebar2"), disabled_WidgetFlag, iFalse); | ||
2529 | } | ||
2530 | |||
2531 | static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) { | 1888 | static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) { |
2532 | if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { | 1889 | if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { |
2533 | if (equal_Command(cmd, "bmed.accept")) { | 1890 | if (equal_Command(cmd, "bmed.accept")) { |
@@ -2552,8 +1909,6 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons | |||
2552 | postCommand_App("bookmarks.changed"); | 1909 | postCommand_App("bookmarks.changed"); |
2553 | } | 1910 | } |
2554 | destroy_Widget(editor); | 1911 | destroy_Widget(editor); |
2555 | /* Sidebars are disabled when a dialog is opened. */ | ||
2556 | enableSidebars_(); | ||
2557 | return iTrue; | 1912 | return iTrue; |
2558 | } | 1913 | } |
2559 | return iFalse; | 1914 | return iFalse; |
@@ -2583,8 +1938,6 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i | |||
2583 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | 1938 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { |
2584 | if (equal_Command(cmd, "cancel")) { | 1939 | if (equal_Command(cmd, "cancel")) { |
2585 | destroy_Widget(dlg); | 1940 | destroy_Widget(dlg); |
2586 | /* Sidebars are disabled when a dialog is opened. */ | ||
2587 | enableSidebars_(); | ||
2588 | return iTrue; | 1941 | return iTrue; |
2589 | } | 1942 | } |
2590 | if (equal_Command(cmd, "feedcfg.accept")) { | 1943 | if (equal_Command(cmd, "feedcfg.accept")) { |
@@ -2619,8 +1972,6 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | |||
2619 | } | 1972 | } |
2620 | postCommand_App("bookmarks.changed"); | 1973 | postCommand_App("bookmarks.changed"); |
2621 | destroy_Widget(dlg); | 1974 | destroy_Widget(dlg); |
2622 | /* Sidebars are disabled when a dialog is opened. */ | ||
2623 | enableSidebars_(); | ||
2624 | return iTrue; | 1975 | return iTrue; |
2625 | } | 1976 | } |
2626 | return iFalse; | 1977 | return iFalse; |
@@ -2659,7 +2010,7 @@ iWidget *makeFeedSettings_Widget(uint32_t bookmarkId) { | |||
2659 | arrange_Widget(dlg); | 2010 | arrange_Widget(dlg); |
2660 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; | 2011 | as_Widget(input)->rect.size.x = 100 * gap_UI - headings->rect.size.x; |
2661 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2012 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2662 | finalizeSheet_Widget(dlg); | 2013 | finalizeSheet_Mobile(dlg); |
2663 | /* Initialize. */ { | 2014 | /* Initialize. */ { |
2664 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; | 2015 | const iBookmark *bm = bookmarkId ? get_Bookmarks(bookmarks_App(), bookmarkId) : NULL; |
2665 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), | 2016 | setText_InputWidget(findChild_Widget(dlg, "feedcfg.title"), |
@@ -2735,7 +2086,7 @@ iWidget *makeIdentityCreation_Widget(void) { | |||
2735 | "ident.accept" } }, | 2086 | "ident.accept" } }, |
2736 | 2))); | 2087 | 2))); |
2737 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2088 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2738 | finalizeSheet_Widget(dlg); | 2089 | finalizeSheet_Mobile(dlg); |
2739 | return dlg; | 2090 | return dlg; |
2740 | } | 2091 | } |
2741 | 2092 | ||
@@ -2786,6 +2137,7 @@ int languageIndex_CStr(const char *langId) { | |||
2786 | iWidget *makeTranslation_Widget(iWidget *parent) { | 2137 | iWidget *makeTranslation_Widget(iWidget *parent) { |
2787 | iWidget *dlg = makeSheet_Widget("xlt"); | 2138 | iWidget *dlg = makeSheet_Widget("xlt"); |
2788 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); | 2139 | setFlags_Widget(dlg, keepOnTop_WidgetFlag, iFalse); |
2140 | dlg->minSize.x = 70 * gap_UI; | ||
2789 | setCommandHandler_Widget(dlg, translationHandler_); | 2141 | setCommandHandler_Widget(dlg, translationHandler_); |
2790 | addChildFlags_Widget(dlg, | 2142 | addChildFlags_Widget(dlg, |
2791 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), | 2143 | iClob(new_LabelWidget(uiHeading_ColorEscape "${heading.translate}", NULL)), |
@@ -2831,6 +2183,6 @@ iWidget *makeTranslation_Widget(iWidget *parent) { | |||
2831 | 2))); | 2183 | 2))); |
2832 | addChild_Widget(parent, iClob(dlg)); | 2184 | addChild_Widget(parent, iClob(dlg)); |
2833 | arrange_Widget(dlg); | 2185 | arrange_Widget(dlg); |
2834 | finalizeSheet_Widget(dlg); | 2186 | finalizeSheet_Mobile(dlg); |
2835 | return dlg; | 2187 | return dlg; |
2836 | } | 2188 | } |
diff --git a/src/ui/util.h b/src/ui/util.h index 50845280..43aeb172 100644 --- a/src/ui/util.h +++ b/src/ui/util.h | |||
@@ -22,6 +22,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
22 | 22 | ||
23 | #pragma once | 23 | #pragma once |
24 | 24 | ||
25 | #include "mobile.h" | ||
26 | |||
25 | #include <the_Foundation/string.h> | 27 | #include <the_Foundation/string.h> |
26 | #include <the_Foundation/rect.h> | 28 | #include <the_Foundation/rect.h> |
27 | #include <the_Foundation/vec2.h> | 29 | #include <the_Foundation/vec2.h> |
@@ -255,9 +257,8 @@ size_t tabCount_Widget (const iWidget *tabs); | |||
255 | 257 | ||
256 | /*-----------------------------------------------------------------------------------------------*/ | 258 | /*-----------------------------------------------------------------------------------------------*/ |
257 | 259 | ||
258 | iWidget * makeSheet_Widget (const char *id); | 260 | iWidget * makeSheet_Widget (const char *id); |
259 | void finalizeSheet_Widget (iWidget *sheet); | 261 | iWidget * makeDialogButtons_Widget (const iMenuItem *actions, size_t numActions); |
260 | iWidget * makeDialogButtons_Widget(const iMenuItem *actions, size_t numActions); | ||
261 | 262 | ||
262 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 263 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
263 | const char *labelText, const char *inputId, | 264 | const char *labelText, const char *inputId, |
diff --git a/src/ui/widget.c b/src/ui/widget.c index c1c920d2..3439fb1b 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -23,6 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
23 | #include "widget.h" | 23 | #include "widget.h" |
24 | 24 | ||
25 | #include "app.h" | 25 | #include "app.h" |
26 | #include "periodic.h" | ||
26 | #include "touch.h" | 27 | #include "touch.h" |
27 | #include "command.h" | 28 | #include "command.h" |
28 | #include "paint.h" | 29 | #include "paint.h" |
@@ -39,6 +40,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
39 | # include "../ios.h" | 40 | # include "../ios.h" |
40 | #endif | 41 | #endif |
41 | 42 | ||
43 | static void printInfo_Widget_(const iWidget *); | ||
44 | |||
42 | void releaseChildren_Widget(iWidget *d) { | 45 | void releaseChildren_Widget(iWidget *d) { |
43 | iForEach(ObjectList, i, d->children) { | 46 | iForEach(ObjectList, i, d->children) { |
44 | ((iWidget *) i.object)->parent = NULL; /* the actual reference being held */ | 47 | ((iWidget *) i.object)->parent = NULL; /* the actual reference being held */ |
@@ -55,6 +58,7 @@ void init_Widget(iWidget *d) { | |||
55 | d->rect = zero_Rect(); | 58 | d->rect = zero_Rect(); |
56 | d->minSize = zero_I2(); | 59 | d->minSize = zero_I2(); |
57 | d->sizeRef = NULL; | 60 | d->sizeRef = NULL; |
61 | d->offsetRef = NULL; | ||
58 | d->bgColor = none_ColorId; | 62 | d->bgColor = none_ColorId; |
59 | d->frameColor = none_ColorId; | 63 | d->frameColor = none_ColorId; |
60 | init_Anim(&d->visualOffset, 0.0f); | 64 | init_Anim(&d->visualOffset, 0.0f); |
@@ -92,6 +96,7 @@ void deinit_Widget(iWidget *d) { | |||
92 | } | 96 | } |
93 | 97 | ||
94 | static void aboutToBeDestroyed_Widget_(iWidget *d) { | 98 | static void aboutToBeDestroyed_Widget_(iWidget *d) { |
99 | d->flags |= destroyPending_WidgetFlag; | ||
95 | if (isFocused_Widget(d)) { | 100 | if (isFocused_Widget(d)) { |
96 | setFocus_Widget(NULL); | 101 | setFocus_Widget(NULL); |
97 | return; | 102 | return; |
@@ -99,6 +104,7 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) { | |||
99 | if (flags_Widget(d) & keepOnTop_WidgetFlag) { | 104 | if (flags_Widget(d) & keepOnTop_WidgetFlag) { |
100 | removeOne_PtrArray(onTop_Root(d->root), d); | 105 | removeOne_PtrArray(onTop_Root(d->root), d); |
101 | } | 106 | } |
107 | remove_Periodic(periodic_App(), d); | ||
102 | if (isHover_Widget(d)) { | 108 | if (isHover_Widget(d)) { |
103 | get_Window()->hover = NULL; | 109 | get_Window()->hover = NULL; |
104 | } | 110 | } |
@@ -149,6 +155,11 @@ void setFlags_Widget(iWidget *d, int64_t flags, iBool set) { | |||
149 | removeOne_PtrArray(onTop, d); | 155 | removeOne_PtrArray(onTop, d); |
150 | } | 156 | } |
151 | } | 157 | } |
158 | if (d->flags & arrangeWidth_WidgetFlag && | ||
159 | d->flags & resizeToParentWidth_WidgetFlag) { | ||
160 | printf("[Widget] Conflicting flags for "); | ||
161 | identify_Widget(d); | ||
162 | } | ||
152 | } | 163 | } |
153 | } | 164 | } |
154 | 165 | ||
@@ -208,7 +219,7 @@ void setVisualOffset_Widget(iWidget *d, int value, uint32_t span, int animFlags) | |||
208 | else { | 219 | else { |
209 | setValue_Anim(&d->visualOffset, value, span); | 220 | setValue_Anim(&d->visualOffset, value, span); |
210 | d->visualOffset.flags = animFlags; | 221 | d->visualOffset.flags = animFlags; |
211 | addTicker_App(visualOffsetAnimation_Widget_, d); | 222 | addTickerRoot_App(visualOffsetAnimation_Widget_, d->root, d); |
212 | } | 223 | } |
213 | } | 224 | } |
214 | 225 | ||
@@ -364,10 +375,13 @@ iRect innerBounds_Widget(const iWidget *d) { | |||
364 | return ib; | 375 | return ib; |
365 | } | 376 | } |
366 | 377 | ||
367 | //iLocalDef iBool isArranged_Widget_(const iWidget *d) { | 378 | iRect innerBoundsWithoutVisualOffset_Widget(const iWidget *d) { |
368 | // return !isCollapsed_Widget_(d) && ~d->flags & fixedPosition_WidgetFlag; | 379 | iRect ib = adjusted_Rect(boundsWithoutVisualOffset_Widget(d), |
369 | //} | 380 | init_I2(d->padding[0], d->padding[1]), |
370 | 381 | init_I2(-d->padding[2], -d->padding[3])); | |
382 | ib.size = max_I2(zero_I2(), ib.size); | ||
383 | return ib; | ||
384 | } | ||
371 | 385 | ||
372 | static size_t numArrangedChildren_Widget_(const iWidget *d) { | 386 | static size_t numArrangedChildren_Widget_(const iWidget *d) { |
373 | size_t count = 0; | 387 | size_t count = 0; |
@@ -469,12 +483,19 @@ static void arrange_Widget_(iWidget *d) { | |||
469 | const int expCount = numExpandingChildren_Widget_(d); | 483 | const int expCount = numExpandingChildren_Widget_(d); |
470 | TRACE(d, "%d expanding children", expCount); | 484 | TRACE(d, "%d expanding children", expCount); |
471 | /* Resize children to fill the parent widget. */ | 485 | /* Resize children to fill the parent widget. */ |
486 | iAssert((d->flags & (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag)) != | ||
487 | (resizeToParentWidth_WidgetFlag | arrangeWidth_WidgetFlag)); | ||
472 | if (d->flags & resizeChildren_WidgetFlag) { | 488 | if (d->flags & resizeChildren_WidgetFlag) { |
473 | const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0, | 489 | const iInt2 dirs = init_I2((d->flags & resizeWidthOfChildren_WidgetFlag) != 0, |
474 | (d->flags & resizeHeightOfChildren_WidgetFlag) != 0); | 490 | (d->flags & resizeHeightOfChildren_WidgetFlag) != 0); |
475 | #if !defined (NDEBUG) | 491 | #if !defined (NDEBUG) |
476 | /* Check for conflicting flags. */ | 492 | /* Check for conflicting flags. */ |
477 | if (dirs.x) iAssert(~d->flags & arrangeWidth_WidgetFlag); | 493 | if (dirs.x) { |
494 | if (d->flags & arrangeWidth_WidgetFlag) { | ||
495 | identify_Widget(d); | ||
496 | } | ||
497 | iAssert(~d->flags & arrangeWidth_WidgetFlag); | ||
498 | } | ||
478 | if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag); | 499 | if (dirs.y) iAssert(~d->flags & arrangeHeight_WidgetFlag); |
479 | #endif | 500 | #endif |
480 | TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y, | 501 | TRACE(d, "resize children, x:%d y:%d (own size: %dx%d)", dirs.x, dirs.y, |
@@ -697,26 +718,32 @@ static void arrange_Widget_(iWidget *d) { | |||
697 | TRACE(d, "END"); | 718 | TRACE(d, "END"); |
698 | } | 719 | } |
699 | 720 | ||
700 | #if 0 | 721 | static void resetArrangement_Widget_(iWidget *d) { |
701 | void resetSize_Widget(iWidget *d) { | ||
702 | if (~d->flags & fixedWidth_WidgetFlag) { | ||
703 | d->rect.size.x = d->minSize.x; | ||
704 | } | ||
705 | if (~d->flags & fixedHeight_WidgetFlag) { | ||
706 | d->rect.size.y = d->minSize.y; | ||
707 | } | ||
708 | iForEach(ObjectList, i, children_Widget(d)) { | 722 | iForEach(ObjectList, i, children_Widget(d)) { |
709 | iWidget *child = as_Widget(i.object); | 723 | iWidget *child = as_Widget(i.object); |
710 | if (isArrangedSize_Widget_(child)) { | 724 | resetArrangement_Widget_(child); |
711 | resetSize_Widget(child); | 725 | if (isArrangedPos_Widget_(child)) { |
726 | if (d->flags & arrangeHorizontal_WidgetFlag) { | ||
727 | child->rect.pos.x = 0; | ||
728 | } | ||
729 | if (d->flags & resizeWidthOfChildren_WidgetFlag && child->flags & expand_WidgetFlag) { | ||
730 | child->rect.size.x = 0; | ||
731 | } | ||
732 | if (d->flags & arrangeVertical_WidgetFlag) { | ||
733 | child->rect.pos.y = 0; | ||
734 | } | ||
735 | if (d->flags & resizeHeightOfChildren_WidgetFlag && child->flags & expand_WidgetFlag) { | ||
736 | child->rect.size.y = 0; | ||
737 | } | ||
712 | } | 738 | } |
713 | } | 739 | } |
714 | } | 740 | } |
715 | #endif | ||
716 | 741 | ||
717 | void arrange_Widget(iWidget *d) { | 742 | void arrange_Widget(iWidget *d) { |
718 | //resetSize_Widget_(d); /* back to initial default sizes */ | 743 | if (d) { |
719 | arrange_Widget_(d); | 744 | resetArrangement_Widget_(d); /* back to initial default sizes */ |
745 | arrange_Widget_(d); | ||
746 | } | ||
720 | } | 747 | } |
721 | 748 | ||
722 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | 749 | static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { |
@@ -729,6 +756,16 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | |||
729 | pos->y += off; | 756 | pos->y += off; |
730 | } | 757 | } |
731 | } | 758 | } |
759 | if (d->flags & refChildrenOffset_WidgetFlag) { | ||
760 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { | ||
761 | const iWidget *child = i.object; | ||
762 | if (child == d) continue; | ||
763 | if (child->flags & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
764 | const int invOff = size_Root(d->root).x - iRound(value_Anim(&child->visualOffset)); | ||
765 | pos->x -= invOff / 4; | ||
766 | } | ||
767 | } | ||
768 | } | ||
732 | } | 769 | } |
733 | 770 | ||
734 | iRect bounds_Widget(const iWidget *d) { | 771 | iRect bounds_Widget(const iWidget *d) { |
@@ -919,18 +956,23 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
919 | } | 956 | } |
920 | } | 957 | } |
921 | if (class_Widget(d)->processEvent(d, ev)) { | 958 | if (class_Widget(d)->processEvent(d, ev)) { |
959 | iAssert(get_Root() == d->root); | ||
922 | return iTrue; | 960 | return iTrue; |
923 | } | 961 | } |
924 | } | 962 | } |
963 | iAssert(get_Root() == d->root); | ||
925 | return iFalse; | 964 | return iFalse; |
926 | } | 965 | } |
927 | 966 | ||
928 | static iBool scrollOverflow_Widget_(iWidget *d, int delta) { | 967 | iBool scrollOverflow_Widget(iWidget *d, int delta) { |
929 | iRect bounds = bounds_Widget(d); | 968 | iRect bounds = boundsWithoutVisualOffset_Widget(d); |
930 | const iInt2 rootSize = size_Root(d->root); | 969 | const iInt2 rootSize = size_Root(d->root); |
931 | const iRect winRect = safeRect_Root(d->root); | 970 | const iRect winRect = safeRect_Root(d->root); |
932 | const int yTop = top_Rect(winRect); | 971 | const int yTop = top_Rect(winRect); |
933 | const int yBottom = bottom_Rect(winRect); | 972 | const int yBottom = bottom_Rect(winRect); |
973 | if (top_Rect(bounds) >= yTop && bottom_Rect(bounds) < yBottom) { | ||
974 | return iFalse; /* fits inside just fine */ | ||
975 | } | ||
934 | //const int safeBottom = rootSize.y - yBottom; | 976 | //const int safeBottom = rootSize.y - yBottom; |
935 | bounds.pos.y += delta; | 977 | bounds.pos.y += delta; |
936 | const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; | 978 | const iRangei range = { bottom_Rect(winRect) - height_Rect(bounds), yTop }; |
@@ -978,7 +1020,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
978 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { | 1020 | if (!isPerPixel_MouseWheelEvent(&ev->wheel)) { |
979 | step *= lineHeight_Text(uiLabel_FontId); | 1021 | step *= lineHeight_Text(uiLabel_FontId); |
980 | } | 1022 | } |
981 | if (scrollOverflow_Widget_(d, step)) { | 1023 | if (scrollOverflow_Widget(d, step)) { |
982 | return iTrue; | 1024 | return iTrue; |
983 | } | 1025 | } |
984 | } | 1026 | } |
@@ -987,10 +1029,11 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) { | |||
987 | if (d->flags & overflowScrollable_WidgetFlag && | 1029 | if (d->flags & overflowScrollable_WidgetFlag && |
988 | ~d->flags & visualOffset_WidgetFlag && | 1030 | ~d->flags & visualOffset_WidgetFlag && |
989 | isCommand_UserEvent(ev, "widget.overflow")) { | 1031 | isCommand_UserEvent(ev, "widget.overflow")) { |
990 | scrollOverflow_Widget_(d, 0); /* check bounds */ | 1032 | scrollOverflow_Widget(d, 0); /* check bounds */ |
991 | } | 1033 | } |
992 | if (ev->user.code == command_UserEventCode && d->commandHandler && | 1034 | if (ev->user.code == command_UserEventCode && d->commandHandler && |
993 | d->commandHandler(d, ev->user.data1)) { | 1035 | d->commandHandler(d, ev->user.data1)) { |
1036 | iAssert(get_Root() == d->root); | ||
994 | return iTrue; | 1037 | return iTrue; |
995 | } | 1038 | } |
996 | break; | 1039 | break; |
@@ -1043,11 +1086,18 @@ void drawBackground_Widget(const iWidget *d) { | |||
1043 | init_Paint(&p); | 1086 | init_Paint(&p); |
1044 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1087 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1045 | } | 1088 | } |
1046 | 1089 | const iBool isFaded = fadeBackground && | |
1047 | if (fadeBackground && ~d->flags & noFadeBackground_WidgetFlag) { | 1090 | ~d->flags & noFadeBackground_WidgetFlag && |
1091 | ~d->flags & destroyPending_WidgetFlag; | ||
1092 | if (isFaded) { | ||
1048 | iPaint p; | 1093 | iPaint p; |
1049 | init_Paint(&p); | 1094 | init_Paint(&p); |
1050 | p.alpha = 0x50; | 1095 | p.alpha = 0x50; |
1096 | if (flags_Widget(d) & (visualOffset_WidgetFlag | dragged_WidgetFlag)) { | ||
1097 | const float area = d->rect.size.x * d->rect.size.y; | ||
1098 | const float visibleArea = area_Rect(intersect_Rect(bounds_Widget(d), rect_Root(d->root))); | ||
1099 | p.alpha *= (area > 0 ? visibleArea / area : 0.0f); | ||
1100 | } | ||
1051 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); | 1101 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_BLEND); |
1052 | int fadeColor; | 1102 | int fadeColor; |
1053 | switch (colorTheme_App()) { | 1103 | switch (colorTheme_App()) { |
@@ -1061,9 +1111,7 @@ void drawBackground_Widget(const iWidget *d) { | |||
1061 | fadeColor = gray50_ColorId; | 1111 | fadeColor = gray50_ColorId; |
1062 | break; | 1112 | break; |
1063 | } | 1113 | } |
1064 | fillRect_Paint(&p, | 1114 | fillRect_Paint(&p, rect_Root(d->root), fadeColor); |
1065 | rect_Root(d->root), | ||
1066 | fadeColor); | ||
1067 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); | 1115 | SDL_SetRenderDrawBlendMode(renderer_Window(get_Window()), SDL_BLENDMODE_NONE); |
1068 | } | 1116 | } |
1069 | if (d->bgColor >= 0 || d->frameColor >= 0) { | 1117 | if (d->bgColor >= 0 || d->frameColor >= 0) { |
@@ -1319,6 +1367,22 @@ iAny *findParentClass_Widget(const iWidget *d, const iAnyClass *class) { | |||
1319 | return i; | 1367 | return i; |
1320 | } | 1368 | } |
1321 | 1369 | ||
1370 | iAny *findOverflowScrollable_Widget(iWidget *d) { | ||
1371 | const iRect rootRect = rect_Root(d->root); | ||
1372 | for (iWidget *w = d; w; w = parent_Widget(w)) { | ||
1373 | if (flags_Widget(w) & overflowScrollable_WidgetFlag) { | ||
1374 | const iRect bounds = boundsWithoutVisualOffset_Widget(w); | ||
1375 | if ((bottom_Rect(bounds) > bottom_Rect(rootRect) || | ||
1376 | top_Rect(bounds) < top_Rect(rootRect)) && | ||
1377 | !hasVisibleChildOnTop_Widget(w)) { | ||
1378 | return w; | ||
1379 | } | ||
1380 | return NULL; | ||
1381 | } | ||
1382 | } | ||
1383 | return NULL; | ||
1384 | } | ||
1385 | |||
1322 | size_t childCount_Widget(const iWidget *d) { | 1386 | size_t childCount_Widget(const iWidget *d) { |
1323 | if (!d->children) return 0; | 1387 | if (!d->children) return 0; |
1324 | return size_ObjectList(d->children); | 1388 | return size_ObjectList(d->children); |
@@ -1567,7 +1631,8 @@ static void printInfo_Widget_(const iWidget *d) { | |||
1567 | cstr_String(text_LabelWidget((const iLabelWidget *) d)), | 1631 | cstr_String(text_LabelWidget((const iLabelWidget *) d)), |
1568 | cstr_String(command_LabelWidget((const iLabelWidget *) d))); | 1632 | cstr_String(command_LabelWidget((const iLabelWidget *) d))); |
1569 | } | 1633 | } |
1570 | printf("size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s\n", | 1634 | printf("pos:%d,%d size:%dx%d {min:%dx%d} [%d..%d %d:%d] flags:%08llx%s%s%s%s%s%s%s\n", |
1635 | d->rect.pos.x, d->rect.pos.y, | ||
1571 | d->rect.size.x, d->rect.size.y, | 1636 | d->rect.size.x, d->rect.size.y, |
1572 | d->minSize.x, d->minSize.y, | 1637 | d->minSize.x, d->minSize.y, |
1573 | d->padding[0], d->padding[2], | 1638 | d->padding[0], d->padding[2], |
@@ -1577,7 +1642,9 @@ static void printInfo_Widget_(const iWidget *d) { | |||
1577 | d->flags & tight_WidgetFlag ? " tight" : "", | 1642 | d->flags & tight_WidgetFlag ? " tight" : "", |
1578 | d->flags & fixedWidth_WidgetFlag ? " fixW" : "", | 1643 | d->flags & fixedWidth_WidgetFlag ? " fixW" : "", |
1579 | d->flags & fixedHeight_WidgetFlag ? " fixH" : "", | 1644 | d->flags & fixedHeight_WidgetFlag ? " fixH" : "", |
1580 | d->flags & resizeToParentWidth_WidgetFlag ? " rsPrnW" : ""); | 1645 | d->flags & resizeToParentWidth_WidgetFlag ? " prnW" : "", |
1646 | d->flags & arrangeWidth_WidgetFlag ? " aW" : "", | ||
1647 | d->flags & resizeWidthOfChildren_WidgetFlag ? " rsWChild" : ""); | ||
1581 | } | 1648 | } |
1582 | 1649 | ||
1583 | static void printTree_Widget_(const iWidget *d, int indent) { | 1650 | static void printTree_Widget_(const iWidget *d, int indent) { |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 5c05e917..79d45f23 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -117,6 +117,9 @@ enum iWidgetFlag { | |||
117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) | 117 | #define parentCannotResizeHeight_WidgetFlag iBit64(58) |
118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) | 118 | #define ignoreForParentWidth_WidgetFlag iBit64(59) |
119 | #define noFadeBackground_WidgetFlag iBit64(60) | 119 | #define noFadeBackground_WidgetFlag iBit64(60) |
120 | #define destroyPending_WidgetFlag iBit64(61) /* TODO: needed? */ | ||
121 | #define edgeDraggable_WidgetFlag iBit64(62) | ||
122 | #define refChildrenOffset_WidgetFlag iBit64(63) /* visual offset determined by the offset of referenced children */ | ||
120 | 123 | ||
121 | enum iWidgetAddPos { | 124 | enum iWidgetAddPos { |
122 | back_WidgetAddPos, | 125 | back_WidgetAddPos, |
@@ -135,13 +138,14 @@ struct Impl_Widget { | |||
135 | iRect rect; | 138 | iRect rect; |
136 | iInt2 minSize; | 139 | iInt2 minSize; |
137 | iWidget * sizeRef; | 140 | iWidget * sizeRef; |
141 | iWidget * offsetRef; | ||
138 | int padding[4]; /* left, top, right, bottom */ | 142 | int padding[4]; /* left, top, right, bottom */ |
139 | iAnim visualOffset; | 143 | iAnim visualOffset; |
140 | int bgColor; | 144 | int bgColor; |
141 | int frameColor; | 145 | int frameColor; |
142 | iObjectList *children; | 146 | iObjectList *children; |
143 | iWidget * parent; | 147 | iWidget * parent; |
144 | iBool (*commandHandler)(iWidget *, const char *); | 148 | iBool (*commandHandler)(iWidget *, const char *); |
145 | iRoot * root; | 149 | iRoot * root; |
146 | }; | 150 | }; |
147 | 151 | ||
@@ -193,6 +197,7 @@ iAny * findChild_Widget (const iWidget *, const char *id); | |||
193 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); | 197 | const iPtrArray *findChildren_Widget (const iWidget *, const char *id); |
194 | iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); | 198 | iAny * findParentClass_Widget (const iWidget *, const iAnyClass *class); |
195 | iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); | 199 | iAny * findFocusable_Widget (const iWidget *startFrom, enum iWidgetFocusDir focusDir); |
200 | iAny * findOverflowScrollable_Widget (iWidget *); | ||
196 | size_t childCount_Widget (const iWidget *); | 201 | size_t childCount_Widget (const iWidget *); |
197 | void draw_Widget (const iWidget *); | 202 | void draw_Widget (const iWidget *); |
198 | void drawBackground_Widget (const iWidget *); | 203 | void drawBackground_Widget (const iWidget *); |
@@ -262,6 +267,7 @@ iAny * child_Widget (iWidget *, size_t index); /* O(n) */ | |||
262 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ | 267 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ |
263 | void arrange_Widget (iWidget *); | 268 | void arrange_Widget (iWidget *); |
264 | void resetSize_Widget (iWidget *); | 269 | void resetSize_Widget (iWidget *); |
270 | iBool scrollOverflow_Widget (iWidget *, int delta); /* moves the widget */ | ||
265 | iBool dispatchEvent_Widget (iWidget *, const SDL_Event *); | 271 | iBool dispatchEvent_Widget (iWidget *, const SDL_Event *); |
266 | iBool processEvent_Widget (iWidget *, const SDL_Event *); | 272 | iBool processEvent_Widget (iWidget *, const SDL_Event *); |
267 | void postCommand_Widget (const iAnyObject *, const char *cmd, ...); | 273 | void postCommand_Widget (const iAnyObject *, const char *cmd, ...); |
diff --git a/src/ui/window.c b/src/ui/window.c index 99430a05..abdc363d 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -171,19 +171,6 @@ int numRoots_Window(const iWindow *d) { | |||
171 | return num; | 171 | return num; |
172 | } | 172 | } |
173 | 173 | ||
174 | static void setupUserInterface_Window(iWindow *d) { | ||
175 | #if defined (iPlatformAppleDesktop) | ||
176 | insertMacMenus_(); | ||
177 | #endif | ||
178 | /* One root is created by default. */ | ||
179 | d->roots[0] = new_Root(); | ||
180 | setCurrent_Root(d->roots[0]); | ||
181 | createUserInterface_Root(d->roots[0]); | ||
182 | setCurrent_Root(NULL); | ||
183 | /* One of the roots always has keyboard input focus. */ | ||
184 | d->keyRoot = d->roots[0]; | ||
185 | } | ||
186 | |||
187 | static void windowSizeChanged_Window_(iWindow *d) { | 174 | static void windowSizeChanged_Window_(iWindow *d) { |
188 | const int numRoots = numRoots_Window(d); | 175 | const int numRoots = numRoots_Window(d); |
189 | const iInt2 rootSize = d->size; | 176 | const iInt2 rootSize = d->size; |
@@ -214,6 +201,19 @@ static void windowSizeChanged_Window_(iWindow *d) { | |||
214 | } | 201 | } |
215 | } | 202 | } |
216 | 203 | ||
204 | static void setupUserInterface_Window(iWindow *d) { | ||
205 | #if defined (iPlatformAppleDesktop) | ||
206 | insertMacMenus_(); | ||
207 | #endif | ||
208 | /* One root is created by default. */ | ||
209 | d->roots[0] = new_Root(); | ||
210 | setCurrent_Root(d->roots[0]); | ||
211 | createUserInterface_Root(d->roots[0]); | ||
212 | setCurrent_Root(NULL); | ||
213 | /* One of the roots always has keyboard input focus. */ | ||
214 | d->keyRoot = d->roots[0]; | ||
215 | } | ||
216 | |||
217 | static void updateSize_Window_(iWindow *d, iBool notifyAlways) { | 217 | static void updateSize_Window_(iWindow *d, iBool notifyAlways) { |
218 | iInt2 *size = &d->size; | 218 | iInt2 *size = &d->size; |
219 | const iInt2 oldSize = *size; | 219 | const iInt2 oldSize = *size; |
@@ -515,15 +515,15 @@ void init_Window(iWindow *d, iRect rect) { | |||
515 | 515 | ||
516 | void deinit_Window(iWindow *d) { | 516 | void deinit_Window(iWindow *d) { |
517 | iRecycle(); | 517 | iRecycle(); |
518 | if (theWindow_ == d) { | ||
519 | theWindow_ = NULL; | ||
520 | } | ||
521 | iForIndices(i, d->roots) { | 518 | iForIndices(i, d->roots) { |
522 | if (d->roots[i]) { | 519 | if (d->roots[i]) { |
523 | setCurrent_Root(d->roots[i]); | 520 | setCurrent_Root(d->roots[i]); |
524 | deinit_Root(d->roots[i]); | 521 | deinit_Root(d->roots[i]); |
525 | } | 522 | } |
526 | } | 523 | } |
524 | if (theWindow_ == d) { | ||
525 | theWindow_ = NULL; | ||
526 | } | ||
527 | setCurrent_Root(NULL); | 527 | setCurrent_Root(NULL); |
528 | delete_String(d->pendingSplitUrl); | 528 | delete_String(d->pendingSplitUrl); |
529 | deinit_Text(); | 529 | deinit_Text(); |
@@ -761,7 +761,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
761 | #if defined (iPlatformMobile) | 761 | #if defined (iPlatformMobile) |
762 | case SDL_WINDOWEVENT_RESIZED: | 762 | case SDL_WINDOWEVENT_RESIZED: |
763 | /* On mobile, this occurs when the display is rotated. */ | 763 | /* On mobile, this occurs when the display is rotated. */ |
764 | invalidate_Window_(d); | 764 | invalidate_Window(d); |
765 | postRefresh_App(); | 765 | postRefresh_App(); |
766 | return iTrue; | 766 | return iTrue; |
767 | #endif | 767 | #endif |
@@ -772,7 +772,7 @@ static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) { | |||
772 | d->isExposed = iTrue; | 772 | d->isExposed = iTrue; |
773 | #if defined (iPlatformMobile) | 773 | #if defined (iPlatformMobile) |
774 | /* Returned to foreground, may have lost buffered content. */ | 774 | /* Returned to foreground, may have lost buffered content. */ |
775 | invalidate_Window_(d); | 775 | invalidate_Window(d); |
776 | postCommand_App("window.unfreeze"); | 776 | postCommand_App("window.unfreeze"); |
777 | #endif | 777 | #endif |
778 | return iFalse; | 778 | return iFalse; |
@@ -995,7 +995,7 @@ void draw_Window(iWindow *d) { | |||
995 | /* Check if root needs resizing. */ { | 995 | /* Check if root needs resizing. */ { |
996 | iInt2 renderSize; | 996 | iInt2 renderSize; |
997 | SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); | 997 | SDL_GetRendererOutputSize(d->render, &renderSize.x, &renderSize.y); |
998 | if (!isEqual_I2(renderSize, d->root->rect.size)) { | 998 | if (!isEqual_I2(renderSize, d->size)) { |
999 | updateSize_Window_(d, iTrue); | 999 | updateSize_Window_(d, iTrue); |
1000 | processEvents_App(postedEventsOnly_AppEventMode); | 1000 | processEvents_App(postedEventsOnly_AppEventMode); |
1001 | } | 1001 | } |
@@ -1007,8 +1007,13 @@ void draw_Window(iWindow *d) { | |||
1007 | init_Paint(&p); | 1007 | init_Paint(&p); |
1008 | /* Clear the window. The clear color is visible as a border around the window | 1008 | /* Clear the window. The clear color is visible as a border around the window |
1009 | when the custom frame is being used. */ { | 1009 | when the custom frame is being used. */ { |
1010 | setCurrent_Root(d->roots[0]); | ||
1010 | #if defined (iPlatformAppleMobile) | 1011 | #if defined (iPlatformAppleMobile) |
1011 | const iColor back = get_Color(tmBackground_ColorId); | 1012 | iColor back = get_Color(uiBackground_ColorId); |
1013 | if (deviceType_App() == phone_AppDeviceType) { | ||
1014 | /* Page background extends to safe area, so fill it completely. */ | ||
1015 | back = get_Color(tmBackground_ColorId); | ||
1016 | } | ||
1012 | #else | 1017 | #else |
1013 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && | 1018 | const iColor back = get_Color(gotFocus && d->place.snap != maximized_WindowSnap && |
1014 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP | 1019 | ~winFlags & SDL_WINDOW_FULLSCREEN_DESKTOP |