diff options
Diffstat (limited to 'src/ui')
-rw-r--r-- | src/ui/certimportwidget.c | 11 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 429 | ||||
-rw-r--r-- | src/ui/documentwidget.h | 1 | ||||
-rw-r--r-- | src/ui/inputwidget.c | 4 | ||||
-rw-r--r-- | src/ui/inputwidget.h | 4 | ||||
-rw-r--r-- | src/ui/keys.c | 15 | ||||
-rw-r--r-- | src/ui/keys.h | 2 | ||||
-rw-r--r-- | src/ui/listwidget.c | 16 | ||||
-rw-r--r-- | src/ui/listwidget.h | 1 | ||||
-rw-r--r-- | src/ui/lookupwidget.c | 15 | ||||
-rw-r--r-- | src/ui/mediaui.c | 12 | ||||
-rw-r--r-- | src/ui/mobile.c | 53 | ||||
-rw-r--r-- | src/ui/root.c | 27 | ||||
-rw-r--r-- | src/ui/scrollwidget.c | 3 | ||||
-rw-r--r-- | src/ui/sidebarwidget.c | 200 | ||||
-rw-r--r-- | src/ui/text.c | 166 | ||||
-rw-r--r-- | src/ui/text.h | 35 | ||||
-rw-r--r-- | src/ui/translation.c | 4 | ||||
-rw-r--r-- | src/ui/util.c | 67 | ||||
-rw-r--r-- | src/ui/widget.c | 20 | ||||
-rw-r--r-- | src/ui/widget.h | 1 | ||||
-rw-r--r-- | src/ui/window.c | 17 |
22 files changed, 830 insertions, 273 deletions
diff --git a/src/ui/certimportwidget.c b/src/ui/certimportwidget.c index fdc189db..6e818137 100644 --- a/src/ui/certimportwidget.c +++ b/src/ui/certimportwidget.c | |||
@@ -114,6 +114,7 @@ void init_CertImportWidget(iCertImportWidget *d) { | |||
114 | setFlags_Widget(w, | 114 | setFlags_Widget(w, |
115 | mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag | | 115 | mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag | |
116 | arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag | | 116 | arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag | |
117 | parentCannotResize_WidgetFlag | | ||
117 | overflowScrollable_WidgetFlag, | 118 | overflowScrollable_WidgetFlag, |
118 | iTrue); | 119 | iTrue); |
119 | } | 120 | } |
@@ -214,11 +215,20 @@ static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Even | |||
214 | return iTrue; | 215 | return iTrue; |
215 | } | 216 | } |
216 | } | 217 | } |
218 | if (isCommand_UserEvent(ev, "input.paste")) { | ||
219 | if (!tryImportFromClipboard_CertImportWidget_(d)) { | ||
220 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.certimport.pasted}", | ||
221 | "${dlg.certimport.notfound}"); | ||
222 | } | ||
223 | postRefresh_App(); | ||
224 | return iTrue; | ||
225 | } | ||
217 | if (isCommand_UserEvent(ev, "certimport.paste")) { | 226 | if (isCommand_UserEvent(ev, "certimport.paste")) { |
218 | tryImportFromClipboard_CertImportWidget_(d); | 227 | tryImportFromClipboard_CertImportWidget_(d); |
219 | return iTrue; | 228 | return iTrue; |
220 | } | 229 | } |
221 | if (isCommand_Widget(w, ev, "cancel")) { | 230 | if (isCommand_Widget(w, ev, "cancel")) { |
231 | setupSheetTransition_Mobile(w, iFalse); | ||
222 | destroy_Widget(w); | 232 | destroy_Widget(w); |
223 | return iTrue; | 233 | return iTrue; |
224 | } | 234 | } |
@@ -226,6 +236,7 @@ static iBool processEvent_CertImportWidget_(iCertImportWidget *d, const SDL_Even | |||
226 | if (d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert)) { | 236 | if (d->cert && !isEmpty_TlsCertificate(d->cert) && hasPrivateKey_TlsCertificate(d->cert)) { |
227 | importIdentity_GmCerts(certs_App(), d->cert, text_InputWidget(d->notes)); | 237 | importIdentity_GmCerts(certs_App(), d->cert, text_InputWidget(d->notes)); |
228 | d->cert = NULL; /* taken */ | 238 | d->cert = NULL; /* taken */ |
239 | setupSheetTransition_Mobile(w, iFalse); | ||
229 | destroy_Widget(w); | 240 | destroy_Widget(w); |
230 | postCommand_App("idents.changed"); | 241 | postCommand_App("idents.changed"); |
231 | } | 242 | } |
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c index 94337f8d..29e264e8 100644 --- a/src/ui/documentwidget.c +++ b/src/ui/documentwidget.c | |||
@@ -222,6 +222,7 @@ enum iDocumentWidgetFlag { | |||
222 | movingSelectMarkStart_DocumentWidgetFlag = iBit(10), | 222 | movingSelectMarkStart_DocumentWidgetFlag = iBit(10), |
223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), | 223 | movingSelectMarkEnd_DocumentWidgetFlag = iBit(11), |
224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ | 224 | otherRootByDefault_DocumentWidgetFlag = iBit(12), /* links open to other root by default */ |
225 | urlChanged_DocumentWidgetFlag = iBit(13), | ||
225 | }; | 226 | }; |
226 | 227 | ||
227 | enum iDocumentLinkOrdinalMode { | 228 | enum iDocumentLinkOrdinalMode { |
@@ -231,15 +232,41 @@ enum iDocumentLinkOrdinalMode { | |||
231 | 232 | ||
232 | struct Impl_DocumentWidget { | 233 | struct Impl_DocumentWidget { |
233 | iWidget widget; | 234 | iWidget widget; |
234 | enum iRequestState state; | ||
235 | iPersistentDocumentState mod; | ||
236 | int flags; | 235 | int flags; |
236 | |||
237 | /* User interface: */ | ||
237 | enum iDocumentLinkOrdinalMode ordinalMode; | 238 | enum iDocumentLinkOrdinalMode ordinalMode; |
238 | size_t ordinalBase; | 239 | size_t ordinalBase; |
239 | iString * titleUser; | 240 | iRangecc selectMark; |
241 | iRangecc initialSelectMark; /* for word/line selection */ | ||
242 | iRangecc foundMark; | ||
243 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | ||
244 | float grabbedStartVolume; | ||
245 | int mediaTimer; | ||
246 | const iGmRun * hoverPre; /* for clicking */ | ||
247 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
248 | const iGmRun * hoverLink; | ||
249 | const iGmRun * contextLink; | ||
250 | iClick click; | ||
251 | iInt2 contextPos; /* coordinates of latest right click */ | ||
252 | int pinchZoomInitial; | ||
253 | int pinchZoomPosted; | ||
254 | iString pendingGotoHeading; | ||
255 | |||
256 | /* Network request: */ | ||
257 | enum iRequestState state; | ||
240 | iGmRequest * request; | 258 | iGmRequest * request; |
241 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ | 259 | iAtomicInt isRequestUpdated; /* request has new content, need to parse it */ |
242 | iObjectList * media; | 260 | int certFlags; |
261 | iBlock * certFingerprint; | ||
262 | iDate certExpiry; | ||
263 | iString * certSubject; | ||
264 | int redirectCount; | ||
265 | iObjectList * media; /* inline media requests */ | ||
266 | |||
267 | /* Document: */ | ||
268 | iPersistentDocumentState mod; | ||
269 | iString * titleUser; | ||
243 | enum iGmStatusCode sourceStatus; | 270 | enum iGmStatusCode sourceStatus; |
244 | iString sourceHeader; | 271 | iString sourceHeader; |
245 | iString sourceMime; | 272 | iString sourceMime; |
@@ -247,53 +274,36 @@ struct Impl_DocumentWidget { | |||
247 | iTime sourceTime; | 274 | iTime sourceTime; |
248 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ | 275 | iGempub * sourceGempub; /* NULL unless the page is Gempub content */ |
249 | iGmDocument * doc; | 276 | iGmDocument * doc; |
250 | int certFlags; | 277 | |
251 | iBlock * certFingerprint; | 278 | /* Rendering: */ |
252 | iDate certExpiry; | ||
253 | iString * certSubject; | ||
254 | int redirectCount; | ||
255 | iRangecc selectMark; | ||
256 | iRangecc initialSelectMark; /* for word/line selection */ | ||
257 | iRangecc foundMark; | ||
258 | int pageMargin; | 279 | int pageMargin; |
280 | float initNormScrollY; | ||
281 | iSmoothScroll scrollY; | ||
282 | iAnim sideOpacity; | ||
283 | iAnim altTextOpacity; | ||
284 | iGmRunRange visibleRuns; | ||
259 | iPtrArray visibleLinks; | 285 | iPtrArray visibleLinks; |
260 | iPtrArray visiblePre; | 286 | iPtrArray visiblePre; |
287 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | ||
261 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ | 288 | iPtrArray visibleWideRuns; /* scrollable blocks; TODO: merge into `visiblePre` */ |
262 | iArray wideRunOffsets; | 289 | iArray wideRunOffsets; |
263 | iAnim animWideRunOffset; | 290 | iAnim animWideRunOffset; |
264 | uint16_t animWideRunId; | 291 | uint16_t animWideRunId; |
265 | iGmRunRange animWideRunRange; | 292 | iGmRunRange animWideRunRange; |
266 | iPtrArray visibleMedia; /* currently playing audio / ongoing downloads */ | 293 | iDrawBufs * drawBufs; /* dynamic state for drawing */ |
267 | const iGmRun * grabbedPlayer; /* currently adjusting volume in a player */ | 294 | iVisBuf * visBuf; |
268 | float grabbedStartVolume; | 295 | iVisBufMeta * visBufMeta; |
269 | int mediaTimer; | ||
270 | const iGmRun * hoverPre; /* for clicking */ | ||
271 | const iGmRun * hoverAltPre; /* for drawing alt text */ | ||
272 | const iGmRun * hoverLink; | ||
273 | const iGmRun * contextLink; | ||
274 | iGmRunRange visibleRuns; | ||
275 | iGmRunRange renderRuns; | 296 | iGmRunRange renderRuns; |
276 | iClick click; | 297 | iPtrSet * invalidRuns; |
277 | iInt2 contextPos; /* coordinates of latest right click */ | 298 | |
278 | iString pendingGotoHeading; | 299 | /* Widget structure: */ |
279 | float initNormScrollY; | ||
280 | // iAnim scrollY; | ||
281 | // int overscroll; | ||
282 | iSmoothScroll scrollY; | ||
283 | iAnim sideOpacity; | ||
284 | iAnim altTextOpacity; | ||
285 | iScrollWidget *scroll; | 300 | iScrollWidget *scroll; |
301 | iWidget * footerButtons; | ||
286 | iWidget * menu; | 302 | iWidget * menu; |
287 | iWidget * playerMenu; | 303 | iWidget * playerMenu; |
288 | iWidget * copyMenu; | 304 | iWidget * copyMenu; |
289 | iVisBuf * visBuf; | ||
290 | iVisBufMeta * visBufMeta; | ||
291 | iPtrSet * invalidRuns; | ||
292 | iDrawBufs * drawBufs; /* dynamic state for drawing */ | ||
293 | iTranslation * translation; | 305 | iTranslation * translation; |
294 | iWidget * phoneToolbar; | 306 | iWidget * phoneToolbar; |
295 | int pinchZoomInitial; | ||
296 | int pinchZoomPosted; | ||
297 | }; | 307 | }; |
298 | 308 | ||
299 | iDefineObjectConstruction(DocumentWidget) | 309 | iDefineObjectConstruction(DocumentWidget) |
@@ -308,6 +318,7 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
308 | init_PersistentDocumentState(&d->mod); | 318 | init_PersistentDocumentState(&d->mod); |
309 | d->flags = 0; | 319 | d->flags = 0; |
310 | d->phoneToolbar = NULL; | 320 | d->phoneToolbar = NULL; |
321 | d->footerButtons = NULL; | ||
311 | iZap(d->certExpiry); | 322 | iZap(d->certExpiry); |
312 | d->certFingerprint = new_Block(0); | 323 | d->certFingerprint = new_Block(0); |
313 | d->certFlags = 0; | 324 | d->certFlags = 0; |
@@ -321,7 +332,6 @@ void init_DocumentWidget(iDocumentWidget *d) { | |||
321 | d->redirectCount = 0; | 332 | d->redirectCount = 0; |
322 | d->ordinalBase = 0; | 333 | d->ordinalBase = 0; |
323 | d->initNormScrollY = 0; | 334 | d->initNormScrollY = 0; |
324 | //init_Anim(&d->scrollY, 0); | ||
325 | init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_); | 335 | init_SmoothScroll(&d->scrollY, w, scrollBegan_DocumentWidget_); |
326 | d->animWideRunId = 0; | 336 | d->animWideRunId = 0; |
327 | init_Anim(&d->animWideRunOffset, 0); | 337 | init_Anim(&d->animWideRunOffset, 0); |
@@ -573,7 +583,8 @@ static float normScrollPos_DocumentWidget_(const iDocumentWidget *d) { | |||
573 | static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { | 583 | static int scrollMax_DocumentWidget_(const iDocumentWidget *d) { |
574 | const iWidget *w = constAs_Widget(d); | 584 | const iWidget *w = constAs_Widget(d); |
575 | int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(w)) + | 585 | int sm = size_GmDocument(d->doc).y - height_Rect(bounds_Widget(w)) + |
576 | (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI; | 586 | (hasSiteBanner_GmDocument(d->doc) ? 1 : 2) * d->pageMargin * gap_UI + |
587 | height_Widget(d->footerButtons); | ||
577 | if (d->phoneToolbar) { | 588 | if (d->phoneToolbar) { |
578 | sm += size_Root(w->root).y - | 589 | sm += size_Root(w->root).y - |
579 | top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); | 590 | top_Rect(boundsWithoutVisualOffset_Widget(d->phoneToolbar)); |
@@ -809,6 +820,13 @@ static iRangecc currentHeading_DocumentWidget_(const iDocumentWidget *d) { | |||
809 | return heading; | 820 | return heading; |
810 | } | 821 | } |
811 | 822 | ||
823 | static int updateScrollMax_DocumentWidget_(iDocumentWidget *d) { | ||
824 | arrange_Widget(d->footerButtons); /* scrollMax depends on footer height */ | ||
825 | const int scrollMax = scrollMax_DocumentWidget_(d); | ||
826 | setMax_SmoothScroll(&d->scrollY, scrollMax); | ||
827 | return scrollMax; | ||
828 | } | ||
829 | |||
812 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | 830 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { |
813 | iChangeFlags(d->flags, | 831 | iChangeFlags(d->flags, |
814 | centerVertically_DocumentWidgetFlag, | 832 | centerVertically_DocumentWidgetFlag, |
@@ -816,8 +834,26 @@ static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | |||
816 | !isSuccess_GmStatusCode(d->sourceStatus)); | 834 | !isSuccess_GmStatusCode(d->sourceStatus)); |
817 | const iRangei visRange = visibleRange_DocumentWidget_(d); | 835 | const iRangei visRange = visibleRange_DocumentWidget_(d); |
818 | const iRect bounds = bounds_Widget(as_Widget(d)); | 836 | const iRect bounds = bounds_Widget(as_Widget(d)); |
819 | const int scrollMax = scrollMax_DocumentWidget_(d); | 837 | const int scrollMax = updateScrollMax_DocumentWidget_(d); |
820 | setMax_SmoothScroll(&d->scrollY, scrollMax); | 838 | /* Reposition the footer buttons as appropriate. */ |
839 | /* TODO: You can just position `footerButtons` here completely without having to get | ||
840 | `Widget` involved with the offset in any way. */ | ||
841 | if (d->footerButtons) { | ||
842 | const iRect bounds = bounds_Widget(as_Widget(d)); | ||
843 | const iRect docBounds = documentBounds_DocumentWidget_(d); | ||
844 | const int hPad = (width_Rect(bounds) - iMin(120 * gap_UI, width_Rect(docBounds))) / 2; | ||
845 | const int vPad = 3 * gap_UI; | ||
846 | setPadding_Widget(d->footerButtons, hPad, vPad, hPad, vPad); | ||
847 | d->footerButtons->animOffsetRef = (scrollMax > 0 ? &d->scrollY.pos : NULL); | ||
848 | if (scrollMax <= 0) { | ||
849 | d->footerButtons->animOffsetRef = NULL; | ||
850 | d->footerButtons->rect.pos.y = height_Rect(bounds) - height_Widget(d->footerButtons); | ||
851 | } | ||
852 | else { | ||
853 | d->footerButtons->animOffsetRef = &d->scrollY.pos; | ||
854 | d->footerButtons->rect.pos.y = size_GmDocument(d->doc).y + 2 * gap_UI * d->pageMargin; | ||
855 | } | ||
856 | } | ||
821 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); | 857 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, scrollMax }); |
822 | const int docSize = size_GmDocument(d->doc).y; | 858 | const int docSize = size_GmDocument(d->doc).y; |
823 | setThumb_ScrollWidget(d->scroll, | 859 | setThumb_ScrollWidget(d->scroll, |
@@ -1036,6 +1072,35 @@ static enum iGmDocumentBanner bannerType_DocumentWidget_(const iDocumentWidget * | |||
1036 | return siteDomain_GmDocumentBanner; | 1072 | return siteDomain_GmDocumentBanner; |
1037 | } | 1073 | } |
1038 | 1074 | ||
1075 | static void makeFooterButtons_DocumentWidget_(iDocumentWidget *d, const iMenuItem *items, size_t count) { | ||
1076 | iWidget *w = as_Widget(d); | ||
1077 | destroy_Widget(d->footerButtons); | ||
1078 | d->footerButtons = NULL; | ||
1079 | if (count == 0) { | ||
1080 | return; | ||
1081 | } | ||
1082 | d->footerButtons = new_Widget(); | ||
1083 | setFlags_Widget(d->footerButtons, | ||
1084 | unhittable_WidgetFlag | arrangeVertical_WidgetFlag | | ||
1085 | resizeWidthOfChildren_WidgetFlag | arrangeHeight_WidgetFlag | | ||
1086 | fixedPosition_WidgetFlag | resizeToParentWidth_WidgetFlag, | ||
1087 | iTrue); | ||
1088 | //setBackgroundColor_Widget(d->footerButtons, tmBackground_ColorId); | ||
1089 | for (size_t i = 0; i < count; ++i) { | ||
1090 | iLabelWidget *button = addChildFlags_Widget( | ||
1091 | d->footerButtons, | ||
1092 | iClob(newKeyMods_LabelWidget( | ||
1093 | items[i].label, items[i].key, items[i].kmods, items[i].command)), | ||
1094 | alignLeft_WidgetFlag | drawKey_WidgetFlag); | ||
1095 | checkIcon_LabelWidget(button); | ||
1096 | setFont_LabelWidget(button, uiContent_FontId); | ||
1097 | } | ||
1098 | addChild_Widget(as_Widget(d), iClob(d->footerButtons)); | ||
1099 | arrange_Widget(d->footerButtons); | ||
1100 | arrange_Widget(w); | ||
1101 | updateVisible_DocumentWidget_(d); /* final placement for the buttons */ | ||
1102 | } | ||
1103 | |||
1039 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, | 1104 | static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode code, |
1040 | const iString *meta) { | 1105 | const iString *meta) { |
1041 | iString *src = collectNewCStr_String("# "); | 1106 | iString *src = collectNewCStr_String("# "); |
@@ -1061,10 +1126,17 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1061 | iString *key = collectNew_String(); | 1126 | iString *key = collectNew_String(); |
1062 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); | 1127 | toString_Sym(SDLK_s, KMOD_PRIMARY, key); |
1063 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); | 1128 | appendFormat_String(src, "\n```\n%s\n```\n", cstr_String(meta)); |
1064 | appendFormat_String(src, | 1129 | // appendFormat_String(src, |
1065 | cstr_Lang("error.unsupported.suggestsave"), | 1130 | // cstr_Lang("error.unsupported.suggestsave"), |
1066 | cstr_String(key), | 1131 | // cstr_String(key), |
1067 | saveToDownloads_Label); | 1132 | // saveToDownloads_Label); |
1133 | makeFooterButtons_DocumentWidget_( | ||
1134 | d, | ||
1135 | (iMenuItem[]){ { translateCStr_Lang(download_Icon " " saveToDownloads_Label), | ||
1136 | 0, | ||
1137 | 0, | ||
1138 | "document.save" } }, | ||
1139 | 1); | ||
1068 | break; | 1140 | break; |
1069 | } | 1141 | } |
1070 | case slowDown_GmStatusCode: | 1142 | case slowDown_GmStatusCode: |
@@ -1072,9 +1144,19 @@ static void showErrorPage_DocumentWidget_(iDocumentWidget *d, enum iGmStatusCode | |||
1072 | cstr_String(meta)); | 1144 | cstr_String(meta)); |
1073 | break; | 1145 | break; |
1074 | default: | 1146 | default: |
1147 | if (!isEmpty_String(meta)) { | ||
1148 | appendFormat_String(src, "\n\n${error.server.msg}\n> %s", cstr_String(meta)); | ||
1149 | } | ||
1075 | break; | 1150 | break; |
1076 | } | 1151 | } |
1077 | } | 1152 | } |
1153 | if (category_GmStatusCode(code) == categoryClientCertificate_GmStatus) { | ||
1154 | makeFooterButtons_DocumentWidget_( | ||
1155 | d, | ||
1156 | (iMenuItem[]){ { leftHalf_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | ||
1157 | { person_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" } }, | ||
1158 | 2); | ||
1159 | } | ||
1078 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); | 1160 | setBanner_GmDocument(d->doc, useBanner ? bannerType_DocumentWidget_(d) : none_GmDocumentBanner); |
1079 | setFormat_GmDocument(d->doc, gemini_SourceFormat); | 1161 | setFormat_GmDocument(d->doc, gemini_SourceFormat); |
1080 | translate_Lang(src); | 1162 | translate_Lang(src); |
@@ -1159,11 +1241,102 @@ static void postProcessRequestContent_DocumentWidget_(iDocumentWidget *d, iBool | |||
1159 | } | 1241 | } |
1160 | } | 1242 | } |
1161 | if (d->sourceGempub) { | 1243 | if (d->sourceGempub) { |
1162 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub)) && | 1244 | if (equal_String(d->mod.url, coverPageUrl_Gempub(d->sourceGempub))) { |
1163 | preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | 1245 | if (!isRemote_Gempub(d->sourceGempub)) { |
1246 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
1247 | pushBack_Array( | ||
1248 | items, | ||
1249 | &(iMenuItem){ book_Icon " ${gempub.cover.view}", | ||
1250 | 0, | ||
1251 | 0, | ||
1252 | format_CStr("!open url:%s", | ||
1253 | cstr_String(indexPageUrl_Gempub(d->sourceGempub))) }); | ||
1254 | if (navSize_Gempub(d->sourceGempub) > 0) { | ||
1255 | pushBack_Array( | ||
1256 | items, | ||
1257 | &(iMenuItem){ | ||
1258 | format_CStr(forwardArrow_Icon " %s", | ||
1259 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, 0))), | ||
1260 | SDLK_RIGHT, | ||
1261 | 0, | ||
1262 | format_CStr("!open url:%s", | ||
1263 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, 0))) }); | ||
1264 | } | ||
1265 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1266 | } | ||
1267 | else { | ||
1268 | makeFooterButtons_DocumentWidget_( | ||
1269 | d, | ||
1270 | (iMenuItem[]){ { book_Icon " ${menu.save.downloads.open}", | ||
1271 | SDLK_s, | ||
1272 | KMOD_PRIMARY | KMOD_SHIFT, | ||
1273 | "document.save open:1" }, | ||
1274 | { download_Icon " " saveToDownloads_Label, | ||
1275 | SDLK_s, | ||
1276 | KMOD_PRIMARY, | ||
1277 | "document.save" } }, | ||
1278 | 2); | ||
1279 | } | ||
1280 | if (preloadCoverImage_Gempub(d->sourceGempub, d->doc)) { | ||
1164 | redoLayout_GmDocument(d->doc); | 1281 | redoLayout_GmDocument(d->doc); |
1165 | updateVisible_DocumentWidget_(d); | 1282 | updateVisible_DocumentWidget_(d); |
1166 | invalidate_DocumentWidget_(d); | 1283 | invalidate_DocumentWidget_(d); |
1284 | } | ||
1285 | } | ||
1286 | else if (equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1287 | makeFooterButtons_DocumentWidget_( | ||
1288 | d, | ||
1289 | (iMenuItem[]){ { format_CStr(book_Icon " %s", | ||
1290 | cstr_String(property_Gempub(d->sourceGempub, | ||
1291 | title_GempubProperty))), | ||
1292 | SDLK_LEFT, | ||
1293 | 0, | ||
1294 | format_CStr("!open url:%s", | ||
1295 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) } }, | ||
1296 | 1); | ||
1297 | } | ||
1298 | else { | ||
1299 | /* Navigation buttons. */ | ||
1300 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
1301 | const size_t navIndex = navIndex_Gempub(d->sourceGempub, d->mod.url); | ||
1302 | if (navIndex != iInvalidPos) { | ||
1303 | if (navIndex < navSize_Gempub(d->sourceGempub) - 1) { | ||
1304 | pushBack_Array( | ||
1305 | items, | ||
1306 | &(iMenuItem){ | ||
1307 | format_CStr(forwardArrow_Icon " %s", | ||
1308 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex + 1))), | ||
1309 | SDLK_RIGHT, | ||
1310 | 0, | ||
1311 | format_CStr("!open url:%s", | ||
1312 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex + 1))) }); | ||
1313 | } | ||
1314 | if (navIndex > 0) { | ||
1315 | pushBack_Array( | ||
1316 | items, | ||
1317 | &(iMenuItem){ | ||
1318 | format_CStr(backArrow_Icon " %s", | ||
1319 | cstr_String(navLinkLabel_Gempub(d->sourceGempub, navIndex - 1))), | ||
1320 | SDLK_LEFT, | ||
1321 | 0, | ||
1322 | format_CStr("!open url:%s", | ||
1323 | cstr_String(navLinkUrl_Gempub(d->sourceGempub, navIndex - 1))) }); | ||
1324 | } | ||
1325 | else if (!equalCase_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | ||
1326 | pushBack_Array( | ||
1327 | items, | ||
1328 | &(iMenuItem){ | ||
1329 | format_CStr(book_Icon " %s", | ||
1330 | cstr_String(property_Gempub(d->sourceGempub, title_GempubProperty))), | ||
1331 | SDLK_LEFT, | ||
1332 | 0, | ||
1333 | format_CStr("!open url:%s", | ||
1334 | cstr_String(coverPageUrl_Gempub(d->sourceGempub))) }); | ||
1335 | } | ||
1336 | } | ||
1337 | if (!isEmpty_Array(items)) { | ||
1338 | makeFooterButtons_DocumentWidget_(d, constData_Array(items), size_Array(items)); | ||
1339 | } | ||
1167 | } | 1340 | } |
1168 | if (!isCached && prefs_App()->pinSplit && | 1341 | if (!isCached && prefs_App()->pinSplit && |
1169 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { | 1342 | equal_String(d->mod.url, indexPageUrl_Gempub(d->sourceGempub))) { |
@@ -1227,7 +1400,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, | |||
1227 | setRange_String(&d->sourceMime, param); | 1400 | setRange_String(&d->sourceMime, param); |
1228 | } | 1401 | } |
1229 | else if (startsWith_Rangecc(param, "text/") || | 1402 | else if (startsWith_Rangecc(param, "text/") || |
1230 | equal_Rangecc(param, "application/json")) { | 1403 | equal_Rangecc(param, "application/json") || |
1404 | equal_Rangecc(param, "application/x-pem-file") || | ||
1405 | equal_Rangecc(param, "application/pem-certificate-chain")) { | ||
1231 | docFormat = plainText_SourceFormat; | 1406 | docFormat = plainText_SourceFormat; |
1232 | setRange_String(&d->sourceMime, param); | 1407 | setRange_String(&d->sourceMime, param); |
1233 | } | 1408 | } |
@@ -1400,44 +1575,58 @@ static void cacheDocumentGlyphs_DocumentWidget_(const iDocumentWidget *d) { | |||
1400 | } | 1575 | } |
1401 | } | 1576 | } |
1402 | 1577 | ||
1578 | static void updateFromCachedResponse_DocumentWidget_(iDocumentWidget *d, float normScrollY, | ||
1579 | const iGmResponse *resp, iGmDocument *cachedDoc) { | ||
1580 | setLinkNumberMode_DocumentWidget_(d, iFalse); | ||
1581 | clear_ObjectList(d->media); | ||
1582 | delete_Gempub(d->sourceGempub); | ||
1583 | d->sourceGempub = NULL; | ||
1584 | iRelease(d->doc); | ||
1585 | destroy_Widget(d->footerButtons); | ||
1586 | d->footerButtons = NULL; | ||
1587 | d->doc = new_GmDocument(); | ||
1588 | resetWideRuns_DocumentWidget_(d); | ||
1589 | d->state = fetching_RequestState; | ||
1590 | /* Do the fetch. */ { | ||
1591 | d->initNormScrollY = normScrollY; | ||
1592 | /* Use the cached response data. */ | ||
1593 | updateTrust_DocumentWidget_(d, resp); | ||
1594 | d->sourceTime = resp->when; | ||
1595 | d->sourceStatus = success_GmStatusCode; | ||
1596 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | ||
1597 | set_Block(&d->sourceContent, &resp->body); | ||
1598 | updateDocument_DocumentWidget_(d, resp, cachedDoc, iTrue); | ||
1599 | setCachedDocument_History(d->mod.history, d->doc); | ||
1600 | } | ||
1601 | d->state = ready_RequestState; | ||
1602 | postProcessRequestContent_DocumentWidget_(d, iTrue); | ||
1603 | init_Anim(&d->altTextOpacity, 0); | ||
1604 | reset_SmoothScroll(&d->scrollY); | ||
1605 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | ||
1606 | updateSideOpacity_DocumentWidget_(d, iFalse); | ||
1607 | updateVisible_DocumentWidget_(d); | ||
1608 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | ||
1609 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1610 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
1611 | d->flags &= ~urlChanged_DocumentWidgetFlag; | ||
1612 | postCommandf_Root( | ||
1613 | as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1614 | } | ||
1615 | |||
1403 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { | 1616 | static iBool updateFromHistory_DocumentWidget_(iDocumentWidget *d) { |
1404 | const iRecentUrl *recent = findUrl_History(d->mod.history, d->mod.url); | 1617 | const iRecentUrl *recent = findUrl_History(d->mod.history, withSpacesEncoded_String(d->mod.url)); |
1405 | if (recent && recent->cachedResponse) { | 1618 | if (recent && recent->cachedResponse) { |
1406 | const iGmResponse *resp = recent->cachedResponse; | 1619 | updateFromCachedResponse_DocumentWidget_( |
1407 | clear_ObjectList(d->media); | 1620 | d, recent->normScrollY, recent->cachedResponse, recent->cachedDoc); |
1408 | delete_Gempub(d->sourceGempub); | ||
1409 | d->sourceGempub = NULL; | ||
1410 | iRelease(d->doc); | ||
1411 | d->doc = new_GmDocument(); | ||
1412 | resetWideRuns_DocumentWidget_(d); | ||
1413 | d->state = fetching_RequestState; | ||
1414 | /* Do the fetch. */ { | ||
1415 | d->initNormScrollY = recent->normScrollY; | ||
1416 | /* Use the cached response data. */ | ||
1417 | updateTrust_DocumentWidget_(d, resp); | ||
1418 | d->sourceTime = resp->when; | ||
1419 | d->sourceStatus = success_GmStatusCode; | ||
1420 | format_String(&d->sourceHeader, cstr_Lang("pageinfo.header.cached")); | ||
1421 | set_Block(&d->sourceContent, &resp->body); | ||
1422 | updateDocument_DocumentWidget_(d, resp, recent->cachedDoc, iTrue); | ||
1423 | setCachedDocument_History(d->mod.history, d->doc); | ||
1424 | } | ||
1425 | d->state = ready_RequestState; | ||
1426 | postProcessRequestContent_DocumentWidget_(d, iTrue); | ||
1427 | init_Anim(&d->altTextOpacity, 0); | ||
1428 | reset_SmoothScroll(&d->scrollY); | ||
1429 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); | ||
1430 | updateSideOpacity_DocumentWidget_(d, iFalse); | ||
1431 | updateVisible_DocumentWidget_(d); | ||
1432 | moveSpan_SmoothScroll(&d->scrollY, 0, 0); /* clamp position to new max */ | ||
1433 | cacheDocumentGlyphs_DocumentWidget_(d); | ||
1434 | d->drawBufs->flags |= updateTimestampBuf_DrawBufsFlag | updateSideBuf_DrawBufsFlag; | ||
1435 | postCommandf_Root(as_Widget(d)->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); | ||
1436 | return iTrue; | 1621 | return iTrue; |
1437 | } | 1622 | } |
1438 | else if (!isEmpty_String(d->mod.url)) { | 1623 | else if (!isEmpty_String(d->mod.url)) { |
1439 | fetch_DocumentWidget_(d); | 1624 | fetch_DocumentWidget_(d); |
1440 | } | 1625 | } |
1626 | if (recent) { | ||
1627 | /* Retain scroll position in refetched content as well. */ | ||
1628 | d->initNormScrollY = recent->normScrollY; | ||
1629 | } | ||
1441 | return iFalse; | 1630 | return iFalse; |
1442 | } | 1631 | } |
1443 | 1632 | ||
@@ -1674,11 +1863,16 @@ static void checkResponse_DocumentWidget_(iDocumentWidget *d) { | |||
1674 | break; | 1863 | break; |
1675 | } | 1864 | } |
1676 | case categorySuccess_GmStatusCode: | 1865 | case categorySuccess_GmStatusCode: |
1677 | //reset_SmoothScroll(&d->scrollY); | 1866 | if (d->flags & urlChanged_DocumentWidgetFlag) { |
1867 | /* Keep scroll position when reloading the same page. */ | ||
1868 | reset_SmoothScroll(&d->scrollY); | ||
1869 | } | ||
1678 | iRelease(d->doc); /* new content incoming */ | 1870 | iRelease(d->doc); /* new content incoming */ |
1679 | d->doc = new_GmDocument(); | 1871 | d->doc = new_GmDocument(); |
1680 | delete_Gempub(d->sourceGempub); | 1872 | delete_Gempub(d->sourceGempub); |
1681 | d->sourceGempub = NULL; | 1873 | d->sourceGempub = NULL; |
1874 | destroy_Widget(d->footerButtons); | ||
1875 | d->footerButtons = NULL; | ||
1682 | resetWideRuns_DocumentWidget_(d); | 1876 | resetWideRuns_DocumentWidget_(d); |
1683 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); | 1877 | updateDocument_DocumentWidget_(d, resp, NULL, iTrue); |
1684 | break; | 1878 | break; |
@@ -1902,7 +2096,8 @@ static iBool fetchNextUnfetchedImage_DocumentWidget_(iDocumentWidget *d) { | |||
1902 | return iFalse; | 2096 | return iFalse; |
1903 | } | 2097 | } |
1904 | 2098 | ||
1905 | static void saveToDownloads_(const iString *url, const iString *mime, const iBlock *content) { | 2099 | static const iString *saveToDownloads_(const iString *url, const iString *mime, const iBlock *content, |
2100 | iBool showDialog) { | ||
1906 | const iString *savePath = downloadPathForUrl_App(url, mime); | 2101 | const iString *savePath = downloadPathForUrl_App(url, mime); |
1907 | /* Write the file. */ { | 2102 | /* Write the file. */ { |
1908 | iFile *f = new_File(savePath); | 2103 | iFile *f = new_File(savePath); |
@@ -1914,17 +2109,22 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo | |||
1914 | #if defined (iPlatformAppleMobile) | 2109 | #if defined (iPlatformAppleMobile) |
1915 | exportDownloadedFile_iOS(savePath); | 2110 | exportDownloadedFile_iOS(savePath); |
1916 | #else | 2111 | #else |
2112 | if (showDialog) { | ||
1917 | const iMenuItem items[2] = { | 2113 | const iMenuItem items[2] = { |
1918 | { "${dlg.save.opendownload}", 0, 0, | 2114 | { "${dlg.save.opendownload}", 0, 0, |
1919 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, | 2115 | format_CStr("!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))) }, |
1920 | { "${dlg.message.ok}", 0, 0, "message.ok" }, | 2116 | { "${dlg.message.ok}", 0, 0, "message.ok" }, |
1921 | }; | 2117 | }; |
1922 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", | 2118 | makeMessage_Widget(uiHeading_ColorEscape "${heading.save}", |
1923 | format_CStr("%s\n${dlg.save.size} %.3f %s", cstr_String(path_File(f)), | 2119 | format_CStr("%s\n${dlg.save.size} %.3f %s", |
2120 | cstr_String(path_File(f)), | ||
1924 | isMega ? size / 1.0e6f : (size / 1.0e3f), | 2121 | isMega ? size / 1.0e6f : (size / 1.0e3f), |
1925 | isMega ? "${mb}" : "${kb}"), | 2122 | isMega ? "${mb}" : "${kb}"), |
1926 | items, iElemCount(items)); | 2123 | items, |
2124 | iElemCount(items)); | ||
2125 | } | ||
1927 | #endif | 2126 | #endif |
2127 | return savePath; | ||
1928 | } | 2128 | } |
1929 | else { | 2129 | else { |
1930 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", | 2130 | makeSimpleMessage_Widget(uiTextCaution_ColorEscape "${heading.save.error}", |
@@ -1932,6 +2132,7 @@ static void saveToDownloads_(const iString *url, const iString *mime, const iBlo | |||
1932 | } | 2132 | } |
1933 | iRelease(f); | 2133 | iRelease(f); |
1934 | } | 2134 | } |
2135 | return collectNew_String(); | ||
1935 | } | 2136 | } |
1936 | 2137 | ||
1937 | static void addAllLinks_(void *context, const iGmRun *run) { | 2138 | static void addAllLinks_(void *context, const iGmRun *run) { |
@@ -2061,6 +2262,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2061 | const iBool keepCenter = equal_Command(cmd, "font.changed"); | 2262 | const iBool keepCenter = equal_Command(cmd, "font.changed"); |
2062 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); | 2263 | updateDocumentWidthRetainingScrollPosition_DocumentWidget_(d, keepCenter); |
2063 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; | 2264 | d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; |
2265 | updateVisible_DocumentWidget_(d); | ||
2064 | invalidate_DocumentWidget_(d); | 2266 | invalidate_DocumentWidget_(d); |
2065 | dealloc_VisBuf(d->visBuf); | 2267 | dealloc_VisBuf(d->visBuf); |
2066 | updateWindowTitle_DocumentWidget_(d); | 2268 | updateWindowTitle_DocumentWidget_(d); |
@@ -2214,7 +2416,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2214 | if (!isEmpty_Array(items)) { | 2416 | if (!isEmpty_Array(items)) { |
2215 | pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); | 2417 | pushBack_Array(items, &(iMenuItem){ "---", 0, 0, 0 }); |
2216 | } | 2418 | } |
2217 | pushBack_Array(items, &(iMenuItem){ "${dismiss}", 0, 0, "message.ok" }); | 2419 | pushBack_Array(items, &(iMenuItem){ "${close}", 0, 0, "message.ok" }); |
2218 | iWidget *dlg = makeQuestion_Widget(uiHeading_ColorEscape "${heading.pageinfo}", | 2420 | iWidget *dlg = makeQuestion_Widget(uiHeading_ColorEscape "${heading.pageinfo}", |
2219 | cstr_String(msg), | 2421 | cstr_String(msg), |
2220 | data_Array(items), | 2422 | data_Array(items), |
@@ -2334,6 +2536,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2334 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { | 2536 | if (category_GmStatusCode(status_GmRequest(d->request)) == categorySuccess_GmStatusCode) { |
2335 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ | 2537 | init_Anim(&d->scrollY.pos, d->initNormScrollY * size_GmDocument(d->doc).y); /* TODO: unless user already scrolled! */ |
2336 | } | 2538 | } |
2539 | d->flags &= ~urlChanged_DocumentWidgetFlag; | ||
2337 | d->state = ready_RequestState; | 2540 | d->state = ready_RequestState; |
2338 | postProcessRequestContent_DocumentWidget_(d, iFalse); | 2541 | postProcessRequestContent_DocumentWidget_(d, iFalse); |
2339 | /* The response may be cached. */ | 2542 | /* The response may be cached. */ |
@@ -2409,7 +2612,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2409 | const iMediaRequest *media = findMediaRequest_DocumentWidget_(d, linkId); | 2612 | const iMediaRequest *media = findMediaRequest_DocumentWidget_(d, linkId); |
2410 | if (media) { | 2613 | if (media) { |
2411 | saveToDownloads_(url_GmRequest(media->req), meta_GmRequest(media->req), | 2614 | saveToDownloads_(url_GmRequest(media->req), meta_GmRequest(media->req), |
2412 | body_GmRequest(media->req)); | 2615 | body_GmRequest(media->req), iTrue); |
2413 | } | 2616 | } |
2414 | } | 2617 | } |
2415 | else if (equal_Command(cmd, "document.save") && document_App() == d) { | 2618 | else if (equal_Command(cmd, "document.save") && document_App() == d) { |
@@ -2418,7 +2621,13 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2418 | "${dlg.save.incomplete}"); | 2621 | "${dlg.save.incomplete}"); |
2419 | } | 2622 | } |
2420 | else if (!isEmpty_Block(&d->sourceContent)) { | 2623 | else if (!isEmpty_Block(&d->sourceContent)) { |
2421 | saveToDownloads_(d->mod.url, &d->sourceMime, &d->sourceContent); | 2624 | const iBool doOpen = argLabel_Command(cmd, "open"); |
2625 | const iString *savePath = saveToDownloads_(d->mod.url, &d->sourceMime, | ||
2626 | &d->sourceContent, !doOpen); | ||
2627 | if (!isEmpty_String(savePath) && doOpen) { | ||
2628 | postCommandf_Root( | ||
2629 | w->root, "!open url:%s", cstrCollect_String(makeFileUrl_String(savePath))); | ||
2630 | } | ||
2422 | } | 2631 | } |
2423 | return iTrue; | 2632 | return iTrue; |
2424 | } | 2633 | } |
@@ -2528,6 +2737,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd) | |||
2528 | return iTrue; | 2737 | return iTrue; |
2529 | } | 2738 | } |
2530 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { | 2739 | else if (equal_Command(cmd, "scroll.bottom") && document_App() == d) { |
2740 | updateScrollMax_DocumentWidget_(d); /* scrollY.max might not be fully updated */ | ||
2531 | init_Anim(&d->scrollY.pos, d->scrollY.max); | 2741 | init_Anim(&d->scrollY.pos, d->scrollY.max); |
2532 | invalidate_VisBuf(d->visBuf); | 2742 | invalidate_VisBuf(d->visBuf); |
2533 | clampScroll_DocumentWidget_(d); | 2743 | clampScroll_DocumentWidget_(d); |
@@ -2835,7 +3045,7 @@ static size_t linkOrdinalFromKey_DocumentWidget_(const iDocumentWidget *d, int k | |||
2835 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { | 3045 | static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t ord) { |
2836 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { | 3046 | if (d->ordinalMode == numbersAndAlphabet_DocumentLinkOrdinalMode) { |
2837 | if (ord < 9) { | 3047 | if (ord < 9) { |
2838 | return 0x278a + ord; | 3048 | return '1' + ord; |
2839 | } | 3049 | } |
2840 | #if defined (iPlatformApple) | 3050 | #if defined (iPlatformApple) |
2841 | if (ord < 9 + 22) { | 3051 | if (ord < 9 + 22) { |
@@ -2844,17 +3054,17 @@ static iChar linkOrdinalChar_DocumentWidget_(const iDocumentWidget *d, size_t or | |||
2844 | if (key >= 'm') key++; | 3054 | if (key >= 'm') key++; |
2845 | if (key >= 'q') key++; | 3055 | if (key >= 'q') key++; |
2846 | if (key >= 'w') key++; | 3056 | if (key >= 'w') key++; |
2847 | return 0x24b6 + key - 'a'; | 3057 | return 'A' + key - 'a'; |
2848 | } | 3058 | } |
2849 | #else | 3059 | #else |
2850 | if (ord < 9 + 26) { | 3060 | if (ord < 9 + 26) { |
2851 | return 0x24b6 + ord - 9; | 3061 | return 'A' + ord - 9; |
2852 | } | 3062 | } |
2853 | #endif | 3063 | #endif |
2854 | } | 3064 | } |
2855 | else { | 3065 | else { |
2856 | if (ord < iElemCount(homeRowKeys_)) { | 3066 | if (ord < iElemCount(homeRowKeys_)) { |
2857 | return 0x24b6 + homeRowKeys_[ord] - 'a'; | 3067 | return 'A' + homeRowKeys_[ord] - 'a'; |
2858 | } | 3068 | } |
2859 | } | 3069 | } |
2860 | return 0; | 3070 | return 0; |
@@ -3041,6 +3251,7 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e | |||
3041 | iArray items; | 3251 | iArray items; |
3042 | init_Array(&items, sizeof(iMenuItem)); | 3252 | init_Array(&items, sizeof(iMenuItem)); |
3043 | if (d->contextLink) { | 3253 | if (d->contextLink) { |
3254 | /* Context menu for a link. */ | ||
3044 | const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); | 3255 | const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId); |
3045 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); | 3256 | // const int linkFlags = linkFlags_GmDocument(d->doc, d->contextLink->linkId); |
3046 | const iRangecc scheme = urlScheme_String(linkUrl); | 3257 | const iRangecc scheme = urlScheme_String(linkUrl); |
@@ -3675,6 +3886,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3675 | /* Preformatted runs can be scrolled. */ | 3886 | /* Preformatted runs can be scrolled. */ |
3676 | runOffset_DocumentWidget_(d->widget, run)); | 3887 | runOffset_DocumentWidget_(d->widget, run)); |
3677 | const iRect visRect = { visPos, run->visBounds.size }; | 3888 | const iRect visRect = { visPos, run->visBounds.size }; |
3889 | #if 0 | ||
3678 | if (run->flags & footer_GmRunFlag) { | 3890 | if (run->flags & footer_GmRunFlag) { |
3679 | iRect footerBack = | 3891 | iRect footerBack = |
3680 | (iRect){ visPos, init_I2(width_Rect(d->widgetBounds), run->visBounds.size.y) }; | 3892 | (iRect){ visPos, init_I2(width_Rect(d->widgetBounds), run->visBounds.size.y) }; |
@@ -3682,6 +3894,7 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3682 | fillRect_Paint(&d->paint, footerBack, tmBackground_ColorId); | 3894 | fillRect_Paint(&d->paint, footerBack, tmBackground_ColorId); |
3683 | return; | 3895 | return; |
3684 | } | 3896 | } |
3897 | #endif | ||
3685 | /* Fill the background. */ { | 3898 | /* Fill the background. */ { |
3686 | if (run->linkId && linkFlags & isOpen_GmLinkFlag) { | 3899 | if (run->linkId && linkFlags & isOpen_GmLinkFlag) { |
3687 | /* Open links get a highlighted background. */ | 3900 | /* Open links get a highlighted background. */ |
@@ -3743,10 +3956,19 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) { | |||
3743 | const iChar ordChar = | 3956 | const iChar ordChar = |
3744 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); | 3957 | linkOrdinalChar_DocumentWidget_(d->widget, ord - d->widget->ordinalBase); |
3745 | if (ordChar) { | 3958 | if (ordChar) { |
3746 | drawString_Text(run->font, | 3959 | const char *circle = "\u25ef"; /* Large Circle */ |
3747 | init_I2(d->viewPos.x - gap_UI / 3, visPos.y), | 3960 | iRect nbArea = { init_I2(d->viewPos.x - gap_UI / 3, visPos.y), |
3961 | init_I2(3.95f * gap_Text, 1.0f * lineHeight_Text(run->font)) }; | ||
3962 | drawRange_Text( | ||
3963 | run->font, topLeft_Rect(nbArea), tmQuote_ColorId, range_CStr(circle)); | ||
3964 | iRect circleArea = visualBounds_Text(run->font, range_CStr(circle)); | ||
3965 | addv_I2(&circleArea.pos, topLeft_Rect(nbArea)); | ||
3966 | drawCentered_Text(defaultContentSmall_FontId, | ||
3967 | circleArea, | ||
3968 | iTrue, | ||
3748 | tmQuote_ColorId, | 3969 | tmQuote_ColorId, |
3749 | collect_String(newUnicodeN_String(&ordChar, 1))); | 3970 | "%lc", |
3971 | (int) ordChar); | ||
3750 | goto runDrawn; | 3972 | goto runDrawn; |
3751 | } | 3973 | } |
3752 | } | 3974 | } |
@@ -4424,9 +4646,16 @@ void deserializeState_DocumentWidget(iDocumentWidget *d, iStream *ins) { | |||
4424 | updateFromHistory_DocumentWidget_(d); | 4646 | updateFromHistory_DocumentWidget_(d); |
4425 | } | 4647 | } |
4426 | 4648 | ||
4649 | static void setUrl_DocumentWidget_(iDocumentWidget *d, const iString *url) { | ||
4650 | if (!equal_String(d->mod.url, url)) { | ||
4651 | d->flags |= urlChanged_DocumentWidgetFlag; | ||
4652 | set_String(d->mod.url, url); | ||
4653 | } | ||
4654 | } | ||
4655 | |||
4427 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { | 4656 | void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBool isFromCache) { |
4428 | setLinkNumberMode_DocumentWidget_(d, iFalse); | 4657 | setLinkNumberMode_DocumentWidget_(d, iFalse); |
4429 | set_String(d->mod.url, urlFragmentStripped_String(url)); | 4658 | setUrl_DocumentWidget_(d, urlFragmentStripped_String(url)); |
4430 | /* See if there a username in the URL. */ | 4659 | /* See if there a username in the URL. */ |
4431 | parseUser_DocumentWidget_(d); | 4660 | parseUser_DocumentWidget_(d); |
4432 | if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) { | 4661 | if (!isFromCache || !updateFromHistory_DocumentWidget_(d)) { |
@@ -4434,6 +4663,20 @@ void setUrlFromCache_DocumentWidget(iDocumentWidget *d, const iString *url, iBoo | |||
4434 | } | 4663 | } |
4435 | } | 4664 | } |
4436 | 4665 | ||
4666 | void setUrlAndSource_DocumentWidget(iDocumentWidget *d, const iString *url, const iString *mime, | ||
4667 | const iBlock *source) { | ||
4668 | setLinkNumberMode_DocumentWidget_(d, iFalse); | ||
4669 | setUrl_DocumentWidget_(d, url); | ||
4670 | parseUser_DocumentWidget_(d); | ||
4671 | iGmResponse *resp = new_GmResponse(); | ||
4672 | resp->statusCode = success_GmStatusCode; | ||
4673 | initCurrent_Time(&resp->when); | ||
4674 | set_String(&resp->meta, mime); | ||
4675 | set_Block(&resp->body, source); | ||
4676 | updateFromCachedResponse_DocumentWidget_(d, 0, resp); | ||
4677 | delete_GmResponse(resp); | ||
4678 | } | ||
4679 | |||
4437 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { | 4680 | iDocumentWidget *duplicate_DocumentWidget(const iDocumentWidget *orig) { |
4438 | iDocumentWidget *d = new_DocumentWidget(); | 4681 | iDocumentWidget *d = new_DocumentWidget(); |
4439 | delete_History(d->mod.history); | 4682 | delete_History(d->mod.history); |
diff --git a/src/ui/documentwidget.h b/src/ui/documentwidget.h index 12603437..c038f981 100644 --- a/src/ui/documentwidget.h +++ b/src/ui/documentwidget.h | |||
@@ -47,6 +47,7 @@ int documentWidth_DocumentWidget (const iDocumentWidget *); | |||
47 | 47 | ||
48 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); | 48 | void setUrl_DocumentWidget (iDocumentWidget *, const iString *url); |
49 | void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache); | 49 | void setUrlFromCache_DocumentWidget (iDocumentWidget *, const iString *url, iBool isFromCache); |
50 | void setUrlAndSource_DocumentWidget (iDocumentWidget *, const iString *url, const iString *mime, const iBlock *source); | ||
50 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ | 51 | void setInitialScroll_DocumentWidget (iDocumentWidget *, float normScrollY); /* set after content received */ |
51 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); | 52 | void setRedirectCount_DocumentWidget (iDocumentWidget *, int count); |
52 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); | 53 | void setSource_DocumentWidget (iDocumentWidget *, const iString *sourceText); |
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c index 0257eda0..cf128017 100644 --- a/src/ui/inputwidget.c +++ b/src/ui/inputwidget.c | |||
@@ -547,7 +547,9 @@ void setText_InputWidget(iInputWidget *d, const iString *text) { | |||
547 | } | 547 | } |
548 | clearUndo_InputWidget_(d); | 548 | clearUndo_InputWidget_(d); |
549 | clear_Array(&d->text); | 549 | clear_Array(&d->text); |
550 | iConstForEach(String, i, text) { | 550 | iString *nfcText = collect_String(copy_String(text)); |
551 | normalize_String(nfcText); | ||
552 | iConstForEach(String, i, nfcText) { | ||
551 | pushBack_Array(&d->text, &i.value); | 553 | pushBack_Array(&d->text, &i.value); |
552 | } | 554 | } |
553 | if (isFocused_Widget(d)) { | 555 | if (isFocused_Widget(d)) { |
diff --git a/src/ui/inputwidget.h b/src/ui/inputwidget.h index 70553488..f8c5bf1e 100644 --- a/src/ui/inputwidget.h +++ b/src/ui/inputwidget.h | |||
@@ -65,6 +65,10 @@ void setEatEscape_InputWidget (iInputWidget *, iBool eatEscape); | |||
65 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); | 65 | iInputWidgetContentPadding contentPadding_InputWidget (const iInputWidget *); |
66 | const iString * text_InputWidget (const iInputWidget *); | 66 | const iString * text_InputWidget (const iInputWidget *); |
67 | 67 | ||
68 | iLocalDef const char *cstrText_InputWidget(const iInputWidget *d) { | ||
69 | return cstr_String(text_InputWidget(d)); | ||
70 | } | ||
71 | |||
68 | iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { | 72 | iLocalDef iInputWidget *newHint_InputWidget(size_t maxLen, const char *hint) { |
69 | iInputWidget *d = new_InputWidget(maxLen); | 73 | iInputWidget *d = new_InputWidget(maxLen); |
70 | setHint_InputWidget(d, hint); | 74 | setHint_InputWidget(d, hint); |
diff --git a/src/ui/keys.c b/src/ui/keys.c index 42d0d613..9df505e0 100644 --- a/src/ui/keys.c +++ b/src/ui/keys.c | |||
@@ -188,6 +188,7 @@ static void clear_Keys_(iKeys *d) { | |||
188 | enum iBindFlag { | 188 | enum iBindFlag { |
189 | argRepeat_BindFlag = iBit(1), | 189 | argRepeat_BindFlag = iBit(1), |
190 | argRelease_BindFlag = iBit(2), | 190 | argRelease_BindFlag = iBit(2), |
191 | noDirectTrigger_BindFlag = iBit(3), /* can only be triggered via LabelWidget */ | ||
191 | }; | 192 | }; |
192 | 193 | ||
193 | /* TODO: This indirection could be used for localization, although all UI strings | 194 | /* TODO: This indirection could be used for localization, although all UI strings |
@@ -227,7 +228,16 @@ static const struct { int id; iMenuItem bind; int flags; } defaultBindings_[] = | |||
227 | { 81, { "${keys.tab.next}", nextTab_KeyShortcut, "tabs.next" }, 0 }, | 228 | { 81, { "${keys.tab.next}", nextTab_KeyShortcut, "tabs.next" }, 0 }, |
228 | { 90, { "${keys.split.menu}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, 0 }, | 229 | { 90, { "${keys.split.menu}", SDLK_j, KMOD_PRIMARY, "splitmenu.open" }, 0 }, |
229 | { 91, { "${keys.split.next}", SDLK_TAB, KMOD_CTRL, "keyroot.next", }, 0 }, | 230 | { 91, { "${keys.split.next}", SDLK_TAB, KMOD_CTRL, "keyroot.next", }, 0 }, |
231 | { 92, { "${keys.split.item} ${menu.split.merge}", '1', 0, "ui.split arg:0", }, noDirectTrigger_BindFlag }, | ||
232 | { 93, { "${keys.split.item} ${menu.split.swap}", SDLK_x, 0, "ui.split swap:1", }, noDirectTrigger_BindFlag }, | ||
233 | { 94, { "${keys.split.item} ${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0", }, noDirectTrigger_BindFlag }, | ||
234 | { 95, { "${keys.split.item} ${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0", }, noDirectTrigger_BindFlag }, | ||
235 | { 96, { "${keys.split.item} ${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0", }, noDirectTrigger_BindFlag }, | ||
236 | { 97, { "${keys.split.item} ${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1", }, noDirectTrigger_BindFlag }, | ||
237 | { 98, { "${keys.split.item} ${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1", }, noDirectTrigger_BindFlag }, | ||
238 | { 99, { "${keys.split.item} ${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1", }, noDirectTrigger_BindFlag }, | ||
230 | { 100,{ "${keys.hoverurl}", '/', KMOD_PRIMARY, "prefs.hoverlink.toggle" }, 0 }, | 239 | { 100,{ "${keys.hoverurl}", '/', KMOD_PRIMARY, "prefs.hoverlink.toggle" }, 0 }, |
240 | { 110,{ "${menu.save.downloads}", SDLK_s, KMOD_PRIMARY, "document.save" }, 0 }, | ||
231 | /* The following cannot currently be changed (built-in duplicates). */ | 241 | /* The following cannot currently be changed (built-in duplicates). */ |
232 | #if defined (iPlatformApple) | 242 | #if defined (iPlatformApple) |
233 | { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 }, | 243 | { 1002, { NULL, SDLK_LEFTBRACKET, KMOD_PRIMARY, "navigate.back" }, 0 }, |
@@ -301,7 +311,10 @@ static iBinding *findCommand_Keys_(iKeys *d, const char *command) { | |||
301 | static void updateLookup_Keys_(iKeys *d) { | 311 | static void updateLookup_Keys_(iKeys *d) { |
302 | clear_PtrSet(&d->lookup); | 312 | clear_PtrSet(&d->lookup); |
303 | iConstForEach(Array, i, &d->bindings) { | 313 | iConstForEach(Array, i, &d->bindings) { |
304 | insert_PtrSet(&d->lookup, i.value); | 314 | const iBinding *bind = i.value; |
315 | if (~bind->flags & noDirectTrigger_BindFlag) { | ||
316 | insert_PtrSet(&d->lookup, i.value); | ||
317 | } | ||
305 | } | 318 | } |
306 | } | 319 | } |
307 | 320 | ||
diff --git a/src/ui/keys.h b/src/ui/keys.h index 4cbca3b7..6273027a 100644 --- a/src/ui/keys.h +++ b/src/ui/keys.h | |||
@@ -26,6 +26,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
26 | #include <the_Foundation/ptrarray.h> | 26 | #include <the_Foundation/ptrarray.h> |
27 | #include <SDL_events.h> | 27 | #include <SDL_events.h> |
28 | 28 | ||
29 | #define newIdentity_KeyShortcut SDLK_n, KMOD_PRIMARY | KMOD_SHIFT | ||
30 | |||
29 | #if defined (iPlatformApple) | 31 | #if defined (iPlatformApple) |
30 | # define reload_KeyShortcut SDLK_r, KMOD_PRIMARY | 32 | # define reload_KeyShortcut SDLK_r, KMOD_PRIMARY |
31 | # define newTab_KeyShortcut SDLK_t, KMOD_PRIMARY | 33 | # define newTab_KeyShortcut SDLK_t, KMOD_PRIMARY |
diff --git a/src/ui/listwidget.c b/src/ui/listwidget.c index a3406d48..f7c43a93 100644 --- a/src/ui/listwidget.c +++ b/src/ui/listwidget.c | |||
@@ -148,11 +148,16 @@ void updateVisible_ListWidget(iListWidget *d) { | |||
148 | const int contentSize = size_PtrArray(&d->items) * d->itemHeight; | 148 | const int contentSize = size_PtrArray(&d->items) * d->itemHeight; |
149 | const iRect bounds = innerBounds_Widget(as_Widget(d)); | 149 | const iRect bounds = innerBounds_Widget(as_Widget(d)); |
150 | const iBool wasVisible = isVisible_Widget(d->scroll); | 150 | const iBool wasVisible = isVisible_Widget(d->scroll); |
151 | if (area_Rect(bounds) == 0) { | 151 | if (width_Rect(bounds) <= 0 || height_Rect(bounds) <= 0) { |
152 | return; | 152 | return; |
153 | } | 153 | } |
154 | /* The scroll widget's visibility depends on it having a valid non-zero size. | ||
155 | However, this may be called during arrangement (sizeChanged_ListWidget_), | ||
156 | which means the child hasn't been arranged yet. The child cannot update | ||
157 | its visibility unless it knows its correct size. */ | ||
158 | arrange_Widget(as_Widget(d->scroll)); | ||
154 | setMax_SmoothScroll(&d->scrollY, scrollMax_ListWidget_(d)); | 159 | setMax_SmoothScroll(&d->scrollY, scrollMax_ListWidget_(d)); |
155 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, d->scrollY.max }); | 160 | setRange_ScrollWidget(d->scroll, (iRangei){ 0, d->scrollY.max }); |
156 | setThumb_ScrollWidget(d->scroll, | 161 | setThumb_ScrollWidget(d->scroll, |
157 | pos_SmoothScroll(&d->scrollY), | 162 | pos_SmoothScroll(&d->scrollY), |
158 | contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) * | 163 | contentSize > 0 ? height_Rect(bounds_Widget(as_Widget(d->scroll))) * |
@@ -369,6 +374,13 @@ static iBool processEvent_ListWidget_(iListWidget *d, const SDL_Event *ev) { | |||
369 | return processEvent_Widget(w, ev); | 374 | return processEvent_Widget(w, ev); |
370 | } | 375 | } |
371 | 376 | ||
377 | iRect itemRect_ListWidget(const iListWidget *d, size_t index) { | ||
378 | const iRect bounds = innerBounds_Widget(constAs_Widget(d)); | ||
379 | const int scrollY = pos_SmoothScroll(&d->scrollY); | ||
380 | return (iRect){ addY_I2(topLeft_Rect(bounds), d->itemHeight * (int) index - scrollY), | ||
381 | init_I2(width_Rect(bounds), d->itemHeight) }; | ||
382 | } | ||
383 | |||
372 | static void draw_ListWidget_(const iListWidget *d) { | 384 | static void draw_ListWidget_(const iListWidget *d) { |
373 | const iWidget *w = constAs_Widget(d); | 385 | const iWidget *w = constAs_Widget(d); |
374 | const iRect bounds = innerBounds_Widget(w); | 386 | const iRect bounds = innerBounds_Widget(w); |
diff --git a/src/ui/listwidget.h b/src/ui/listwidget.h index 16adf664..314c183a 100644 --- a/src/ui/listwidget.h +++ b/src/ui/listwidget.h | |||
@@ -73,6 +73,7 @@ iAnyObject * hoverItem_ListWidget (iListWidget *); | |||
73 | size_t numItems_ListWidget (const iListWidget *); | 73 | size_t numItems_ListWidget (const iListWidget *); |
74 | int visCount_ListWidget (const iListWidget *); | 74 | int visCount_ListWidget (const iListWidget *); |
75 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); | 75 | size_t itemIndex_ListWidget (const iListWidget *, iInt2 pos); |
76 | iRect itemRect_ListWidget (const iListWidget *, size_t index); | ||
76 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); | 77 | const iAnyObject * constItem_ListWidget (const iListWidget *, size_t index); |
77 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); | 78 | const iAnyObject * constHoverItem_ListWidget (const iListWidget *); |
78 | size_t hoverItemIndex_ListWidget (const iListWidget *); | 79 | size_t hoverItemIndex_ListWidget (const iListWidget *); |
diff --git a/src/ui/lookupwidget.c b/src/ui/lookupwidget.c index 3eafd4bd..254aad93 100644 --- a/src/ui/lookupwidget.c +++ b/src/ui/lookupwidget.c | |||
@@ -127,10 +127,11 @@ static void draw_LookupItem_(iLookupItem *d, iPaint *p, iRect rect, const iListW | |||
127 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font); | 127 | pos.y = bottom_Rect(rect) - lineHeight_Text(d->font); |
128 | } | 128 | } |
129 | if (!isEmpty_String(&d->icon)) { | 129 | if (!isEmpty_String(&d->icon)) { |
130 | const iRect iconRect = { pos, init_I2(gap_UI * 5, height_Rect(rect)) }; | 130 | const iRect iconRect = { init_I2(pos.x, top_Rect(rect)), |
131 | const iInt2 iconSize = measureRange_Text(d->font, range_String(&d->icon)); | 131 | init_I2(gap_UI * 5, height_Rect(rect)) }; |
132 | const iRect iconVis = visualBounds_Text(d->font, range_String(&d->icon)); | ||
132 | drawRange_Text(d->font, | 133 | drawRange_Text(d->font, |
133 | addX_I2(pos, width_Rect(iconRect) / 2 - iconSize.x / 2), | 134 | sub_I2(mid_Rect(iconRect), mid_Rect(iconVis)), |
134 | fg, | 135 | fg, |
135 | range_String(&d->icon)); | 136 | range_String(&d->icon)); |
136 | pos.x += width_Rect(iconRect) + gap_UI * 3 / 2; | 137 | pos.x += width_Rect(iconRect) + gap_UI * 3 / 2; |
@@ -301,7 +302,7 @@ static void searchIdentities_LookupJob_(iLookupJob *d) { | |||
301 | iLookupResult *res = new_LookupResult(); | 302 | iLookupResult *res = new_LookupResult(); |
302 | res->type = identity_LookupResultType; | 303 | res->type = identity_LookupResultType; |
303 | res->relevance = identityRelevance_LookupJob_(d, identity); | 304 | res->relevance = identityRelevance_LookupJob_(d, identity); |
304 | res->icon = identity->icon; | 305 | res->icon = 0x1f464; /* identity->icon; */ |
305 | iString *cn = subject_TlsCertificate(identity->cert); | 306 | iString *cn = subject_TlsCertificate(identity->cert); |
306 | set_String(&res->label, cn); | 307 | set_String(&res->label, cn); |
307 | delete_String(cn); | 308 | delete_String(cn); |
@@ -701,6 +702,12 @@ static iBool processEvent_LookupWidget_(iLookupWidget *d, const SDL_Event *ev) { | |||
701 | } | 702 | } |
702 | return iTrue; | 703 | return iTrue; |
703 | } | 704 | } |
705 | if (ev->type == SDL_MOUSEMOTION) { | ||
706 | if (contains_Widget(w, init_I2(ev->motion.x, ev->motion.y))) { | ||
707 | setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_HAND); | ||
708 | } | ||
709 | return iFalse; | ||
710 | } | ||
704 | if (ev->type == SDL_KEYDOWN) { | 711 | if (ev->type == SDL_KEYDOWN) { |
705 | const int mods = keyMods_Sym(ev->key.keysym.mod); | 712 | const int mods = keyMods_Sym(ev->key.keysym.mod); |
706 | const int key = ev->key.keysym.sym; | 713 | const int key = ev->key.keysym.sym; |
diff --git a/src/ui/mediaui.c b/src/ui/mediaui.c index 24b29cb0..bc417fc3 100644 --- a/src/ui/mediaui.c +++ b/src/ui/mediaui.c | |||
@@ -86,7 +86,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { | |||
86 | const int hours = seconds / 3600; | 86 | const int hours = seconds / 3600; |
87 | const int mins = (seconds / 60) % 60; | 87 | const int mins = (seconds / 60) % 60; |
88 | const int secs = seconds % 60; | 88 | const int secs = seconds % 60; |
89 | const int font = uiLabel_FontId; | 89 | const int font = defaultBig_FontId; |
90 | iString num; | 90 | iString num; |
91 | init_String(&num); | 91 | init_String(&num); |
92 | if (hours) { | 92 | if (hours) { |
@@ -102,7 +102,7 @@ static int drawSevenSegmentTime_(iInt2 pos, int color, int align, int seconds) { | |||
102 | if (align == right_Alignment) { | 102 | if (align == right_Alignment) { |
103 | pos.x -= size.x; | 103 | pos.x -= size.x; |
104 | } | 104 | } |
105 | drawRange_Text(font, pos, color, range_String(&num)); | 105 | drawRange_Text(font, addY_I2(pos, -gap_UI / 8), color, range_String(&num)); |
106 | deinit_String(&num); | 106 | deinit_String(&num); |
107 | return size.x; | 107 | return size.x; |
108 | } | 108 | } |
@@ -123,7 +123,7 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
123 | drawPlayerButton_( | 123 | drawPlayerButton_( |
124 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); | 124 | p, d->volumeRect, volumeChar_(volume_Player(d->player)), uiContentSymbols_FontId); |
125 | } | 125 | } |
126 | const int hgt = lineHeight_Text(uiLabel_FontId); | 126 | const int hgt = lineHeight_Text(defaultBig_FontId); |
127 | const int yMid = mid_Rect(d->scrubberRect).y; | 127 | const int yMid = mid_Rect(d->scrubberRect).y; |
128 | const float playTime = time_Player(d->player); | 128 | const float playTime = time_Player(d->player); |
129 | const float totalTime = duration_Player(d->player); | 129 | const float totalTime = duration_Player(d->player); |
@@ -153,7 +153,8 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
153 | const char *dot = "\u23fa"; | 153 | const char *dot = "\u23fa"; |
154 | const int dotWidth = advance_Text(uiLabel_FontId, dot).x; | 154 | const int dotWidth = advance_Text(uiLabel_FontId, dot).x; |
155 | draw_Text(uiLabel_FontId, | 155 | draw_Text(uiLabel_FontId, |
156 | init_I2(s1 * (1.0f - normPos) + s2 * normPos - dotWidth / 2, yMid - hgt / 2), | 156 | init_I2(s1 * (1.0f - normPos) + s2 * normPos - dotWidth / 2, |
157 | yMid - lineHeight_Text(uiLabel_FontId) / 2), | ||
157 | isPaused_Player(d->player) ? dim : bright, | 158 | isPaused_Player(d->player) ? dim : bright, |
158 | dot); | 159 | dot); |
159 | /* Volume adjustment. */ | 160 | /* Volume adjustment. */ |
@@ -186,7 +187,8 @@ void draw_PlayerUI(iPlayerUI *d, iPaint *p) { | |||
186 | width_Rect(d->volumeSlider) - volPart, | 187 | width_Rect(d->volumeSlider) - volPart, |
187 | dim); | 188 | dim); |
188 | draw_Text(uiLabel_FontId, | 189 | draw_Text(uiLabel_FontId, |
189 | init_I2(left_Rect(d->volumeSlider) + volPart - dotWidth / 2, yMid - hgt / 2), | 190 | init_I2(left_Rect(d->volumeSlider) + volPart - dotWidth / 2, |
191 | yMid - lineHeight_Text(uiLabel_FontId) / 2), | ||
190 | volColor, | 192 | volColor, |
191 | dot); | 193 | dot); |
192 | } | 194 | } |
diff --git a/src/ui/mobile.c b/src/ui/mobile.c index 6c3a0b32..263fc141 100644 --- a/src/ui/mobile.c +++ b/src/ui/mobile.c | |||
@@ -381,22 +381,22 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
381 | } | 381 | } |
382 | /* TODO: In portrait, top panel and detail stack are all stacked together. | 382 | /* TODO: In portrait, top panel and detail stack are all stacked together. |
383 | 383 | ||
384 | Landscape Layout Portrait Layout | 384 | Landscape Layout Portrait Layout |
385 | 385 | ||
386 | ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐ | 386 | ┌─────────┬──────Detail─Stack─────┐ ┌─────────┬ ─ ─ ─ ─ ┐ |
387 | │ │┌───────────────────┐ │ │ │Detail | 387 | │ │┌───────────────────┐ │ │ │Detail |
388 | │ ││┌──────────────────┴┐ │ │ │Stack │ | 388 | │ ││┌──────────────────┴┐ │ │ │Stack │ |
389 | │ │││┌──────────────────┴┐│ │ │┌──────┐ | 389 | │ │││┌──────────────────┴┐│ │ │┌──────┐ |
390 | │ ││││ ││ │ ││┌─────┴┐│ | 390 | │ ││││ ││ │ ││┌─────┴┐│ |
391 | │ ││││ ││ │ │││ │ | 391 | │ ││││ ││ │ │││ │ |
392 | │Top Panel││││ ││ │Top Panel│││ ││ | 392 | │Top Panel││││ ││ │Top Panel│││ ││ |
393 | │ ││││ Panels ││ │ │││Panels│ | 393 | │ ││││ Panels ││ │ │││Panels│ |
394 | │ ││││ ││ │ │││ ││ | 394 | │ ││││ ││ │ │││ ││ |
395 | │ │└┤│ ││ │ │││ │ | 395 | │ │└┤│ ││ │ │││ │ |
396 | │ │ └┤ ││ │ │└┤ ││ | 396 | │ │ └┤ ││ │ │└┤ ││ |
397 | │ │ └───────────────────┘│ │ │ └──────┘ | 397 | │ │ └───────────────────┘│ │ │ └──────┘ |
398 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ | 398 | └─────────┴───────────────────────┘ └─────────┴ ─ ─ ─ ─ ┘ |
399 | offscreen | 399 | offscreen |
400 | */ | 400 | */ |
401 | /* Modify the top sheet to act as a fullscreen background. */ | 401 | /* Modify the top sheet to act as a fullscreen background. */ |
402 | setPadding1_Widget(sheet, 0); | 402 | setPadding1_Widget(sheet, 0); |
@@ -759,6 +759,9 @@ void finalizeSheet_Mobile(iWidget *sheet) { | |||
759 | else { | 759 | else { |
760 | arrange_Widget(sheet); | 760 | arrange_Widget(sheet); |
761 | } | 761 | } |
762 | if (!useMobileSheetLayout_()) { | ||
763 | setupSheetTransition_Mobile(sheet, iTrue); | ||
764 | } | ||
762 | postRefresh_App(); | 765 | postRefresh_App(); |
763 | } | 766 | } |
764 | 767 | ||
@@ -784,16 +787,28 @@ void setupMenuTransition_Mobile(iWidget *sheet, iBool isIncoming) { | |||
784 | } | 787 | } |
785 | 788 | ||
786 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { | 789 | void setupSheetTransition_Mobile(iWidget *sheet, iBool isIncoming) { |
787 | if (isSideBySideLayout_()) { | 790 | if (!useMobileSheetLayout_()) { |
791 | if (prefs_App()->uiAnimations) { | ||
792 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iFalse); | ||
793 | if (isIncoming) { | ||
794 | setVisualOffset_Widget(sheet, -height_Widget(sheet), 0, 0); | ||
795 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag | softer_AnimFlag); | ||
796 | } | ||
797 | else { | ||
798 | setVisualOffset_Widget(sheet, -height_Widget(sheet), 200, easeIn_AnimFlag); | ||
799 | } | ||
800 | } | ||
801 | return; | ||
802 | } | ||
803 | if(isSideBySideLayout_()) { | ||
788 | return; | 804 | return; |
789 | } | 805 | } |
806 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
790 | if (isIncoming) { | 807 | if (isIncoming) { |
791 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
792 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); | 808 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, 0, 0); |
793 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); | 809 | setVisualOffset_Widget(sheet, 0, 200, easeOut_AnimFlag); |
794 | } | 810 | } |
795 | else { | 811 | else { |
796 | setFlags_Widget(sheet, horizontalOffset_WidgetFlag, iTrue); | ||
797 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; | 812 | const iBool wasDragged = iAbs(value_Anim(&sheet->visualOffset)) > 0; |
798 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, | 813 | setVisualOffset_Widget(sheet, size_Root(sheet->root).x, wasDragged ? 100 : 200, |
799 | wasDragged ? 0 : easeIn_AnimFlag); | 814 | wasDragged ? 0 : easeIn_AnimFlag); |
diff --git a/src/ui/root.c b/src/ui/root.c index 9ed62711..15548e74 100644 --- a/src/ui/root.c +++ b/src/ui/root.c | |||
@@ -128,7 +128,7 @@ static const iMenuItem phoneNavMenuItems_[] = { | |||
128 | static const iMenuItem identityButtonMenuItems_[] = { | 128 | static const iMenuItem identityButtonMenuItems_[] = { |
129 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | 129 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, |
130 | { "---", 0, 0, NULL }, | 130 | { "---", 0, 0, NULL }, |
131 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | 131 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, |
132 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | 132 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, |
133 | { "---", 0, 0, NULL }, | 133 | { "---", 0, 0, NULL }, |
134 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, | 134 | { person_Icon " ${menu.show.identities}", 0, 0, "toolbar.showident" }, |
@@ -138,7 +138,7 @@ static const iMenuItem identityButtonMenuItems_[] = { | |||
138 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, | 138 | { "${menu.identity.notactive}", 0, 0, "ident.showactive" }, |
139 | { "---", 0, 0, NULL }, | 139 | { "---", 0, 0, NULL }, |
140 | # if !defined (iPlatformAppleDesktop) | 140 | # if !defined (iPlatformAppleDesktop) |
141 | { add_Icon " ${menu.identity.new}", SDLK_n, KMOD_PRIMARY | KMOD_SHIFT, "ident.new" }, | 141 | { add_Icon " ${menu.identity.new}", newIdentity_KeyShortcut, "ident.new" }, |
142 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, | 142 | { "${menu.identity.import}", SDLK_i, KMOD_PRIMARY | KMOD_SHIFT, "ident.import" }, |
143 | { "---", 0, 0, NULL }, | 143 | { "---", 0, 0, NULL }, |
144 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, | 144 | { person_Icon " ${menu.show.identities}", '4', KMOD_PRIMARY, "sidebar.mode arg:3 show:1" }, |
@@ -156,10 +156,10 @@ static const char *pageMenuCStr_ = midEllipsis_Icon; | |||
156 | /* TODO: A preference for these, maybe? */ | 156 | /* TODO: A preference for these, maybe? */ |
157 | static const char *stopSeqCStr_[] = { | 157 | static const char *stopSeqCStr_[] = { |
158 | /* Corners */ | 158 | /* Corners */ |
159 | uiTextCaution_ColorEscape "\U0000230c", | 159 | uiTextCaution_ColorEscape "\U0000231c", |
160 | uiTextCaution_ColorEscape "\U0000230d", | 160 | uiTextCaution_ColorEscape "\U0000231d", |
161 | uiTextCaution_ColorEscape "\U0000230f", | 161 | uiTextCaution_ColorEscape "\U0000231f", |
162 | uiTextCaution_ColorEscape "\U0000230e", | 162 | uiTextCaution_ColorEscape "\U0000231e", |
163 | #if 0 | 163 | #if 0 |
164 | /* Rotating arrow */ | 164 | /* Rotating arrow */ |
165 | uiTextCaution_ColorEscape "\U00002b62", | 165 | uiTextCaution_ColorEscape "\U00002b62", |
@@ -276,6 +276,9 @@ void destroyPending_Root(iRoot *d) { | |||
276 | if (!isFinished_Anim(&widget->visualOffset)) { | 276 | if (!isFinished_Anim(&widget->visualOffset)) { |
277 | continue; | 277 | continue; |
278 | } | 278 | } |
279 | if (widget->flags & keepOnTop_WidgetFlag) { | ||
280 | removeOne_PtrArray(onTop_Root(widget->root), widget); | ||
281 | } | ||
279 | if (widget->parent) { | 282 | if (widget->parent) { |
280 | removeChild_Widget(widget->parent, widget); | 283 | removeChild_Widget(widget->parent, widget); |
281 | } | 284 | } |
@@ -435,11 +438,11 @@ static void updateNavBarIdentity_(iWidget *navBar) { | |||
435 | setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); | 438 | setFlags_Widget(tool, selected_WidgetFlag, ident != NULL); |
436 | /* Update menu. */ | 439 | /* Update menu. */ |
437 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); | 440 | iLabelWidget *idItem = child_Widget(findChild_Widget(button, "menu"), 0); |
441 | const iString *subjectName = ident ? name_GmIdentity(ident) : NULL; | ||
438 | setTextCStr_LabelWidget( | 442 | setTextCStr_LabelWidget( |
439 | idItem, | 443 | idItem, |
440 | ident ? format_CStr(uiTextAction_ColorEscape "%s", | 444 | subjectName ? format_CStr(uiTextAction_ColorEscape "%s", cstr_String(subjectName)) |
441 | cstrCollect_String(subject_TlsCertificate(ident->cert))) | 445 | : "${menu.identity.notactive}"); |
442 | : "${menu.identity.notactive}"); | ||
443 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); | 446 | setFlags_Widget(as_Widget(idItem), disabled_WidgetFlag, !ident); |
444 | } | 447 | } |
445 | 448 | ||
@@ -1046,7 +1049,7 @@ void createUserInterface_Root(iRoot *d) { | |||
1046 | moveToParentRightEdge_WidgetFlag); | 1049 | moveToParentRightEdge_WidgetFlag); |
1047 | /* Feeds refresh indicator is inside the input field. */ { | 1050 | /* Feeds refresh indicator is inside the input field. */ { |
1048 | iLabelWidget *queryInd = | 1051 | iLabelWidget *queryInd = |
1049 | new_LabelWidget(uiTextAction_ColorEscape "${status.query} \u21a9", NULL); | 1052 | new_LabelWidget(uiTextAction_ColorEscape "${status.query} " return_Icon, NULL); |
1050 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); | 1053 | setId_Widget(as_Widget(queryInd), "input.indicator.search"); |
1051 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); | 1054 | setBackgroundColor_Widget(as_Widget(queryInd), uiBackground_ColorId); |
1052 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); | 1055 | setFrameColor_Widget(as_Widget(queryInd), uiTextAction_ColorId); |
@@ -1305,11 +1308,11 @@ void createUserInterface_Root(iRoot *d) { | |||
1305 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, | 1308 | { "${menu.split.merge}", '1', 0, "ui.split arg:0" }, |
1306 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, | 1309 | { "${menu.split.swap}", SDLK_x, 0, "ui.split swap:1" }, |
1307 | { "---", 0, 0, NULL }, | 1310 | { "---", 0, 0, NULL }, |
1308 | { "${menu.split.horizontal}", '2', 0, "ui.split arg:3 axis:0" }, | 1311 | { "${menu.split.horizontal}", '3', 0, "ui.split arg:3 axis:0" }, |
1309 | { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" }, | 1312 | { "${menu.split.horizontal} 1:2", SDLK_d, 0, "ui.split arg:1 axis:0" }, |
1310 | { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" }, | 1313 | { "${menu.split.horizontal} 2:1", SDLK_e, 0, "ui.split arg:2 axis:0" }, |
1311 | { "---", 0, 0, NULL }, | 1314 | { "---", 0, 0, NULL }, |
1312 | { "${menu.split.vertical}", '3', 0, "ui.split arg:3 axis:1" }, | 1315 | { "${menu.split.vertical}", '2', 0, "ui.split arg:3 axis:1" }, |
1313 | { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" }, | 1316 | { "${menu.split.vertical} 1:2", SDLK_f, 0, "ui.split arg:1 axis:1" }, |
1314 | { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" }, | 1317 | { "${menu.split.vertical} 2:1", SDLK_r, 0, "ui.split arg:2 axis:1" }, |
1315 | }, 10); | 1318 | }, 10); |
diff --git a/src/ui/scrollwidget.c b/src/ui/scrollwidget.c index ff5144b2..0bab601a 100644 --- a/src/ui/scrollwidget.c +++ b/src/ui/scrollwidget.c | |||
@@ -54,7 +54,8 @@ struct Impl_ScrollWidget { | |||
54 | }; | 54 | }; |
55 | 55 | ||
56 | static void updateMetrics_ScrollWidget_(iScrollWidget *d) { | 56 | static void updateMetrics_ScrollWidget_(iScrollWidget *d) { |
57 | as_Widget(d)->rect.size.x = gap_UI * 3; | 57 | iWidget *w = as_Widget(d); |
58 | w->rect.size.x = gap_UI * 3; | ||
58 | } | 59 | } |
59 | 60 | ||
60 | static void animateOpacity_ScrollWidget_(void *ptr) { | 61 | static void animateOpacity_ScrollWidget_(void *ptr) { |
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c index 86410d11..27646b22 100644 --- a/src/ui/sidebarwidget.c +++ b/src/ui/sidebarwidget.c | |||
@@ -148,6 +148,65 @@ static iLabelWidget *addActionButton_SidebarWidget_(iSidebarWidget *d, const cha | |||
148 | return btn; | 148 | return btn; |
149 | } | 149 | } |
150 | 150 | ||
151 | static iGmIdentity *menuIdentity_SidebarWidget_(const iSidebarWidget *d) { | ||
152 | if (d->mode == identities_SidebarMode) { | ||
153 | if (d->contextItem) { | ||
154 | return identity_GmCerts(certs_App(), d->contextItem->id); | ||
155 | } | ||
156 | } | ||
157 | return NULL; | ||
158 | } | ||
159 | |||
160 | static void updateContextMenu_SidebarWidget_(iSidebarWidget *d) { | ||
161 | if (d->mode != identities_SidebarMode) { | ||
162 | return; | ||
163 | } | ||
164 | iArray *items = collectNew_Array(sizeof(iMenuItem)); | ||
165 | pushBackN_Array(items, (iMenuItem[]){ | ||
166 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | ||
167 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | ||
168 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | ||
169 | { "---", 0, 0, NULL }, | ||
170 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | ||
171 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | ||
172 | { export_Icon " ${ident.export}", 0, 0, "ident.export" }, | ||
173 | { "---", 0, 0, NULL }, | ||
174 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | ||
175 | }, 9); | ||
176 | /* Used URLs. */ | ||
177 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
178 | if (ident) { | ||
179 | size_t insertPos = 3; | ||
180 | if (!isEmpty_StringSet(ident->useUrls)) { | ||
181 | insert_Array(items, insertPos++, &(iMenuItem){ "---", 0, 0, NULL }); | ||
182 | } | ||
183 | const iString *docUrl = url_DocumentWidget(document_App()); | ||
184 | iBool usedOnCurrentPage = iFalse; | ||
185 | iConstForEach(StringSet, i, ident->useUrls) { | ||
186 | const iString *url = i.value; | ||
187 | usedOnCurrentPage |= equalCase_String(docUrl, url); | ||
188 | iRangecc urlStr = range_String(url); | ||
189 | if (startsWith_Rangecc(urlStr, "gemini://")) { | ||
190 | urlStr.start += 9; /* omit the default scheme */ | ||
191 | } | ||
192 | if (endsWith_Rangecc(urlStr, "/")) { | ||
193 | urlStr.end--; /* looks cleaner */ | ||
194 | } | ||
195 | insert_Array(items, | ||
196 | insertPos++, | ||
197 | &(iMenuItem){ format_CStr(globe_Icon " %s", cstr_Rangecc(urlStr)), | ||
198 | 0, | ||
199 | 0, | ||
200 | format_CStr("!open url:%s", cstr_String(url)) }); | ||
201 | } | ||
202 | if (!usedOnCurrentPage) { | ||
203 | remove_Array(items, 1); | ||
204 | } | ||
205 | } | ||
206 | destroy_Widget(d->menu); | ||
207 | d->menu = makeMenu_Widget(as_Widget(d), data_Array(items), size_Array(items)); | ||
208 | } | ||
209 | |||
151 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { | 210 | static void updateItems_SidebarWidget_(iSidebarWidget *d) { |
152 | clear_ListWidget(d->list); | 211 | clear_ListWidget(d->list); |
153 | releaseChildren_Widget(d->blank); | 212 | releaseChildren_Widget(d->blank); |
@@ -375,13 +434,14 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
375 | } | 434 | } |
376 | case identities_SidebarMode: { | 435 | case identities_SidebarMode: { |
377 | const iString *tabUrl = url_DocumentWidget(document_App()); | 436 | const iString *tabUrl = url_DocumentWidget(document_App()); |
437 | const iRangecc tabHost = urlHost_String(tabUrl); | ||
378 | isEmpty = iTrue; | 438 | isEmpty = iTrue; |
379 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { | 439 | iConstForEach(PtrArray, i, identities_GmCerts(certs_App())) { |
380 | const iGmIdentity *ident = i.ptr; | 440 | const iGmIdentity *ident = i.ptr; |
381 | iSidebarItem *item = new_SidebarItem(); | 441 | iSidebarItem *item = new_SidebarItem(); |
382 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); | 442 | item->id = (uint32_t) index_PtrArrayConstIterator(&i); |
383 | item->icon = ident->icon; | 443 | item->icon = 0x1f464; /* person */ |
384 | set_String(&item->label, collect_String(subject_TlsCertificate(ident->cert))); | 444 | set_String(&item->label, name_GmIdentity(ident)); |
385 | iDate until; | 445 | iDate until; |
386 | validUntil_TlsCertificate(ident->cert, &until); | 446 | validUntil_TlsCertificate(ident->cert, &until); |
387 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); | 447 | const iBool isActive = isUsedOn_GmIdentity(ident, tabUrl); |
@@ -406,6 +466,9 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
406 | cstr_String(&ident->notes)); | 466 | cstr_String(&ident->notes)); |
407 | } | 467 | } |
408 | item->listItem.isSelected = isActive; | 468 | item->listItem.isSelected = isActive; |
469 | if (isUsedOnDomain_GmIdentity(ident, tabHost)) { | ||
470 | item->indent = 1; /* will be highlighted */ | ||
471 | } | ||
409 | addItem_ListWidget(d->list, item); | 472 | addItem_ListWidget(d->list, item); |
410 | iRelease(item); | 473 | iRelease(item); |
411 | isEmpty = iFalse; | 474 | isEmpty = iFalse; |
@@ -415,11 +478,11 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
415 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); | 478 | addActionButton_SidebarWidget_(d, add_Icon " ${sidebar.action.ident.new}", "ident.new", 0); |
416 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); | 479 | addActionButton_SidebarWidget_(d, "${sidebar.action.ident.import}", "ident.import", 0); |
417 | } | 480 | } |
481 | /* | ||
418 | const iMenuItem menuItems[] = { | 482 | const iMenuItem menuItems[] = { |
419 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, | 483 | { person_Icon " ${ident.use}", 0, 0, "ident.use arg:1" }, |
420 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, | 484 | { close_Icon " ${ident.stopuse}", 0, 0, "ident.use arg:0" }, |
421 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, | 485 | { close_Icon " ${ident.stopuse.all}", 0, 0, "ident.use arg:0 clear:1" }, |
422 | { "${ident.showuse}", 0, 0, "ident.showuse" }, | ||
423 | { "---", 0, 0, NULL }, | 486 | { "---", 0, 0, NULL }, |
424 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, | 487 | { edit_Icon " ${menu.edit.notes}", 0, 0, "ident.edit" }, |
425 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, | 488 | { "${ident.fingerprint}", 0, 0, "ident.fingerprint" }, |
@@ -429,6 +492,7 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) { | |||
429 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, | 492 | { delete_Icon " " uiTextCaution_ColorEscape "${ident.delete}", 0, 0, "ident.delete confirm:1" }, |
430 | }; | 493 | }; |
431 | d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems)); | 494 | d->menu = makeMenu_Widget(as_Widget(d), menuItems, iElemCount(menuItems)); |
495 | */ | ||
432 | break; | 496 | break; |
433 | } | 497 | } |
434 | default: | 498 | default: |
@@ -697,20 +761,11 @@ static const iGmIdentity *constHoverIdentity_SidebarWidget_(const iSidebarWidget | |||
697 | return NULL; | 761 | return NULL; |
698 | } | 762 | } |
699 | 763 | ||
700 | static iGmIdentity *menuIdentity_SidebarWidget_(const iSidebarWidget *d) { | ||
701 | if (d->mode == identities_SidebarMode) { | ||
702 | if (d->contextItem) { | ||
703 | return identity_GmCerts(certs_App(), d->contextItem->id); | ||
704 | } | ||
705 | } | ||
706 | return NULL; | ||
707 | } | ||
708 | |||
709 | static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) { | 764 | static iGmIdentity *hoverIdentity_SidebarWidget_(const iSidebarWidget *d) { |
710 | return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d)); | 765 | return iConstCast(iGmIdentity *, constHoverIdentity_SidebarWidget_(d)); |
711 | } | 766 | } |
712 | 767 | ||
713 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *item) { | 768 | static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, size_t itemIndex) { |
714 | setFocus_Widget(NULL); | 769 | setFocus_Widget(NULL); |
715 | switch (d->mode) { | 770 | switch (d->mode) { |
716 | case documentOutline_SidebarMode: { | 771 | case documentOutline_SidebarMode: { |
@@ -735,17 +790,19 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, const iSidebarItem *it | |||
735 | break; | 790 | break; |
736 | } | 791 | } |
737 | case identities_SidebarMode: { | 792 | case identities_SidebarMode: { |
738 | iGmIdentity *ident = hoverIdentity_SidebarWidget_(d); | 793 | d->contextItem = item; |
739 | if (ident) { | 794 | if (d->contextIndex != iInvalidPos) { |
740 | const iString *tabUrl = url_DocumentWidget(document_App()); | 795 | invalidateItem_ListWidget(d->list, d->contextIndex); |
741 | if (isUsedOn_GmIdentity(ident, tabUrl)) { | 796 | } |
742 | signOut_GmCerts(certs_App(), tabUrl); | 797 | d->contextIndex = itemIndex; |
743 | } | 798 | if (itemIndex < numItems_ListWidget(d->list)) { |
744 | else { | 799 | updateContextMenu_SidebarWidget_(d); |
745 | signIn_GmCerts(certs_App(), ident, tabUrl); | 800 | arrange_Widget(d->menu); |
746 | } | 801 | openMenu_Widget(d->menu, |
747 | updateItems_SidebarWidget_(d); | 802 | d->side == left_SideBarSide |
748 | updateMouseHover_ListWidget(d->list); | 803 | ? topRight_Rect(itemRect_ListWidget(d->list, itemIndex)) |
804 | : addX_I2(topLeft_Rect(itemRect_ListWidget(d->list, itemIndex)), | ||
805 | -width_Widget(d->menu))); | ||
749 | } | 806 | } |
750 | break; | 807 | break; |
751 | } | 808 | } |
@@ -848,6 +905,7 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c | |||
848 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); | 905 | isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); |
849 | postCommand_App("bookmarks.changed"); | 906 | postCommand_App("bookmarks.changed"); |
850 | } | 907 | } |
908 | setupSheetTransition_Mobile(editor, iFalse); | ||
851 | destroy_Widget(editor); | 909 | destroy_Widget(editor); |
852 | return iTrue; | 910 | return iTrue; |
853 | } | 911 | } |
@@ -878,7 +936,8 @@ static iBool handleSidebarCommand_SidebarWidget_(iSidebarWidget *d, const char * | |||
878 | if (arg_Command(cmd) && isVisible_Widget(w)) { | 936 | if (arg_Command(cmd) && isVisible_Widget(w)) { |
879 | return iTrue; | 937 | return iTrue; |
880 | } | 938 | } |
881 | const iBool isAnimated = argLabel_Command(cmd, "noanim") == 0 && | 939 | const iBool isAnimated = prefs_App()->uiAnimations && |
940 | argLabel_Command(cmd, "noanim") == 0 && | ||
882 | (deviceType_App() != phone_AppDeviceType); | 941 | (deviceType_App() != phone_AppDeviceType); |
883 | int visX = 0; | 942 | int visX = 0; |
884 | if (isVisible_Widget(w)) { | 943 | if (isVisible_Widget(w)) { |
@@ -1008,7 +1067,8 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1008 | return iTrue; | 1067 | return iTrue; |
1009 | } | 1068 | } |
1010 | else if (isCommand_Widget(w, ev, "list.clicked")) { | 1069 | else if (isCommand_Widget(w, ev, "list.clicked")) { |
1011 | itemClicked_SidebarWidget_(d, pointerLabel_Command(cmd, "item")); | 1070 | itemClicked_SidebarWidget_( |
1071 | d, pointerLabel_Command(cmd, "item"), argU32Label_Command(cmd, "arg")); | ||
1012 | return iTrue; | 1072 | return iTrue; |
1013 | } | 1073 | } |
1014 | else if (isCommand_Widget(w, ev, "menu.closed")) { | 1074 | else if (isCommand_Widget(w, ev, "menu.closed")) { |
@@ -1190,23 +1250,17 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1190 | } | 1250 | } |
1191 | else if (arg_Command(cmd)) { | 1251 | else if (arg_Command(cmd)) { |
1192 | signIn_GmCerts(certs_App(), ident, tabUrl); | 1252 | signIn_GmCerts(certs_App(), ident, tabUrl); |
1253 | postCommand_App("navigate.reload"); | ||
1193 | } | 1254 | } |
1194 | else { | 1255 | else { |
1195 | signOut_GmCerts(certs_App(), tabUrl); | 1256 | signOut_GmCerts(certs_App(), tabUrl); |
1257 | postCommand_App("navigate.reload"); | ||
1196 | } | 1258 | } |
1197 | saveIdentities_GmCerts(certs_App()); | 1259 | saveIdentities_GmCerts(certs_App()); |
1198 | updateItems_SidebarWidget_(d); | 1260 | updateItems_SidebarWidget_(d); |
1199 | } | 1261 | } |
1200 | return iTrue; | 1262 | return iTrue; |
1201 | } | 1263 | } |
1202 | else if (isCommand_Widget(w, ev, "ident.showuse")) { | ||
1203 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1204 | if (ident) { | ||
1205 | makeSimpleMessage_Widget(uiHeading_ColorEscape "${heading.ident.use}", | ||
1206 | cstrCollect_String(joinCStr_StringSet(ident->useUrls, "\n"))); | ||
1207 | } | ||
1208 | return iTrue; | ||
1209 | } | ||
1210 | else if (isCommand_Widget(w, ev, "ident.edit")) { | 1264 | else if (isCommand_Widget(w, ev, "ident.edit")) { |
1211 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | 1265 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); |
1212 | if (ident) { | 1266 | if (ident) { |
@@ -1228,6 +1282,20 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1228 | } | 1282 | } |
1229 | return iTrue; | 1283 | return iTrue; |
1230 | } | 1284 | } |
1285 | else if (isCommand_Widget(w, ev, "ident.export")) { | ||
1286 | const iGmIdentity *ident = menuIdentity_SidebarWidget_(d); | ||
1287 | if (ident) { | ||
1288 | iString *pem = collect_String(pem_TlsCertificate(ident->cert)); | ||
1289 | append_String(pem, collect_String(privateKeyPem_TlsCertificate(ident->cert))); | ||
1290 | iDocumentWidget *expTab = newTab_App(NULL, iTrue); | ||
1291 | setUrlAndSource_DocumentWidget( | ||
1292 | expTab, | ||
1293 | collectNewFormat_String("file:%s.pem", cstr_String(name_GmIdentity(ident))), | ||
1294 | collectNewCStr_String("text/plain"), | ||
1295 | utf8_String(pem)); | ||
1296 | } | ||
1297 | return iTrue; | ||
1298 | } | ||
1231 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { | 1299 | else if (isCommand_Widget(w, ev, "ident.setnotes")) { |
1232 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); | 1300 | iGmIdentity *ident = pointerLabel_Command(cmd, "ident"); |
1233 | if (ident) { | 1301 | if (ident) { |
@@ -1336,20 +1404,21 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1336 | d->contextIndex = iInvalidPos; | 1404 | d->contextIndex = iInvalidPos; |
1337 | } | 1405 | } |
1338 | } | 1406 | } |
1339 | if (d->menu && ev->type == SDL_MOUSEBUTTONDOWN) { | 1407 | if ((d->menu || d->mode == identities_SidebarMode )&& ev->type == SDL_MOUSEBUTTONDOWN) { |
1340 | if (ev->button.button == SDL_BUTTON_RIGHT) { | 1408 | if (ev->button.button == SDL_BUTTON_RIGHT) { |
1341 | d->contextItem = NULL; | 1409 | d->contextItem = NULL; |
1342 | if (!isVisible_Widget(d->menu)) { | 1410 | if (!isVisible_Widget(d->menu)) { |
1343 | updateMouseHover_ListWidget(d->list); | 1411 | updateMouseHover_ListWidget(d->list); |
1344 | } | 1412 | } |
1345 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { | 1413 | if (constHoverItem_ListWidget(d->list) || isVisible_Widget(d->menu)) { |
1346 | d->contextItem = hoverItem_ListWidget(d->list); | 1414 | d->contextItem = hoverItem_ListWidget(d->list); |
1347 | /* Context is drawn in hover state. */ | 1415 | /* Context is drawn in hover state. */ |
1348 | if (d->contextIndex != iInvalidPos) { | 1416 | if (d->contextIndex != iInvalidPos) { |
1349 | invalidateItem_ListWidget(d->list, d->contextIndex); | 1417 | invalidateItem_ListWidget(d->list, d->contextIndex); |
1350 | } | 1418 | } |
1351 | d->contextIndex = hoverItemIndex_ListWidget(d->list); | 1419 | d->contextIndex = hoverItemIndex_ListWidget(d->list); |
1352 | /* Update menu items. */ | 1420 | /* Update menu items. */ |
1421 | updateContextMenu_SidebarWidget_(d); | ||
1353 | /* TODO: Some callback-based mechanism would be nice for updating menus right | 1422 | /* TODO: Some callback-based mechanism would be nice for updating menus right |
1354 | before they open? */ | 1423 | before they open? */ |
1355 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { | 1424 | if (d->mode == bookmarks_SidebarMode && d->contextItem) { |
@@ -1407,11 +1476,13 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1407 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || | 1476 | (!cmdClear && cmdUse && isUsedOn_GmIdentity(ident, docUrl)) || |
1408 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); | 1477 | (!cmdClear && !cmdUse && !isUsedOn_GmIdentity(ident, docUrl))); |
1409 | } | 1478 | } |
1479 | /* | ||
1410 | else if (equal_Command(cmdItem, "ident.showuse")) { | 1480 | else if (equal_Command(cmdItem, "ident.showuse")) { |
1411 | setFlags_Widget(as_Widget(menuItem), | 1481 | setFlags_Widget(as_Widget(menuItem), |
1412 | disabled_WidgetFlag, | 1482 | disabled_WidgetFlag, |
1413 | !isUsed_GmIdentity(ident)); | 1483 | !isUsed_GmIdentity(ident)); |
1414 | } | 1484 | } |
1485 | */ | ||
1415 | } | 1486 | } |
1416 | } | 1487 | } |
1417 | } | 1488 | } |
@@ -1423,10 +1494,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev) | |||
1423 | const int kmods = keyMods_Sym(ev->key.keysym.mod); | 1494 | const int kmods = keyMods_Sym(ev->key.keysym.mod); |
1424 | /* Hide the sidebar when Escape is pressed. */ | 1495 | /* Hide the sidebar when Escape is pressed. */ |
1425 | if (kmods == 0 && key == SDLK_ESCAPE && isVisible_Widget(d)) { | 1496 | if (kmods == 0 && key == SDLK_ESCAPE && isVisible_Widget(d)) { |
1426 | setFlags_Widget(w, hidden_WidgetFlag, iTrue); | 1497 | postCommand_Widget(d, "%s.toggle", cstr_String(id_Widget(w))); |
1427 | arrange_Widget(w->parent); | ||
1428 | updateSize_DocumentWidget(document_App()); | ||
1429 | refresh_Widget(w->parent); | ||
1430 | return iTrue; | 1498 | return iTrue; |
1431 | } | 1499 | } |
1432 | } | 1500 | } |
@@ -1460,7 +1528,8 @@ static void draw_SidebarWidget_(const iSidebarWidget *d) { | |||
1460 | const iRect bounds = bounds_Widget(w); | 1528 | const iRect bounds = bounds_Widget(w); |
1461 | iPaint p; | 1529 | iPaint p; |
1462 | init_Paint(&p); | 1530 | init_Paint(&p); |
1463 | if (flags_Widget(w) & visualOffset_WidgetFlag && isVisible_Widget(w)) { | 1531 | if (flags_Widget(w) & visualOffset_WidgetFlag && |
1532 | flags_Widget(w) & horizontalOffset_WidgetFlag && isVisible_Widget(w)) { | ||
1464 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); | 1533 | fillRect_Paint(&p, boundsWithoutVisualOffset_Widget(w), tmBackground_ColorId); |
1465 | } | 1534 | } |
1466 | draw_Widget(w); | 1535 | draw_Widget(w); |
@@ -1493,6 +1562,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1493 | const int itemHeight = height_Rect(itemRect); | 1562 | const int itemHeight = height_Rect(itemRect); |
1494 | const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) | 1563 | const int iconColor = isHover ? (isPressing ? uiTextPressed_ColorId : uiIconHover_ColorId) |
1495 | : uiIcon_ColorId; | 1564 | : uiIcon_ColorId; |
1565 | const int altIconColor = isPressing ? uiTextPressed_ColorId : uiTextCaution_ColorId; | ||
1496 | const int font = sidebar->itemFonts[d->isBold ? 1 : 0]; | 1566 | const int font = sidebar->itemFonts[d->isBold ? 1 : 0]; |
1497 | int bg = uiBackgroundSidebar_ColorId; | 1567 | int bg = uiBackgroundSidebar_ColorId; |
1498 | if (isHover) { | 1568 | if (isHover) { |
@@ -1611,7 +1681,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1611 | : uiText_ColorId; | 1681 | : uiText_ColorId; |
1612 | iString str; | 1682 | iString str; |
1613 | init_String(&str); | 1683 | init_String(&str); |
1614 | appendChar_String(&str, d->icon ? d->icon : 0x1f588); | 1684 | appendChar_String(&str, d->icon ? d->icon : 0x1f588); |
1615 | const iRect iconArea = { addX_I2(pos, gap_UI), | 1685 | const iRect iconArea = { addX_I2(pos, gap_UI), |
1616 | init_I2(1.75f * lineHeight_Text(font), itemHeight) }; | 1686 | init_I2(1.75f * lineHeight_Text(font), itemHeight) }; |
1617 | drawCentered_Text(font, | 1687 | drawCentered_Text(font, |
@@ -1625,9 +1695,13 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1625 | deinit_String(&str); | 1695 | deinit_String(&str); |
1626 | const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2); | 1696 | const iInt2 textPos = addY_I2(topRight_Rect(iconArea), (itemHeight - lineHeight_Text(font)) / 2); |
1627 | drawRange_Text(font, textPos, fg, range_String(&d->label)); | 1697 | drawRange_Text(font, textPos, fg, range_String(&d->label)); |
1698 | const int metaFont = default_FontId; | ||
1699 | const int metaIconWidth = 4.5f * gap_UI; | ||
1628 | const iInt2 metaPos = | 1700 | const iInt2 metaPos = |
1629 | init_I2(right_Rect(itemRect) - advanceRange_Text(font, range_String(&d->meta)).x - | 1701 | init_I2(right_Rect(itemRect) - |
1630 | 2 * gap_UI - (scrollBarWidth ? scrollBarWidth - gap_UI : 0), | 1702 | length_String(&d->meta) * |
1703 | metaIconWidth | ||
1704 | - 2 * gap_UI - (blankWidth ? blankWidth - 1.5f * gap_UI : (gap_UI / 2)), | ||
1631 | textPos.y); | 1705 | textPos.y); |
1632 | fillRect_Paint(p, | 1706 | fillRect_Paint(p, |
1633 | init_Rect(metaPos.x, | 1707 | init_Rect(metaPos.x, |
@@ -1635,10 +1709,22 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1635 | right_Rect(itemRect) - metaPos.x, | 1709 | right_Rect(itemRect) - metaPos.x, |
1636 | height_Rect(itemRect)), | 1710 | height_Rect(itemRect)), |
1637 | bg); | 1711 | bg); |
1638 | drawRange_Text(font, | 1712 | iInt2 mpos = metaPos; |
1639 | metaPos, | 1713 | iStringConstIterator iter; |
1640 | isHover && isPressing ? fg : uiTextCaution_ColorId, | 1714 | init_StringConstIterator(&iter, &d->meta); |
1641 | range_String(&d->meta)); | 1715 | iRangecc range = { cstr_String(&d->meta), iter.pos }; |
1716 | while (iter.value) { | ||
1717 | next_StringConstIterator(&iter); | ||
1718 | range.end = iter.pos; | ||
1719 | iRect iconArea = { mpos, init_I2(metaIconWidth, lineHeight_Text(metaFont)) }; | ||
1720 | iRect visBounds = visualBounds_Text(metaFont, range); | ||
1721 | drawRange_Text(metaFont, | ||
1722 | sub_I2(mid_Rect(iconArea), mid_Rect(visBounds)), | ||
1723 | isHover && isPressing ? fg : uiTextCaution_ColorId, | ||
1724 | range); | ||
1725 | mpos.x += metaIconWidth; | ||
1726 | range.start = range.end; | ||
1727 | } | ||
1642 | } | 1728 | } |
1643 | else if (sidebar->mode == history_SidebarMode) { | 1729 | else if (sidebar->mode == history_SidebarMode) { |
1644 | iBeginCollect(); | 1730 | iBeginCollect(); |
@@ -1683,6 +1769,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1683 | else if (sidebar->mode == identities_SidebarMode) { | 1769 | else if (sidebar->mode == identities_SidebarMode) { |
1684 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) | 1770 | const int fg = isHover ? (isPressing ? uiTextPressed_ColorId : uiTextFramelessHover_ColorId) |
1685 | : uiTextStrong_ColorId; | 1771 | : uiTextStrong_ColorId; |
1772 | const iBool isUsedOnDomain = (d->indent != 0); | ||
1686 | iString icon; | 1773 | iString icon; |
1687 | initUnicodeN_String(&icon, &d->icon, 1); | 1774 | initUnicodeN_String(&icon, &d->icon, 1); |
1688 | iInt2 cPos = topLeft_Rect(itemRect); | 1775 | iInt2 cPos = topLeft_Rect(itemRect); |
@@ -1694,8 +1781,21 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect, | |||
1694 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId | 1781 | const int metaFg = isHover ? permanent_ColorId | (isPressing ? uiTextPressed_ColorId |
1695 | : uiTextFramelessHover_ColorId) | 1782 | : uiTextFramelessHover_ColorId) |
1696 | : uiTextDim_ColorId; | 1783 | : uiTextDim_ColorId; |
1697 | drawRange_Text( | 1784 | if (!d->listItem.isSelected && !isUsedOnDomain) { |
1698 | font, cPos, d->listItem.isSelected ? iconColor : metaFg, range_String(&icon)); | 1785 | /* Draw an outline of the icon. */ |
1786 | for (int off = 0; off < 4; ++off) { | ||
1787 | drawRange_Text(font, | ||
1788 | add_I2(cPos, init_I2(off % 2 == 0 ? -1 : 1, off / 2 == 0 ? -1 : 1)), | ||
1789 | metaFg, | ||
1790 | range_String(&icon)); | ||
1791 | } | ||
1792 | } | ||
1793 | drawRange_Text(font, | ||
1794 | cPos, | ||
1795 | d->listItem.isSelected ? iconColor | ||
1796 | : isUsedOnDomain ? altIconColor | ||
1797 | : uiBackgroundSidebar_ColorId, | ||
1798 | range_String(&icon)); | ||
1699 | deinit_String(&icon); | 1799 | deinit_String(&icon); |
1700 | drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font, | 1800 | drawRange_Text(d->listItem.isSelected ? sidebar->itemFonts[1] : font, |
1701 | add_I2(cPos, init_I2(indent, 0)), | 1801 | add_I2(cPos, init_I2(indent, 0)), |
diff --git a/src/ui/text.c b/src/ui/text.c index 889aa2e4..ffe08fca 100644 --- a/src/ui/text.c +++ b/src/ui/text.c | |||
@@ -32,6 +32,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ | |||
32 | 32 | ||
33 | #include <the_Foundation/array.h> | 33 | #include <the_Foundation/array.h> |
34 | #include <the_Foundation/file.h> | 34 | #include <the_Foundation/file.h> |
35 | #include <the_Foundation/fileinfo.h> | ||
35 | #include <the_Foundation/hash.h> | 36 | #include <the_Foundation/hash.h> |
36 | #include <the_Foundation/math.h> | 37 | #include <the_Foundation/math.h> |
37 | #include <the_Foundation/stringlist.h> | 38 | #include <the_Foundation/stringlist.h> |
@@ -124,10 +125,6 @@ struct Impl_Font { | |||
124 | iBool isMonospaced; | 125 | iBool isMonospaced; |
125 | iBool manualKernOnly; | 126 | iBool manualKernOnly; |
126 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ | 127 | enum iFontSize sizeId; /* used to look up different fonts of matching size */ |
127 | // enum iFontId | ||
128 | // enum iFontId japaneseFont; /* font to use for Japanese glyphs */ | ||
129 | // enum iFontId chineseFont; /* font to use for Simplified Chinese glyphs */ | ||
130 | // enum iFontId koreanFont; /* font to use for Korean glyphs */ | ||
131 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ | 128 | uint32_t indexTable[128 - 32]; /* quick ASCII lookup */ |
132 | }; | 129 | }; |
133 | 130 | ||
@@ -155,13 +152,17 @@ static void init_Font(iFont *d, const iBlock *data, int height, float scale, | |||
155 | d->xScale *= floorf(advance) / advance; | 152 | d->xScale *= floorf(advance) / advance; |
156 | } | 153 | } |
157 | } | 154 | } |
158 | d->vertOffset = height * (1.0f - scale) / 2; | 155 | d->baseline = ascent * d->yScale; |
159 | d->baseline = ascent * d->yScale; | 156 | d->vertOffset = height * (1.0f - scale) / 2; |
160 | d->sizeId = sizeId; | 157 | /* Custom tweaks. */ |
161 | // d->symbolsFont = symbolsFont; | 158 | if (data == &fontNotoSansSymbolsRegular_Embedded || |
162 | // d->japaneseFont = regularJapanese_FontId; | 159 | data == &fontNotoSansSymbols2Regular_Embedded) { |
163 | // d->chineseFont = regularChinese_FontId; | 160 | d->vertOffset /= 2; |
164 | // d->koreanFont = regularKorean_FontId; | 161 | } |
162 | else if (data == &fontNotoEmojiRegular_Embedded) { | ||
163 | //d->vertOffset -= height / 30; | ||
164 | } | ||
165 | d->sizeId = sizeId; | ||
165 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); | 166 | memset(d->indexTable, 0xff, sizeof(d->indexTable)); |
166 | } | 167 | } |
167 | 168 | ||
@@ -215,7 +216,8 @@ struct Impl_Text { | |||
215 | iRegExp * ansiEscape; | 216 | iRegExp * ansiEscape; |
216 | }; | 217 | }; |
217 | 218 | ||
218 | static iText text_; | 219 | static iText text_; |
220 | static iBlock *userFont_; | ||
219 | 221 | ||
220 | static void initFonts_Text_(iText *d) { | 222 | static void initFonts_Text_(iText *d) { |
221 | const float textSize = fontSize_UI * d->contentFontSize; | 223 | const float textSize = fontSize_UI * d->contentFontSize; |
@@ -321,27 +323,31 @@ static void initFonts_Text_(iText *d) { | |||
321 | { &fontIosevkaTermExtended_Embedded, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, | 323 | { &fontIosevkaTermExtended_Embedded, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, |
322 | { &fontIosevkaTermExtended_Embedded, monoSize, 1.0f, contentMono_FontSize }, | 324 | { &fontIosevkaTermExtended_Embedded, monoSize, 1.0f, contentMono_FontSize }, |
323 | /* extra content fonts */ | 325 | /* extra content fonts */ |
324 | { &fontSourceSans3Regular_Embedded, textSize, scaling, contentRegular_FontSize }, | 326 | { &fontSourceSans3Regular_Embedded, textSize, scaling, contentRegular_FontSize }, |
325 | { &fontIosevkaTermExtended_Embedded, textSize, 0.866f, contentRegular_FontSize }, | 327 | { &fontSourceSans3Regular_Embedded, textSize * 0.80f, scaling, contentRegular_FontSize }, |
326 | /* symbols and scripts */ | 328 | /* symbols and scripts */ |
327 | #define DEFINE_FONT_SET(data) \ | 329 | #define DEFINE_FONT_SET(data, glyphScale) \ |
328 | { &data, uiSize, 1.0f, uiNormal_FontSize }, \ | 330 | { (data), uiSize, glyphScale, uiNormal_FontSize }, \ |
329 | { &data, uiSize * 1.125f, 1.0f, uiMedium_FontSize }, \ | 331 | { (data), uiSize * 1.125f, glyphScale, uiMedium_FontSize }, \ |
330 | { &data, uiSize * 1.333f, 1.0f, uiBig_FontSize }, \ | 332 | { (data), uiSize * 1.333f, glyphScale, uiBig_FontSize }, \ |
331 | { &data, uiSize * 1.666f, 1.0f, uiLarge_FontSize }, \ | 333 | { (data), uiSize * 1.666f, glyphScale, uiLarge_FontSize }, \ |
332 | { &data, textSize, 1.0f, contentRegular_FontSize }, \ | 334 | { (data), textSize, glyphScale, contentRegular_FontSize }, \ |
333 | { &data, textSize * 1.200f, 1.0f, contentMedium_FontSize }, \ | 335 | { (data), textSize * 1.200f, glyphScale, contentMedium_FontSize }, \ |
334 | { &data, textSize * 1.333f, 1.0f, contentBig_FontSize }, \ | 336 | { (data), textSize * 1.333f, glyphScale, contentBig_FontSize }, \ |
335 | { &data, textSize * 1.666f, 1.0f, contentLarge_FontSize }, \ | 337 | { (data), textSize * 1.666f, glyphScale, contentLarge_FontSize }, \ |
336 | { &data, textSize * 2.000f, 1.0f, contentHuge_FontSize }, \ | 338 | { (data), textSize * 2.000f, glyphScale, contentHuge_FontSize }, \ |
337 | { &data, smallMonoSize, 1.0f, contentMonoSmall_FontSize }, \ | 339 | { (data), smallMonoSize, glyphScale, contentMonoSmall_FontSize }, \ |
338 | { &data, monoSize, 1.0f, contentMono_FontSize } | 340 | { (data), monoSize, glyphScale, contentMono_FontSize } |
339 | DEFINE_FONT_SET(fontSymbola_Embedded), | 341 | DEFINE_FONT_SET(userFont_ ? userFont_ : &fontIosevkaTermExtended_Embedded, 1.0f), |
340 | DEFINE_FONT_SET(fontNotoEmojiRegular_Embedded), | 342 | DEFINE_FONT_SET(&fontIosevkaTermExtended_Embedded, 0.866f), |
341 | DEFINE_FONT_SET(fontNotoSansJPRegular_Embedded), | 343 | DEFINE_FONT_SET(&fontNotoSansSymbolsRegular_Embedded, 1.45f), |
342 | DEFINE_FONT_SET(fontNotoSansSCRegular_Embedded), | 344 | DEFINE_FONT_SET(&fontNotoSansSymbols2Regular_Embedded, 1.45f), |
343 | DEFINE_FONT_SET(fontNanumGothicRegular_Embedded), /* TODO: should use Noto Sans here, too */ | 345 | DEFINE_FONT_SET(&fontSmolEmojiRegular_Embedded, 1.0f), |
344 | DEFINE_FONT_SET(fontNotoSansArabicUIRegular_Embedded), | 346 | DEFINE_FONT_SET(&fontNotoEmojiRegular_Embedded, 1.10f), |
347 | DEFINE_FONT_SET(&fontNotoSansJPRegular_Embedded, 1.0f), | ||
348 | DEFINE_FONT_SET(&fontNotoSansSCRegular_Embedded, 1.0f), | ||
349 | DEFINE_FONT_SET(&fontNanumGothicRegular_Embedded, 1.0f), /* TODO: should use Noto Sans here, too */ | ||
350 | DEFINE_FONT_SET(&fontNotoSansArabicUIRegular_Embedded, 1.0f), | ||
345 | }; | 351 | }; |
346 | iForIndices(i, fontData) { | 352 | iForIndices(i, fontData) { |
347 | iFont *font = &d->fonts[i]; | 353 | iFont *font = &d->fonts[i]; |
@@ -401,8 +407,28 @@ static void deinitCache_Text_(iText *d) { | |||
401 | SDL_DestroyTexture(d->cache); | 407 | SDL_DestroyTexture(d->cache); |
402 | } | 408 | } |
403 | 409 | ||
410 | void loadUserFonts_Text(void) { | ||
411 | if (userFont_) { | ||
412 | delete_Block(userFont_); | ||
413 | userFont_ = NULL; | ||
414 | } | ||
415 | /* Load the system font. */ | ||
416 | const iPrefs *prefs = prefs_App(); | ||
417 | if (!isEmpty_String(&prefs->symbolFontPath)) { | ||
418 | iFile *f = new_File(&prefs->symbolFontPath); | ||
419 | if (open_File(f, readOnly_FileMode)) { | ||
420 | userFont_ = readAll_File(f); | ||
421 | } | ||
422 | else { | ||
423 | fprintf(stderr, "[Text] failed to open: %s\n", cstr_String(&prefs->symbolFontPath)); | ||
424 | } | ||
425 | iRelease(f); | ||
426 | } | ||
427 | } | ||
428 | |||
404 | void init_Text(SDL_Renderer *render) { | 429 | void init_Text(SDL_Renderer *render) { |
405 | iText *d = &text_; | 430 | iText *d = &text_; |
431 | loadUserFonts_Text(); | ||
406 | d->contentFont = nunito_TextFont; | 432 | d->contentFont = nunito_TextFont; |
407 | d->headingFont = nunito_TextFont; | 433 | d->headingFont = nunito_TextFont; |
408 | d->contentFontSize = contentScale_Text_; | 434 | d->contentFontSize = contentScale_Text_; |
@@ -542,14 +568,36 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) { | |||
542 | } | 568 | } |
543 | 569 | ||
544 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | 570 | iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { |
571 | if (isVariationSelector_Char(ch)) { | ||
572 | return d; | ||
573 | } | ||
574 | /* Smol Emoji overrides all other fonts. */ | ||
575 | if (ch != 0x20) { | ||
576 | iFont *smol = font_Text_(smolEmoji_FontId + d->sizeId); | ||
577 | if (smol != d && (*glyphIndex = glyphIndex_Font_(smol, ch)) != 0) { | ||
578 | return smol; | ||
579 | } | ||
580 | } | ||
581 | /* Manual exceptions. */ { | ||
582 | if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) { | ||
583 | d = font_Text_(iosevka_FontId + d->sizeId); | ||
584 | *glyphIndex = glyphIndex_Font_(d, ch); | ||
585 | return d; | ||
586 | } | ||
587 | } | ||
545 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { | 588 | if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { |
546 | return d; | 589 | return d; |
547 | } | 590 | } |
548 | /* Not defined in current font, try Noto Emoji (for selected characters). */ | 591 | const int fallbacks[] = { |
549 | if ((ch >= 0x1f300 && ch < 0x1f600) || (ch >= 0x1f680 && ch <= 0x1f6c5)) { | 592 | notoEmoji_FontId, |
550 | iFont *emoji = font_Text_(emoji_FontId + d->sizeId); | 593 | symbols2_FontId, |
551 | if (emoji != d && (*glyphIndex = glyphIndex_Font_(emoji, ch)) != 0) { | 594 | symbols_FontId |
552 | return emoji; | 595 | }; |
596 | /* First fallback is Smol Emoji. */ | ||
597 | iForIndices(i, fallbacks) { | ||
598 | iFont *fallback = font_Text_(fallbacks[i] + d->sizeId); | ||
599 | if (fallback != d && (*glyphIndex = glyphIndex_Font_(fallback, ch)) != 0) { | ||
600 | return fallback; | ||
553 | } | 601 | } |
554 | } | 602 | } |
555 | /* Try Simplified Chinese. */ | 603 | /* Try Simplified Chinese. */ |
@@ -584,17 +632,25 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) { | |||
584 | /* White up arrow is used for the Shift key on macOS. Symbola's glyph is not a great | 632 | /* White up arrow is used for the Shift key on macOS. Symbola's glyph is not a great |
585 | match to the other text, so use the UI font instead. */ | 633 | match to the other text, so use the UI font instead. */ |
586 | if ((ch == 0x2318 || ch == 0x21e7) && d == font_Text_(regular_FontId)) { | 634 | if ((ch == 0x2318 || ch == 0x21e7) && d == font_Text_(regular_FontId)) { |
587 | *glyphIndex = glyphIndex_Font_(d = font_Text_(defaultContentSized_FontId), ch); | 635 | *glyphIndex = glyphIndex_Font_(d = font_Text_(defaultContentRegular_FontId), ch); |
588 | return d; | 636 | return d; |
589 | } | 637 | } |
590 | #endif | 638 | #endif |
591 | /* Fall back to Symbola for anything else. */ | 639 | /* User's symbols font. */ { |
592 | iFont *font = font_Text_(symbols_FontId + d->sizeId); | 640 | iFont *sys = font_Text_(userSymbols_FontId + d->sizeId); |
593 | *glyphIndex = glyphIndex_Font_(font, ch); | 641 | if (sys != d && (*glyphIndex = glyphIndex_Font_(sys, ch)) != 0) { |
594 | // if (!*glyphIndex) { | 642 | return sys; |
595 | // fprintf(stderr, "failed to find %08x (%lc)\n", ch, ch); fflush(stderr); | 643 | } |
596 | // } | 644 | } |
597 | return font; | 645 | /* Final fallback. */ |
646 | iFont *font = font_Text_(iosevka_FontId + d->sizeId); | ||
647 | if (d != font) { | ||
648 | *glyphIndex = glyphIndex_Font_(font, ch); | ||
649 | } | ||
650 | if (!*glyphIndex) { | ||
651 | fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); | ||
652 | } | ||
653 | return d; | ||
598 | } | 654 | } |
599 | 655 | ||
600 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { | 656 | static iGlyph *glyph_Font_(iFont *d, iChar ch) { |
@@ -1190,7 +1246,7 @@ iInt2 advanceN_Text(int fontId, const char *text, size_t n) { | |||
1190 | return init_I2(advance, lineHeight_Text(fontId)); | 1246 | return init_I2(advance, lineHeight_Text(fontId)); |
1191 | } | 1247 | } |
1192 | 1248 | ||
1193 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | 1249 | static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { |
1194 | iText *d = &text_; | 1250 | iText *d = &text_; |
1195 | iFont *font = font_Text_(fontId); | 1251 | iFont *font = font_Text_(fontId); |
1196 | const iColor clr = get_Color(color & mask_ColorId); | 1252 | const iColor clr = get_Color(color & mask_ColorId); |
@@ -1201,11 +1257,16 @@ static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, i | |||
1201 | (color & fillBackground_ColorId ? fillBackground_RunMode : 0) | | 1257 | (color & fillBackground_ColorId ? fillBackground_RunMode : 0) | |
1202 | runFlagsFromId_(fontId), | 1258 | runFlagsFromId_(fontId), |
1203 | .text = text, | 1259 | .text = text, |
1260 | .maxLen = maxLen, | ||
1204 | .pos = pos, | 1261 | .pos = pos, |
1205 | .xposLayoutBound = xposBound, | 1262 | .xposLayoutBound = xposBound, |
1206 | .color = color & mask_ColorId }); | 1263 | .color = color & mask_ColorId }); |
1207 | } | 1264 | } |
1208 | 1265 | ||
1266 | static void drawBounded_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text) { | ||
1267 | drawBoundedN_Text_(fontId, pos, xposBound, color, text, 0); | ||
1268 | } | ||
1269 | |||
1209 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { | 1270 | static void draw_Text_(int fontId, iInt2 pos, int color, iRangecc text) { |
1210 | drawBounded_Text_(fontId, pos, 0, color, text); | 1271 | drawBounded_Text_(fontId, pos, 0, color, text); |
1211 | } | 1272 | } |
@@ -1248,6 +1309,10 @@ void drawRange_Text(int fontId, iInt2 pos, int color, iRangecc text) { | |||
1248 | draw_Text_(fontId, pos, color, text); | 1309 | draw_Text_(fontId, pos, color, text); |
1249 | } | 1310 | } |
1250 | 1311 | ||
1312 | void drawRangeN_Text(int fontId, iInt2 pos, int color, iRangecc text, size_t maxChars) { | ||
1313 | drawBoundedN_Text_(fontId, pos, 0, color, text, maxChars); | ||
1314 | } | ||
1315 | |||
1251 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { | 1316 | iInt2 advanceWrapRange_Text(int fontId, int maxWidth, iRangecc text) { |
1252 | iInt2 size = zero_I2(); | 1317 | iInt2 size = zero_I2(); |
1253 | const char *endp; | 1318 | const char *endp; |
@@ -1285,13 +1350,16 @@ void drawCentered_Text(int fontId, iRect rect, iBool alignVisual, int color, con | |||
1285 | vprintf_Block(&chars, format, args); | 1350 | vprintf_Block(&chars, format, args); |
1286 | va_end(args); | 1351 | va_end(args); |
1287 | } | 1352 | } |
1288 | const iRangecc text = range_Block(&chars); | 1353 | drawCenteredRange_Text(fontId, rect, alignVisual, color, range_Block(&chars)); |
1289 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) | 1354 | deinit_Block(&chars); |
1355 | } | ||
1356 | |||
1357 | void drawCenteredRange_Text(int fontId, iRect rect, iBool alignVisual, int color, iRangecc text) { | ||
1358 | iRect textBounds = alignVisual ? visualBounds_Text(fontId, text) | ||
1290 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; | 1359 | : (iRect){ zero_I2(), advanceRange_Text(fontId, text) }; |
1291 | textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds)); | 1360 | textBounds.pos = sub_I2(mid_Rect(rect), mid_Rect(textBounds)); |
1292 | textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */ | 1361 | textBounds.pos.x = iMax(textBounds.pos.x, left_Rect(rect)); /* keep left edge visible */ |
1293 | draw_Text_(fontId, textBounds.pos, color, text); | 1362 | draw_Text_(fontId, textBounds.pos, color, text); |
1294 | deinit_Block(&chars); | ||
1295 | } | 1363 | } |
1296 | 1364 | ||
1297 | SDL_Texture *glyphCache_Text(void) { | 1365 | SDL_Texture *glyphCache_Text(void) { |
diff --git a/src/ui/text.h b/src/ui/text.h index 044ddd32..2f2bcf3a 100644 --- a/src/ui/text.h +++ b/src/ui/text.h | |||
@@ -67,12 +67,16 @@ enum iFontId { | |||
67 | monospaceSmall_FontId, | 67 | monospaceSmall_FontId, |
68 | monospace_FontId, | 68 | monospace_FontId, |
69 | /* extra content fonts */ | 69 | /* extra content fonts */ |
70 | defaultContentSized_FontId, /* UI font but sized to regular_FontId */ | 70 | defaultContentRegular_FontId, /* UI font but sized to regular_FontId */ |
71 | regularMonospace_FontId, | 71 | defaultContentSmall_FontId, /* UI font but sized smaller */ |
72 | /* symbols and scripts */ | 72 | /* symbols and scripts */ |
73 | symbols_FontId, | 73 | userSymbols_FontId, |
74 | emoji_FontId = symbols_FontId + max_FontSize, | 74 | iosevka_FontId = userSymbols_FontId + max_FontSize, |
75 | japanese_FontId = emoji_FontId + max_FontSize, | 75 | symbols_FontId = iosevka_FontId + max_FontSize, |
76 | symbols2_FontId = symbols_FontId + max_FontSize, | ||
77 | smolEmoji_FontId = symbols2_FontId + max_FontSize, | ||
78 | notoEmoji_FontId = smolEmoji_FontId + max_FontSize, | ||
79 | japanese_FontId = notoEmoji_FontId + max_FontSize, | ||
76 | chineseSimplified_FontId = japanese_FontId + max_FontSize, | 80 | chineseSimplified_FontId = japanese_FontId + max_FontSize, |
77 | korean_FontId = chineseSimplified_FontId + max_FontSize, | 81 | korean_FontId = chineseSimplified_FontId + max_FontSize, |
78 | arabic_FontId = korean_FontId + max_FontSize, | 82 | arabic_FontId = korean_FontId + max_FontSize, |
@@ -91,7 +95,7 @@ enum iFontId { | |||
91 | uiInput_FontId = defaultMedium_FontId, | 95 | uiInput_FontId = defaultMedium_FontId, |
92 | uiContent_FontId = defaultMedium_FontId, | 96 | uiContent_FontId = defaultMedium_FontId, |
93 | uiContentBold_FontId = defaultMediumBold_FontId, | 97 | uiContentBold_FontId = defaultMediumBold_FontId, |
94 | uiContentSymbols_FontId = symbols_FontId + uiMedium_FontSize, | 98 | uiContentSymbols_FontId = symbols_FontId + uiMedium_FontSize, |
95 | /* Document fonts: */ | 99 | /* Document fonts: */ |
96 | paragraph_FontId = regular_FontId, | 100 | paragraph_FontId = regular_FontId, |
97 | firstParagraph_FontId = medium_FontId, | 101 | firstParagraph_FontId = medium_FontId, |
@@ -102,6 +106,7 @@ enum iFontId { | |||
102 | heading2_FontId = largeBold_FontId, | 106 | heading2_FontId = largeBold_FontId, |
103 | heading3_FontId = big_FontId, | 107 | heading3_FontId = big_FontId, |
104 | banner_FontId = largeLight_FontId, | 108 | banner_FontId = largeLight_FontId, |
109 | regularMonospace_FontId = iosevka_FontId + contentRegular_FontSize | ||
105 | }; | 110 | }; |
106 | 111 | ||
107 | iLocalDef iBool isJapanese_FontId(enum iFontId id) { | 112 | iLocalDef iBool isJapanese_FontId(enum iFontId id) { |
@@ -124,6 +129,8 @@ extern int gap_Text; /* affected by content font size */ | |||
124 | void init_Text (SDL_Renderer *); | 129 | void init_Text (SDL_Renderer *); |
125 | void deinit_Text (void); | 130 | void deinit_Text (void); |
126 | 131 | ||
132 | void loadUserFonts_Text (void); /* based on Prefs */ | ||
133 | |||
127 | void setContentFont_Text (enum iTextFont font); | 134 | void setContentFont_Text (enum iTextFont font); |
128 | void setHeadingFont_Text (enum iTextFont font); | 135 | void setHeadingFont_Text (enum iTextFont font); |
129 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ | 136 | void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ |
@@ -151,13 +158,15 @@ void setOpacity_Text (float opacity); | |||
151 | 158 | ||
152 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ | 159 | void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ |
153 | 160 | ||
154 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); | 161 | void draw_Text (int fontId, iInt2 pos, int color, const char *text, ...); |
155 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); | 162 | void drawAlign_Text (int fontId, iInt2 pos, int color, enum iAlignment align, const char *text, ...); |
156 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); | 163 | void drawCentered_Text (int fontId, iRect rect, iBool alignVisual, int color, const char *text, ...); |
157 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); | 164 | void drawCenteredRange_Text (int fontId, iRect rect, iBool alignVisual, int color, iRangecc text); |
158 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); | 165 | void drawString_Text (int fontId, iInt2 pos, int color, const iString *text); |
159 | void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */ | 166 | void drawRange_Text (int fontId, iInt2 pos, int color, iRangecc text); |
160 | int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ | 167 | void drawRangeN_Text (int fontId, iInt2 pos, int color, iRangecc text, size_t maxLen); |
168 | void drawBoundRange_Text (int fontId, iInt2 pos, int boundWidth, int color, iRangecc text); /* bound does not wrap */ | ||
169 | int drawWrapRange_Text (int fontId, iInt2 pos, int maxWidth, int color, iRangecc text); /* returns new Y */ | ||
161 | 170 | ||
162 | SDL_Texture * glyphCache_Text (void); | 171 | SDL_Texture * glyphCache_Text (void); |
163 | 172 | ||
diff --git a/src/ui/translation.c b/src/ui/translation.c index 4102fcb9..88edc48b 100644 --- a/src/ui/translation.c +++ b/src/ui/translation.c | |||
@@ -460,6 +460,7 @@ iBool handleCommand_Translation(iTranslation *d, const char *cmd) { | |||
460 | if (equalWidget_Command(cmd, w, "translation.finished")) { | 460 | if (equalWidget_Command(cmd, w, "translation.finished")) { |
461 | if (!isFinished_Translation(d)) { | 461 | if (!isFinished_Translation(d)) { |
462 | if (processResult_Translation_(d)) { | 462 | if (processResult_Translation_(d)) { |
463 | setupSheetTransition_Mobile(d->dlg, iFalse); | ||
463 | destroy_Widget(d->dlg); | 464 | destroy_Widget(d->dlg); |
464 | d->dlg = NULL; | 465 | d->dlg = NULL; |
465 | } | 466 | } |
@@ -472,10 +473,11 @@ iBool handleCommand_Translation(iTranslation *d, const char *cmd) { | |||
472 | updateTextCStr_LabelWidget( | 473 | updateTextCStr_LabelWidget( |
473 | findMenuItem_Widget(findChild_Widget(d->dlg, "dialogbuttons"), | 474 | findMenuItem_Widget(findChild_Widget(d->dlg, "dialogbuttons"), |
474 | "translation.cancel"), | 475 | "translation.cancel"), |
475 | "${dismiss}"); | 476 | "${close}"); |
476 | cancel_TlsRequest(d->request); | 477 | cancel_TlsRequest(d->request); |
477 | } | 478 | } |
478 | else { | 479 | else { |
480 | setupSheetTransition_Mobile(d->dlg, iFalse); | ||
479 | destroy_Widget(d->dlg); | 481 | destroy_Widget(d->dlg); |
480 | d->dlg = NULL; | 482 | d->dlg = NULL; |
481 | } | 483 | } |
diff --git a/src/ui/util.c b/src/ui/util.c index 4b35f8f7..c4fb8886 100644 --- a/src/ui/util.c +++ b/src/ui/util.c | |||
@@ -1176,6 +1176,7 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1176 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); | 1176 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); |
1177 | setId_Widget(dlg, ""); /* no further commands to emit */ | 1177 | setId_Widget(dlg, ""); /* no further commands to emit */ |
1178 | } | 1178 | } |
1179 | setupSheetTransition_Mobile(dlg, iFalse); | ||
1179 | destroy_Widget(dlg); | 1180 | destroy_Widget(dlg); |
1180 | return iTrue; | 1181 | return iTrue; |
1181 | } | 1182 | } |
@@ -1184,11 +1185,13 @@ iBool valueInputHandler_(iWidget *dlg, const char *cmd) { | |||
1184 | else if (equal_Command(cmd, "cancel")) { | 1185 | else if (equal_Command(cmd, "cancel")) { |
1185 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); | 1186 | postCommandf_App("valueinput.cancelled id:%s", cstr_String(id_Widget(dlg))); |
1186 | setId_Widget(dlg, ""); /* no further commands to emit */ | 1187 | setId_Widget(dlg, ""); /* no further commands to emit */ |
1188 | setupSheetTransition_Mobile(dlg, iFalse); | ||
1187 | destroy_Widget(dlg); | 1189 | destroy_Widget(dlg); |
1188 | return iTrue; | 1190 | return iTrue; |
1189 | } | 1191 | } |
1190 | else if (equal_Command(cmd, "valueinput.accept")) { | 1192 | else if (equal_Command(cmd, "valueinput.accept")) { |
1191 | acceptValueInput_(dlg); | 1193 | acceptValueInput_(dlg); |
1194 | setupSheetTransition_Mobile(dlg, iFalse); | ||
1192 | destroy_Widget(dlg); | 1195 | destroy_Widget(dlg); |
1193 | return iTrue; | 1196 | return iTrue; |
1194 | } | 1197 | } |
@@ -1324,6 +1327,7 @@ static iBool messageHandler_(iWidget *msg, const char *cmd) { | |||
1324 | equal_Command(cmd, "scrollbar.fade") || | 1327 | equal_Command(cmd, "scrollbar.fade") || |
1325 | equal_Command(cmd, "widget.overflow") || | 1328 | equal_Command(cmd, "widget.overflow") || |
1326 | startsWith_CStr(cmd, "window."))) { | 1329 | startsWith_CStr(cmd, "window."))) { |
1330 | setupSheetTransition_Mobile(msg, iFalse); | ||
1327 | destroy_Widget(msg); | 1331 | destroy_Widget(msg); |
1328 | } | 1332 | } |
1329 | return iFalse; | 1333 | return iFalse; |
@@ -1521,6 +1525,7 @@ void updatePreferencesLayout_Widget(iWidget *prefs) { | |||
1521 | static const char *inputIds[] = { | 1525 | static const char *inputIds[] = { |
1522 | "prefs.searchurl", | 1526 | "prefs.searchurl", |
1523 | "prefs.downloads", | 1527 | "prefs.downloads", |
1528 | "prefs.userfont", | ||
1524 | "prefs.ca.file", | 1529 | "prefs.ca.file", |
1525 | "prefs.ca.path", | 1530 | "prefs.ca.path", |
1526 | "prefs.proxy.gemini", | 1531 | "prefs.proxy.gemini", |
@@ -1547,8 +1552,8 @@ void updatePreferencesLayout_Widget(iWidget *prefs) { | |||
1547 | } | 1552 | } |
1548 | } | 1553 | } |
1549 | 1554 | ||
1550 | static void addDialogInputWithHeading_(iWidget *headings, iWidget *values, const char *labelText, | 1555 | static void addDialogInputWithHeadingAndFlags_(iWidget *headings, iWidget *values, const char *labelText, |
1551 | const char *inputId, iInputWidget *input) { | 1556 | const char *inputId, iInputWidget *input, int64_t flags) { |
1552 | iLabelWidget *head = addChild_Widget(headings, iClob(makeHeading_Widget(labelText))); | 1557 | iLabelWidget *head = addChild_Widget(headings, iClob(makeHeading_Widget(labelText))); |
1553 | #if defined (iPlatformMobile) | 1558 | #if defined (iPlatformMobile) |
1554 | /* On mobile, inputs have 2 gaps of extra padding. */ | 1559 | /* On mobile, inputs have 2 gaps of extra padding. */ |
@@ -1560,6 +1565,13 @@ static void addDialogInputWithHeading_(iWidget *headings, iWidget *values, const | |||
1560 | /* Ensure that the label has the same height as the input widget. */ | 1565 | /* Ensure that the label has the same height as the input widget. */ |
1561 | as_Widget(head)->sizeRef = as_Widget(input); | 1566 | as_Widget(head)->sizeRef = as_Widget(input); |
1562 | } | 1567 | } |
1568 | setFlags_Widget(as_Widget(head), flags, iTrue); | ||
1569 | setFlags_Widget(as_Widget(input), flags, iTrue); | ||
1570 | } | ||
1571 | |||
1572 | static void addDialogInputWithHeading_(iWidget *headings, iWidget *values, const char *labelText, | ||
1573 | const char *inputId, iInputWidget *input) { | ||
1574 | addDialogInputWithHeadingAndFlags_(headings, values, labelText, inputId, input, 0); | ||
1563 | } | 1575 | } |
1564 | 1576 | ||
1565 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, | 1577 | iInputWidget *addTwoColumnDialogInputField_Widget(iWidget *headings, iWidget *values, |
@@ -1686,6 +1698,8 @@ iWidget *makePreferences_Widget(void) { | |||
1686 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); | 1698 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.customframe}"))); |
1687 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); | 1699 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.customframe"))); |
1688 | #endif | 1700 | #endif |
1701 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.animate}"))); | ||
1702 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.animate"))); | ||
1689 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); | 1703 | makeTwoColumnHeading_("${heading.prefs.scrolling}", headings, values); |
1690 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.smoothscroll}"))); | 1704 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.smoothscroll}"))); |
1691 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll"))); | 1705 | addChild_Widget(values, iClob(makeToggle_Widget("prefs.smoothscroll"))); |
@@ -1775,6 +1789,7 @@ iWidget *makePreferences_Widget(void) { | |||
1775 | updateSize_LabelWidget((iLabelWidget *) tog); | 1789 | updateSize_LabelWidget((iLabelWidget *) tog); |
1776 | } | 1790 | } |
1777 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); | 1791 | addChildFlags_Widget(values, iClob(boldLink), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); |
1792 | addPrefsInputWithHeading_(headings, values, "prefs.userfont", iClob(new_InputWidget(0))); | ||
1778 | } | 1793 | } |
1779 | makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); | 1794 | makeTwoColumnHeading_("${heading.prefs.paragraph}", headings, values); |
1780 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); | 1795 | addChild_Widget(headings, iClob(makeHeading_Widget("${prefs.linewidth}"))); |
@@ -1834,7 +1849,7 @@ iWidget *makePreferences_Widget(void) { | |||
1834 | updatePreferencesLayout_Widget(dlg); | 1849 | updatePreferencesLayout_Widget(dlg); |
1835 | addChild_Widget(dlg, | 1850 | addChild_Widget(dlg, |
1836 | iClob(makeDialogButtons_Widget( | 1851 | iClob(makeDialogButtons_Widget( |
1837 | (iMenuItem[]){ { "${dismiss}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); | 1852 | (iMenuItem[]){ { "${close}", SDLK_ESCAPE, 0, "prefs.dismiss" } }, 1))); |
1838 | addChild_Widget(dlg->root->widget, iClob(dlg)); | 1853 | addChild_Widget(dlg->root->widget, iClob(dlg)); |
1839 | finalizeSheet_Mobile(dlg); | 1854 | finalizeSheet_Mobile(dlg); |
1840 | setupSheetTransition_Mobile(dlg, iTrue); | 1855 | setupSheetTransition_Mobile(dlg, iTrue); |
@@ -1908,6 +1923,7 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons | |||
1908 | } | 1923 | } |
1909 | postCommand_App("bookmarks.changed"); | 1924 | postCommand_App("bookmarks.changed"); |
1910 | } | 1925 | } |
1926 | setupSheetTransition_Mobile(editor, iFalse); | ||
1911 | destroy_Widget(editor); | 1927 | destroy_Widget(editor); |
1912 | return iTrue; | 1928 | return iTrue; |
1913 | } | 1929 | } |
@@ -1937,6 +1953,7 @@ iWidget *makeBookmarkCreation_Widget(const iString *url, const iString *title, i | |||
1937 | 1953 | ||
1938 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | 1954 | static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { |
1939 | if (equal_Command(cmd, "cancel")) { | 1955 | if (equal_Command(cmd, "cancel")) { |
1956 | setupSheetTransition_Mobile(dlg, iFalse); | ||
1940 | destroy_Widget(dlg); | 1957 | destroy_Widget(dlg); |
1941 | return iTrue; | 1958 | return iTrue; |
1942 | } | 1959 | } |
@@ -1971,6 +1988,7 @@ static iBool handleFeedSettingCommands_(iWidget *dlg, const char *cmd) { | |||
1971 | } | 1988 | } |
1972 | } | 1989 | } |
1973 | postCommand_App("bookmarks.changed"); | 1990 | postCommand_App("bookmarks.changed"); |
1991 | setupSheetTransition_Mobile(dlg, iFalse); | ||
1974 | destroy_Widget(dlg); | 1992 | destroy_Widget(dlg); |
1975 | return iTrue; | 1993 | return iTrue; |
1976 | } | 1994 | } |
@@ -2042,7 +2060,21 @@ iWidget *makeIdentityCreation_Widget(void) { | |||
2042 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 2060 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); |
2043 | iWidget *values = addChildFlags_Widget( | 2061 | iWidget *values = addChildFlags_Widget( |
2044 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); | 2062 | page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); |
2063 | setId_Widget(headings, "headings"); | ||
2064 | setId_Widget(values, "values"); | ||
2045 | iInputWidget *inputs[6]; | 2065 | iInputWidget *inputs[6]; |
2066 | /* Where will the new identity be active on? */ { | ||
2067 | addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.newident.scope}"))); | ||
2068 | const iMenuItem items[] = { | ||
2069 | { "${dlg.newident.scope.domain}", 0, 0, "ident.scope arg:0" }, | ||
2070 | { "${dlg.newident.scope.page}", 0, 0, "ident.scope arg:1" }, | ||
2071 | { "${dlg.newident.scope.none}", 0, 0, "ident.scope arg:2" }, | ||
2072 | }; | ||
2073 | setId_Widget(addChild_Widget(values, | ||
2074 | iClob(makeMenuButton_LabelWidget( | ||
2075 | items[0].label, items, iElemCount(items)))), | ||
2076 | "ident.scope"); | ||
2077 | } | ||
2046 | addDialogInputWithHeading_(headings, | 2078 | addDialogInputWithHeading_(headings, |
2047 | values, | 2079 | values, |
2048 | "${dlg.newident.until}", | 2080 | "${dlg.newident.until}", |
@@ -2059,32 +2091,35 @@ iWidget *makeIdentityCreation_Widget(void) { | |||
2059 | setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); | 2091 | setFlags_Widget(tmpGroup, arrangeSize_WidgetFlag | arrangeHorizontal_WidgetFlag, iTrue); |
2060 | addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); | 2092 | addChild_Widget(tmpGroup, iClob(makeToggle_Widget("ident.temp"))); |
2061 | setId_Widget( | 2093 | setId_Widget( |
2062 | addChildFlags_Widget( | 2094 | addChildFlags_Widget(tmpGroup, |
2063 | tmpGroup, | 2095 | iClob(new_LabelWidget(uiTextCaution_ColorEscape warning_Icon |
2064 | iClob(new_LabelWidget(uiTextCaution_ColorEscape "\u26a0 ${dlg.newident.notsaved}", NULL)), | 2096 | " ${dlg.newident.notsaved}", |
2065 | hidden_WidgetFlag | frameless_WidgetFlag), | 2097 | NULL)), |
2098 | hidden_WidgetFlag | frameless_WidgetFlag), | ||
2066 | "ident.temp.note"); | 2099 | "ident.temp.note"); |
2067 | addChild_Widget(values, iClob(tmpGroup)); | 2100 | addChild_Widget(values, iClob(tmpGroup)); |
2068 | } | 2101 | } |
2069 | addChild_Widget(headings, iClob(makePadding_Widget(gap_UI))); | 2102 | addChildFlags_Widget(headings, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); |
2070 | addChild_Widget(values, iClob(makePadding_Widget(gap_UI))); | 2103 | addChildFlags_Widget(values, iClob(makePadding_Widget(gap_UI)), collapse_WidgetFlag | hidden_WidgetFlag); |
2071 | addDialogInputWithHeading_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}"))); | 2104 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.email}", "ident.email", iClob(inputs[1] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); |
2072 | addDialogInputWithHeading_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}"))); | 2105 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.userid}", "ident.userid", iClob(inputs[2] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); |
2073 | addDialogInputWithHeading_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}"))); | 2106 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.domain}", "ident.domain", iClob(inputs[3] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); |
2074 | addDialogInputWithHeading_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}"))); | 2107 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.org}", "ident.org", iClob(inputs[4] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); |
2075 | addDialogInputWithHeading_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}"))); | 2108 | addDialogInputWithHeadingAndFlags_(headings, values, "${dlg.newident.country}", "ident.country", iClob(inputs[5] = newHint_InputWidget(0, "${hint.newident.optional}")), collapse_WidgetFlag | hidden_WidgetFlag); |
2076 | arrange_Widget(dlg); | 2109 | arrange_Widget(dlg); |
2077 | for (size_t i = 0; i < iElemCount(inputs); ++i) { | 2110 | for (size_t i = 0; i < iElemCount(inputs); ++i) { |
2078 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; | 2111 | as_Widget(inputs[i])->rect.size.x = 100 * gap_UI - headings->rect.size.x; |
2079 | } | 2112 | } |
2080 | addChild_Widget(dlg, | 2113 | addChild_Widget(dlg, |
2081 | iClob(makeDialogButtons_Widget( | 2114 | iClob(makeDialogButtons_Widget( |
2082 | (iMenuItem[]){ { "${cancel}", 0, 0, NULL }, | 2115 | (iMenuItem[]){ { "${dlg.newident.more}", 0, 0, "ident.showmore" }, |
2116 | { "---", 0, 0, NULL }, | ||
2117 | { "${cancel}", SDLK_ESCAPE, 0, "ident.cancel" }, | ||
2083 | { uiTextAction_ColorEscape "${dlg.newident.create}", | 2118 | { uiTextAction_ColorEscape "${dlg.newident.create}", |
2084 | SDLK_RETURN, | 2119 | SDLK_RETURN, |
2085 | KMOD_PRIMARY, | 2120 | KMOD_PRIMARY, |
2086 | "ident.accept" } }, | 2121 | "ident.accept" } }, |
2087 | 2))); | 2122 | 4))); |
2088 | addChild_Widget(get_Root()->widget, iClob(dlg)); | 2123 | addChild_Widget(get_Root()->widget, iClob(dlg)); |
2089 | finalizeSheet_Mobile(dlg); | 2124 | finalizeSheet_Mobile(dlg); |
2090 | return dlg; | 2125 | return dlg; |
diff --git a/src/ui/widget.c b/src/ui/widget.c index d31f7577..4eac7ecf 100644 --- a/src/ui/widget.c +++ b/src/ui/widget.c | |||
@@ -59,6 +59,7 @@ void init_Widget(iWidget *d) { | |||
59 | d->minSize = zero_I2(); | 59 | d->minSize = zero_I2(); |
60 | d->sizeRef = NULL; | 60 | d->sizeRef = NULL; |
61 | d->offsetRef = NULL; | 61 | d->offsetRef = NULL; |
62 | d->animOffsetRef = NULL; | ||
62 | d->bgColor = none_ColorId; | 63 | d->bgColor = none_ColorId; |
63 | d->frameColor = none_ColorId; | 64 | d->frameColor = none_ColorId; |
64 | init_Anim(&d->visualOffset, 0.0f); | 65 | init_Anim(&d->visualOffset, 0.0f); |
@@ -101,9 +102,6 @@ static void aboutToBeDestroyed_Widget_(iWidget *d) { | |||
101 | setFocus_Widget(NULL); | 102 | setFocus_Widget(NULL); |
102 | return; | 103 | return; |
103 | } | 104 | } |
104 | if (flags_Widget(d) & keepOnTop_WidgetFlag) { | ||
105 | removeOne_PtrArray(onTop_Root(d->root), d); | ||
106 | } | ||
107 | remove_Periodic(periodic_App(), d); | 105 | remove_Periodic(periodic_App(), d); |
108 | if (isHover_Widget(d)) { | 106 | if (isHover_Widget(d)) { |
109 | get_Window()->hover = NULL; | 107 | get_Window()->hover = NULL; |
@@ -771,6 +769,9 @@ static void applyVisualOffset_Widget_(const iWidget *d, iInt2 *pos) { | |||
771 | pos->y += off; | 769 | pos->y += off; |
772 | } | 770 | } |
773 | } | 771 | } |
772 | if (d->animOffsetRef) { | ||
773 | pos->y -= value_Anim(d->animOffsetRef); | ||
774 | } | ||
774 | if (d->flags & refChildrenOffset_WidgetFlag) { | 775 | if (d->flags & refChildrenOffset_WidgetFlag) { |
775 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { | 776 | iConstForEach(ObjectList, i, children_Widget(d->offsetRef)) { |
776 | const iWidget *child = i.object; | 777 | const iWidget *child = i.object; |
@@ -843,6 +844,12 @@ iBool containsExpanded_Widget(const iWidget *d, iInt2 windowCoord, int expand) { | |||
843 | addY_I2(d->rect.size, | 844 | addY_I2(d->rect.size, |
844 | d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0) | 845 | d->flags & drawBackgroundToBottom_WidgetFlag ? size_Root(d->root).y : 0) |
845 | }; | 846 | }; |
847 | /* Apply the animated offset. (Visual offsets don't affect interaction.) */ | ||
848 | for (const iWidget *w = d; w; w = w->parent) { | ||
849 | if (w->animOffsetRef) { | ||
850 | windowCoord.y += value_Anim(w->animOffsetRef); | ||
851 | } | ||
852 | } | ||
846 | return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds, | 853 | return contains_Rect(expand ? expanded_Rect(bounds, init1_I2(expand)) : bounds, |
847 | windowToInner_Widget(d, windowCoord)); | 854 | windowToInner_Widget(d, windowCoord)); |
848 | } | 855 | } |
@@ -857,6 +864,9 @@ iLocalDef iBool isMouseEvent_(const SDL_Event *ev) { | |||
857 | } | 864 | } |
858 | 865 | ||
859 | static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) { | 866 | static iBool filterEvent_Widget_(const iWidget *d, const SDL_Event *ev) { |
867 | if (d->flags & destroyPending_WidgetFlag) { | ||
868 | return iFalse; /* no more events handled */ | ||
869 | } | ||
860 | const iBool isKey = isKeyboardEvent_(ev); | 870 | const iBool isKey = isKeyboardEvent_(ev); |
861 | const iBool isMouse = isMouseEvent_(ev); | 871 | const iBool isMouse = isMouseEvent_(ev); |
862 | if ((d->flags & disabled_WidgetFlag) || (d->flags & hidden_WidgetFlag && | 872 | if ((d->flags & disabled_WidgetFlag) || (d->flags & hidden_WidgetFlag && |
@@ -1102,8 +1112,8 @@ void drawBackground_Widget(const iWidget *d) { | |||
1102 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); | 1112 | drawSoftShadow_Paint(&p, bounds_Widget(d), 12 * gap_UI, black_ColorId, 30); |
1103 | } | 1113 | } |
1104 | const iBool isFaded = fadeBackground && | 1114 | const iBool isFaded = fadeBackground && |
1105 | ~d->flags & noFadeBackground_WidgetFlag && | 1115 | ~d->flags & noFadeBackground_WidgetFlag;/* && |
1106 | ~d->flags & destroyPending_WidgetFlag; | 1116 | ~d->flags & destroyPending_WidgetFlag;*/ |
1107 | if (isFaded) { | 1117 | if (isFaded) { |
1108 | iPaint p; | 1118 | iPaint p; |
1109 | init_Paint(&p); | 1119 | init_Paint(&p); |
diff --git a/src/ui/widget.h b/src/ui/widget.h index 79d45f23..8de62b7a 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h | |||
@@ -139,6 +139,7 @@ struct Impl_Widget { | |||
139 | iInt2 minSize; | 139 | iInt2 minSize; |
140 | iWidget * sizeRef; | 140 | iWidget * sizeRef; |
141 | iWidget * offsetRef; | 141 | iWidget * offsetRef; |
142 | const iAnim *animOffsetRef; | ||
142 | int padding[4]; /* left, top, right, bottom */ | 143 | int padding[4]; /* left, top, right, bottom */ |
143 | iAnim visualOffset; | 144 | iAnim visualOffset; |
144 | int bgColor; | 145 | int bgColor; |
diff --git a/src/ui/window.c b/src/ui/window.c index 87db2f3e..96a22fee 100644 --- a/src/ui/window.c +++ b/src/ui/window.c | |||
@@ -858,6 +858,19 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
858 | const iInt2 pos = coord_Window(d, event.button.x, event.button.y); | 858 | const iInt2 pos = coord_Window(d, event.button.x, event.button.y); |
859 | event.button.x = pos.x; | 859 | event.button.x = pos.x; |
860 | event.button.y = pos.y; | 860 | event.button.y = pos.y; |
861 | if (event.type == SDL_MOUSEBUTTONDOWN) { | ||
862 | /* Button clicks will change keyroot. */ | ||
863 | if (numRoots_Window(d) > 1) { | ||
864 | const iInt2 click = init_I2(event.button.x, event.button.y); | ||
865 | iForIndices(i, d->roots) { | ||
866 | iRoot *root = d->roots[i]; | ||
867 | if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) { | ||
868 | setKeyRoot_Window(d, root); | ||
869 | break; | ||
870 | } | ||
871 | } | ||
872 | } | ||
873 | } | ||
861 | } | 874 | } |
862 | const iWidget *oldHover = d->hover; | 875 | const iWidget *oldHover = d->hover; |
863 | iBool wasUsed = iFalse; | 876 | iBool wasUsed = iFalse; |
@@ -889,7 +902,9 @@ iBool processEvent_Window(iWindow *d, const SDL_Event *ev) { | |||
889 | wasUsed = dispatchEvent_Window(d, &paste); | 902 | wasUsed = dispatchEvent_Window(d, &paste); |
890 | } | 903 | } |
891 | if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { | 904 | if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { |
892 | postContextClick_Window(d, &event.button); | 905 | if (postContextClick_Window(d, &event.button)) { |
906 | wasUsed = iTrue; | ||
907 | } | ||
893 | } | 908 | } |
894 | } | 909 | } |
895 | if (isMetricsChange_UserEvent(&event)) { | 910 | if (isMetricsChange_UserEvent(&event)) { |