summaryrefslogtreecommitdiff
path: root/src/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/ui')
-rw-r--r--src/ui/color.c67
-rw-r--r--src/ui/color.h3
-rw-r--r--src/ui/documentwidget.c10
-rw-r--r--src/ui/labelwidget.c2
-rw-r--r--src/ui/root.c15
-rw-r--r--src/ui/sidebarwidget.c42
-rw-r--r--src/ui/text.c258
-rw-r--r--src/ui/text.h5
-rw-r--r--src/ui/util.c69
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/window.c8
11 files changed, 293 insertions, 187 deletions
diff --git a/src/ui/color.c b/src/ui/color.c
index 0f7d4368..adbe444b 100644
--- a/src/ui/color.c
+++ b/src/ui/color.c
@@ -785,8 +785,14 @@ static const iColor ansi8BitColors_[256] = {
785 { 255, 255, 255, 255 } 785 { 255, 255, 255, 255 }
786}; 786};
787 787
788iColor ansiForeground_Color(iRangecc escapeSequence, int fallback) { 788void ansiColors_Color(iRangecc escapeSequence, int fgDefault, int bgDefault,
789 iColor clr = get_Color(fallback); 789 iColor *fg_out, iColor *bg_out) {
790 if (!fg_out && !bg_out) {
791 return;
792 }
793 iColor fg, bg;
794 iZap(fg);
795 iZap(bg);
790 for (const char *ch = escapeSequence.start; ch < escapeSequence.end; ch++) { 796 for (const char *ch = escapeSequence.start; ch < escapeSequence.end; ch++) {
791 char *endPtr; 797 char *endPtr;
792 unsigned long arg = strtoul(ch, &endPtr, 10); 798 unsigned long arg = strtoul(ch, &endPtr, 10);
@@ -802,19 +808,37 @@ iColor ansiForeground_Color(iRangecc escapeSequence, int fallback) {
802 case 35: 808 case 35:
803 case 36: 809 case 36:
804 case 37: 810 case 37:
805 clr = ansi8BitColors_[arg - 30]; 811 fg = ansi8BitColors_[arg - 30];
806 break; 812 break;
807 case 38: { 813 case 38:
814 case 48: {
815 iColor *dst = (arg == 38 ? &fg : &bg);
808 /* Extended foreground color. */ 816 /* Extended foreground color. */
809 arg = strtoul(ch + 1, &endPtr, 10); 817 arg = strtoul(ch + 1, &endPtr, 10);
810 ch = endPtr; 818 ch = endPtr;
811 if (arg == 5) /* 8-bit palette */ { 819 if (arg == 5) /* 8-bit palette */ {
812 arg = strtoul(ch + 1, &endPtr, 10); 820 arg = strtoul(ch + 1, &endPtr, 10);
813 ch = endPtr; 821 ch = endPtr;
814 clr = ansi8BitColors_[iClamp(arg, 0, 255)]; 822 *dst = ansi8BitColors_[iClamp(arg, 0, 255)];
815 } 823 }
816 break; 824 break;
817 } 825 }
826 case 39:
827 fg = get_Color(fgDefault);
828 break;
829 case 40:
830 case 41:
831 case 42:
832 case 43:
833 case 44:
834 case 45:
835 case 46:
836 case 47:
837 bg = ansi8BitColors_[arg - 40];
838 break;
839 case 49:
840 bg = get_Color(bgDefault);
841 break;
818 case 90: 842 case 90:
819 case 91: 843 case 91:
820 case 92: 844 case 92:
@@ -823,17 +847,36 @@ iColor ansiForeground_Color(iRangecc escapeSequence, int fallback) {
823 case 95: 847 case 95:
824 case 96: 848 case 96:
825 case 97: 849 case 97:
826 clr = ansi8BitColors_[8 + arg - 90]; 850 fg = ansi8BitColors_[8 + arg - 90];
827 break; 851 break;
828 } 852 }
829 } 853 }
830 /* On light backgrounds, darken the colors to make them more legible. */ 854 /* Ensure legibility if only one of the colors is set. */
831 if (get_HSLColor(tmBackground_ColorId).lum > 0.5f) { 855 /* TODO: Force darkening of the background color, unless it is also specified. */
832 clr.r /= 2; 856#if 0
833 clr.g /= 2; 857 if (bg.a == 0 && !equal_Color(fg, get_Color(fgDefault))) {
834 clr.b /= 2; 858 if (delta_Color(fg, get_Color(tmBackground_ColorId)) < 64) {
859 const iHSLColor fgHsl = hsl_Color(fg);
860 iHSLColor legibleBg = get_HSLColor(tmBackground_ColorId);
861 if ()
862 bg = rgb_HSLColor(bgHsl);
863 }
864 }
865 }
866#endif
867 /*
868 if (!bg_out || get_HSLColor(tmBackground_ColorId).lum > 0.5f) {
869 fg.r /= 2;
870 fg.g /= 2;
871 fg.b /= 2;
872 }
873 */
874 if (fg.a && fg_out) {
875 *fg_out = fg;
876 }
877 if (bg.a && bg_out) {
878 *bg_out = bg;
835 } 879 }
836 return clr;
837} 880}
838 881
839iBool loadPalette_Color(const char *path) { 882iBool loadPalette_Color(const char *path) {
diff --git a/src/ui/color.h b/src/ui/color.h
index 03172fcb..6602b226 100644
--- a/src/ui/color.h
+++ b/src/ui/color.h
@@ -255,7 +255,8 @@ iLocalDef void setHsl_Color(int color, iHSLColor hsl) {
255iBool loadPalette_Color (const char *path); 255iBool loadPalette_Color (const char *path);
256void setThemePalette_Color (enum iColorTheme theme); 256void setThemePalette_Color (enum iColorTheme theme);
257 257
258iColor ansiForeground_Color (iRangecc escapeSequence, int fallback); 258void ansiColors_Color (iRangecc escapeSequence, int fgDefault, int bgDefault,
259 iColor *fg_out, iColor *bg_out); /* can be NULL */
259const char * escape_Color (int color); 260const char * escape_Color (int color);
260enum iColorId parseEscape_Color (const char *cstr, const char **endp); 261enum iColorId parseEscape_Color (const char *cstr, const char **endp);
261 262
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index f2553632..0f0aeae8 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -3012,7 +3012,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3012 } 3012 }
3013 else if (equal_Command(cmd, "document.input.submit") && document_Command(cmd) == d) { 3013 else if (equal_Command(cmd, "document.input.submit") && document_Command(cmd) == d) {
3014 postCommandf_Root(w->root, 3014 postCommandf_Root(w->root,
3015 "open url:%s", 3015 /* use the `redirect:1` argument to cause the input query URL to be
3016 replaced in History; we don't want to navigate onto it */
3017 "open redirect:1 url:%s",
3016 cstrCollect_String(makeQueryUrl_DocumentWidget_ 3018 cstrCollect_String(makeQueryUrl_DocumentWidget_
3017 (d, collect_String(suffix_Command(cmd, "value"))))); 3019 (d, collect_String(suffix_Command(cmd, "value")))));
3018 return iTrue; 3020 return iTrue;
@@ -3066,7 +3068,11 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
3066 iReleasePtr(&d->request); 3068 iReleasePtr(&d->request);
3067 updateVisible_DocumentWidget_(d); 3069 updateVisible_DocumentWidget_(d);
3068 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag; 3070 d->drawBufs->flags |= updateSideBuf_DrawBufsFlag;
3069 postCommandf_Root(w->root, "document.changed doc:%p url:%s", d, cstr_String(d->mod.url)); 3071 postCommandf_Root(w->root,
3072 "document.changed doc:%p status:%d url:%s",
3073 d,
3074 d->sourceStatus,
3075 cstr_String(d->mod.url));
3070 /* Check for a pending goto. */ 3076 /* Check for a pending goto. */
3071 if (!isEmpty_String(&d->pendingGotoHeading)) { 3077 if (!isEmpty_String(&d->pendingGotoHeading)) {
3072 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading)); 3078 scrollToHeading_DocumentWidget_(d, cstr_String(&d->pendingGotoHeading));
diff --git a/src/ui/labelwidget.c b/src/ui/labelwidget.c
index daca05d1..46bdc890 100644
--- a/src/ui/labelwidget.c
+++ b/src/ui/labelwidget.c
@@ -421,7 +421,7 @@ static void draw_LabelWidget_(const iLabelWidget *d) {
421 else { 421 else {
422 drawCenteredOutline_Text( 422 drawCenteredOutline_Text(
423 d->font, 423 d->font,
424 adjusted_Rect(bounds, add_I2(zero_I2(), init_I2(iconPad, 0)), neg_I2(zero_I2())), 424 adjusted_Rect(bounds, init_I2(iconPad * 1.5f, 0), init_I2(-iconPad, 0)),
425 d->flags.alignVisual, 425 d->flags.alignVisual,
426 d->flags.drawAsOutline ? fg : none_ColorId, 426 d->flags.drawAsOutline ? fg : none_ColorId,
427 d->flags.drawAsOutline ? d->widget.bgColor : fg, 427 d->flags.drawAsOutline ? d->widget.bgColor : fg,
diff --git a/src/ui/root.c b/src/ui/root.c
index 95126654..5bfae857 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -27,7 +27,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
27#include "command.h" 27#include "command.h"
28#include "defs.h" 28#include "defs.h"
29#include "documentwidget.h" 29#include "documentwidget.h"
30#include "embedded.h" 30#include "resources.h"
31#include "inputwidget.h" 31#include "inputwidget.h"
32#include "keys.h" 32#include "keys.h"
33#include "labelwidget.h" 33#include "labelwidget.h"
@@ -78,6 +78,9 @@ static const iMenuItem navMenuItems_[] = {
78 { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" }, 78 { gear_Icon " ${menu.preferences}", SDLK_COMMA, KMOD_PRIMARY, "preferences" },
79 { "${menu.help}", SDLK_F1, 0, "!open url:about:help" }, 79 { "${menu.help}", SDLK_F1, 0, "!open url:about:help" },
80 { "${menu.releasenotes}", 0, 0, "!open url:about:version" }, 80 { "${menu.releasenotes}", 0, 0, "!open url:about:version" },
81 #if defined (LAGRANGE_ENABLE_WINSPARKLE)
82 { "${menu.update}", 0, 0, "updater.check" },
83 #endif
81 { "---" }, 84 { "---" },
82 { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" } 85 { "${menu.quit}", 'q', KMOD_PRIMARY, "quit" }
83}; 86};
@@ -757,9 +760,17 @@ static iBool handleNavBarCommands_(iWidget *navBar, const char *cmd) {
757 if (equal_Command(cmd, "document.changed")) { 760 if (equal_Command(cmd, "document.changed")) {
758 iInputWidget *url = findWidget_Root("url"); 761 iInputWidget *url = findWidget_Root("url");
759 const iString *urlStr = collect_String(suffix_Command(cmd, "url")); 762 const iString *urlStr = collect_String(suffix_Command(cmd, "url"));
763 const enum iGmStatusCode statusCode = argLabel_Command(cmd, "status");
760 trimCache_App(); 764 trimCache_App();
761 trimMemory_App(); 765 trimMemory_App();
762 visitUrl_Visited(visited_App(), urlStr, 0); 766 visitUrl_Visited(visited_App(),
767 urlStr,
768 /* The transient flag modifies history navigation behavior on
769 special responses like input queries. */
770 category_GmStatusCode(statusCode) == categoryInput_GmStatusCode ||
771 category_GmStatusCode(statusCode) == categoryRedirect_GmStatusCode
772 ? transient_VisitedUrlFlag
773 : 0);
763 postCommand_App("visited.changed"); /* sidebar will update */ 774 postCommand_App("visited.changed"); /* sidebar will update */
764 setText_InputWidget(url, urlStr); 775 setText_InputWidget(url, urlStr);
765 checkLoadAnimation_Root_(get_Root()); 776 checkLoadAnimation_Root_(get_Root());
diff --git a/src/ui/sidebarwidget.c b/src/ui/sidebarwidget.c
index 4b6fde3c..581a7bb2 100644
--- a/src/ui/sidebarwidget.c
+++ b/src/ui/sidebarwidget.c
@@ -346,10 +346,24 @@ static void updateItems_SidebarWidget_(iSidebarWidget *d) {
346 } 346 }
347 } 347 }
348 /* Actions. */ { 348 /* Actions. */ {
349 addActionButton_SidebarWidget_(d, "${sidebar.action.feeds.showall}", "feeds.mode arg:0", 349 addActionButton_SidebarWidget_(
350 d->feedsMode == all_FeedsMode ? selected_WidgetFlag : 0); 350 d, check_Icon " ${feeds.markallread}", "feeds.markallread", expand_WidgetFlag);
351 addActionButton_SidebarWidget_(d, "${sidebar.action.feeds.showunread}", "feeds.mode arg:1", 351 addChild_Widget(d->actions, iClob(new_LabelWidget("${sidebar.action.show}", NULL)));
352 d->feedsMode == unread_FeedsMode ? selected_WidgetFlag : 0); 352 const iMenuItem items[] = {
353 { "${sidebar.action.feeds.showall}", SDLK_u, KMOD_SHIFT, "feeds.mode arg:0" },
354 { "${sidebar.action.feeds.showunread}", SDLK_u, 0, "feeds.mode arg:1" },
355 };
356 iWidget *dropButton = addChild_Widget(
357 d->actions,
358 iClob(makeMenuButton_LabelWidget(items[d->feedsMode].label, items, 2)));
359 setFixedSize_Widget(
360 dropButton,
361 init_I2(measure_Text(
362 default_FontId,
363 translateCStr_Lang(items[findWidestLabel_MenuItem(items, 2)].label))
364 .advance.x +
365 6 * gap_UI,
366 -1));
353 } 367 }
354 d->menu = makeMenu_Widget( 368 d->menu = makeMenu_Widget(
355 as_Widget(d), 369 as_Widget(d),
@@ -893,6 +907,7 @@ static void itemClicked_SidebarWidget_(iSidebarWidget *d, iSidebarItem *item, si
893 if (isEmpty_String(&item->url)) /* a folder */ { 907 if (isEmpty_String(&item->url)) /* a folder */ {
894 if (contains_IntSet(d->closedFolders, item->id)) { 908 if (contains_IntSet(d->closedFolders, item->id)) {
895 remove_IntSet(d->closedFolders, item->id); 909 remove_IntSet(d->closedFolders, item->id);
910 setRecentFolder_Bookmarks(bookmarks_App(), item->id);
896 } 911 }
897 else { 912 else {
898 insert_IntSet(d->closedFolders, item->id); 913 insert_IntSet(d->closedFolders, item->id);
@@ -990,7 +1005,11 @@ void setWidth_SidebarWidget(iSidebarWidget *d, float widthAsGaps) {
990} 1005}
991 1006
992iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *cmd) { 1007iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *cmd) {
993 if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { 1008 if (equal_Command(cmd, "dlg.bookmark.setfolder")) {
1009 setBookmarkEditorFolder_Widget(editor, arg_Command(cmd));
1010 return iTrue;
1011 }
1012 if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "bmed.cancel")) {
994 iAssert(startsWith_String(id_Widget(editor), "bmed.")); 1013 iAssert(startsWith_String(id_Widget(editor), "bmed."));
995 iSidebarWidget *d = findWidget_App(cstr_String(id_Widget(editor)) + 5); /* bmed.sidebar */ 1014 iSidebarWidget *d = findWidget_App(cstr_String(id_Widget(editor)) + 5); /* bmed.sidebar */
996 if (equal_Command(cmd, "bmed.accept")) { 1015 if (equal_Command(cmd, "bmed.accept")) {
@@ -1020,7 +1039,11 @@ iBool handleBookmarkEditorCommands_SidebarWidget_(iWidget *editor, const char *c
1020 isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote"))); 1039 isSelected_Widget(findChild_Widget(editor, "bmed.tag.remote")));
1021 addOrRemoveTag_Bookmark(bm, linkSplit_BookmarkTag, 1040 addOrRemoveTag_Bookmark(bm, linkSplit_BookmarkTag,
1022 isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))); 1041 isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit")));
1023 } 1042 }
1043 const iBookmark *folder = userData_Object(findChild_Widget(editor, "bmed.folder"));
1044 if (!folder || !hasParent_Bookmark(folder, id_Bookmark(bm))) {
1045 bm->parentId = folder ? id_Bookmark(folder) : 0;
1046 }
1024 postCommand_App("bookmarks.changed"); 1047 postCommand_App("bookmarks.changed");
1025 } 1048 }
1026 setupSheetTransition_Mobile(editor, iFalse); 1049 setupSheetTransition_Mobile(editor, iFalse);
@@ -1327,6 +1350,7 @@ static iBool processEvent_SidebarWidget_(iSidebarWidget *d, const SDL_Event *ev)
1327 setFlags_Widget(notNeeded[i], disabled_WidgetFlag, iTrue); 1350 setFlags_Widget(notNeeded[i], disabled_WidgetFlag, iTrue);
1328 } 1351 }
1329 } 1352 }
1353 setBookmarkEditorFolder_Widget(dlg, bm ? bm->parentId : 0);
1330 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_); 1354 setCommandHandler_Widget(dlg, handleBookmarkEditorCommands_SidebarWidget_);
1331 setFocus_Widget(findChild_Widget(dlg, "bmed.title")); 1355 setFocus_Widget(findChild_Widget(dlg, "bmed.title"));
1332 } 1356 }
@@ -2019,7 +2043,7 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2019 add_I2(topLeft_Rect(itemRect), 2043 add_I2(topLeft_Rect(itemRect),
2020 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)), 2044 init_I2(3 * gap_UI, (itemHeight - lineHeight_Text(font)) / 2)),
2021 fg, 2045 fg,
2022 "%s%s%s%s%s%s", 2046 "%s%s%s%s%s%s%s%s",
2023 isGemini ? "" : cstr_Rangecc(parts.scheme), 2047 isGemini ? "" : cstr_Rangecc(parts.scheme),
2024 isGemini ? "" : isAbout ? ":" : "://", 2048 isGemini ? "" : isAbout ? ":" : "://",
2025 escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId 2049 escape_Color(isHover ? (isPressing ? uiTextPressed_ColorId
@@ -2027,7 +2051,9 @@ static void draw_SidebarItem_(const iSidebarItem *d, iPaint *p, iRect itemRect,
2027 : uiTextStrong_ColorId), 2051 : uiTextStrong_ColorId),
2028 cstr_Rangecc(parts.host), 2052 cstr_Rangecc(parts.host),
2029 escape_Color(fg), 2053 escape_Color(fg),
2030 cstr_Rangecc(parts.path)); 2054 cstr_Rangecc(parts.path),
2055 !isEmpty_Range(&parts.query) ? escape_Color(uiAnnotation_ColorId) : "",
2056 !isEmpty_Range(&parts.query) ? cstr_Rangecc(parts.query) : "");
2031 } 2057 }
2032 iEndCollect(); 2058 iEndCollect();
2033 } 2059 }
diff --git a/src/ui/text.c b/src/ui/text.c
index abe8640c..91ff137a 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -23,7 +23,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
23#include "text.h" 23#include "text.h"
24#include "color.h" 24#include "color.h"
25#include "metrics.h" 25#include "metrics.h"
26#include "embedded.h" 26#include "resources.h"
27#include "window.h" 27#include "window.h"
28#include "paint.h" 28#include "paint.h"
29#include "app.h" 29#include "app.h"
@@ -180,21 +180,21 @@ static void init_Font(iFont *d, const iFontSpec *fontSpec, const iFontFile *font
180 d->data = NULL; 180 d->data = NULL;
181 d->family = undefined_TextFont; 181 d->family = undefined_TextFont;
182 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */ 182 /* Note: We only use `family` currently for applying a kerning fix to Nunito. */
183 if (data == &fontNunitoRegular_Embedded || 183 if (data == &fontNunitoRegular_Resources ||
184 data == &fontNunitoBold_Embedded || 184 data == &fontNunitoBold_Resources ||
185 data == &fontNunitoExtraBold_Embedded || 185 data == &fontNunitoExtraBold_Resources ||
186 //data == &fontNunitoLightItalic_Embedded || 186 //data == &fontNunitoLightItalic_Resources ||
187 data == &fontNunitoExtraLight_Embedded) { 187 data == &fontNunitoExtraLight_Resources) {
188 d->family = nunito_TextFont; 188 d->family = nunito_TextFont;
189 } 189 }
190 else if (//data == &fontScheherazadeNewRegular_Embedded) { 190 else if (//data == &fontScheherazadeNewRegular_Resources) {
191 data == &fontNotoSansArabicUIRegular_Embedded) { 191 data == &fontNotoSansArabicUIRegular_Resources) {
192 d->family = arabic_TextFont; 192 d->family = arabic_TextFont;
193 } 193 }
194 else if (data == &fontNotoSansSymbolsRegular_Embedded || 194 else if (data == &fontNotoSansSymbolsRegular_Resources ||
195 data == &fontNotoSansSymbols2Regular_Embedded || 195 data == &fontNotoSansSymbols2Regular_Resources ||
196 data == &fontNotoEmojiRegular_Embedded || 196 data == &fontNotoEmojiRegular_Resources ||
197 data == &fontSmolEmojiRegular_Embedded) { 197 data == &fontSmolEmojiRegular_Resources) {
198 d->family = emojiAndSymbols_TextFont; 198 d->family = emojiAndSymbols_TextFont;
199 } 199 }
200#endif 200#endif
@@ -263,7 +263,7 @@ struct Impl_Text {
263 iRegExp * ansiEscape; 263 iRegExp * ansiEscape;
264 int ansiFlags; 264 int ansiFlags;
265 int baseFontId; /* base attributes (for restoring via escapes) */ 265 int baseFontId; /* base attributes (for restoring via escapes) */
266 int baseColorId; 266 int baseFgColorId;
267 iBool missingGlyphs; /* true if a glyph couldn't be found */ 267 iBool missingGlyphs; /* true if a glyph couldn't be found */
268}; 268};
269 269
@@ -390,35 +390,14 @@ static void deinitCache_Text_(iText *d) {
390 SDL_DestroyTexture(d->cache); 390 SDL_DestroyTexture(d->cache);
391} 391}
392 392
393#if 0
394void loadUserFonts_Text(void) {
395 if (userFont_) {
396 delete_Block(userFont_);
397 userFont_ = NULL;
398 }
399 /* Load the system font. */
400 const iPrefs *prefs = prefs_App();
401 if (!isEmpty_String(&prefs->symbolFontPath)) {
402 iFile *f = new_File(&prefs->symbolFontPath);
403 if (open_File(f, readOnly_FileMode)) {
404 userFont_ = readAll_File(f);
405 }
406 else {
407 fprintf(stderr, "[Text] failed to open: %s\n", cstr_String(&prefs->symbolFontPath));
408 }
409 iRelease(f);
410 }
411}
412#endif
413
414void init_Text(iText *d, SDL_Renderer *render) { 393void init_Text(iText *d, SDL_Renderer *render) {
415 iText *oldActive = activeText_; 394 iText *oldActive = activeText_;
416 activeText_ = d; 395 activeText_ = d;
417 init_Array(&d->fonts, sizeof(iFont)); 396 init_Array(&d->fonts, sizeof(iFont));
418 d->contentFontSize = contentScale_Text_; 397 d->contentFontSize = contentScale_Text_;
419 d->ansiEscape = new_RegExp("[[()]([0-9;AB]*?)m", 0); 398 d->ansiEscape = new_RegExp("[[()][?]?([0-9;AB]*?)([ABCDEFGHJKSTfhilmn])", 0);
420 d->baseFontId = -1; 399 d->baseFontId = -1;
421 d->baseColorId = -1; 400 d->baseFgColorId = -1;
422 d->render = render; 401 d->render = render;
423 /* A grayscale palette for rasterized glyphs. */ { 402 /* A grayscale palette for rasterized glyphs. */ {
424 SDL_Color colors[256]; 403 SDL_Color colors[256];
@@ -459,9 +438,10 @@ void setOpacity_Text(float opacity) {
459 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 438 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
460} 439}
461 440
462void setBaseAttributes_Text(int fontId, int colorId) { 441void setBaseAttributes_Text(int fontId, int fgColorId) {
463 activeText_->baseFontId = fontId; 442 iText *d = activeText_;
464 activeText_->baseColorId = colorId; 443 d->baseFontId = fontId;
444 d->baseFgColorId = fgColorId;
465} 445}
466 446
467void setAnsiFlags_Text(int ansiFlags) { 447void setAnsiFlags_Text(int ansiFlags) {
@@ -574,16 +554,6 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
574 return overrideFont; 554 return overrideFont;
575 } 555 }
576 } 556 }
577#if 0
578 /* TODO: Put arrows in Smol Emoji. */
579 /* Manual exceptions. */ {
580 if (ch >= 0x2190 && ch <= 0x2193 /* arrows */) {
581 d = font_Text_(iosevka_FontId + d->sizeId);
582 *glyphIndex = glyphIndex_Font_(d, ch);
583 return d;
584 }
585 }
586#endif
587 /* The font's own version of the glyph. */ 557 /* The font's own version of the glyph. */
588 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) { 558 if ((*glyphIndex = glyphIndex_Font_(d, ch)) != 0) {
589 return d; 559 return d;
@@ -609,69 +579,6 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
609 } 579 }
610 } 580 }
611 } 581 }
612#if 0
613 const int fallbacks[] = {
614 notoEmoji_FontId,
615 symbols2_FontId,
616 symbols_FontId
617 };
618 /* First fallback is Smol Emoji. */
619 if (ch != 0x20) {
620 iForIndices(i, fallbacks) {
621 iFont *fallback = font_Text_(fallbacks[i] + d->sizeId);
622 if (fallback != d && (*glyphIndex = glyphIndex_Font_(fallback, ch)) != 0) {
623 return fallback;
624 }
625 }
626 }
627 /* Try Simplified Chinese. */
628 if (ch >= 0x2e80) {
629 iFont *sc = font_Text_(chineseSimplified_FontId + d->sizeId);
630 if (sc != d && (*glyphIndex = glyphIndex_Font_(sc, ch)) != 0) {
631 return sc;
632 }
633 }
634 /* Could be Korean. */
635 if (ch >= 0x3000) {
636 iFont *korean = font_Text_(korean_FontId + d->sizeId);
637 if (korean != d && (*glyphIndex = glyphIndex_Font_(korean, ch)) != 0) {
638 return korean;
639 }
640 }
641 /* Japanese perhaps? */
642 if (ch > 0x3040) {
643 iFont *japanese = font_Text_(japanese_FontId + d->sizeId);
644 if (japanese != d && (*glyphIndex = glyphIndex_Font_(japanese, ch)) != 0) {
645 return japanese;
646 }
647 }
648 /* Maybe Arabic. */
649 if (ch >= 0x600) {
650 iFont *arabic = font_Text_(arabic_FontId + d->sizeId);
651 if (arabic != d && (*glyphIndex = glyphIndex_Font_(arabic, ch)) != 0) {
652 return arabic;
653 }
654 }
655#if defined (iPlatformApple)
656 /* White up arrow is used for the Shift key on macOS. Symbola's glyph is not a great
657 match to the other text, so use the UI font instead. */
658 if ((ch == 0x2318 || ch == 0x21e7) && d == font_Text_(regular_FontId)) {
659 *glyphIndex = glyphIndex_Font_(d = font_Text_(defaultContentRegular_FontId), ch);
660 return d;
661 }
662#endif
663 /* User's symbols font. */ {
664 iFont *sys = font_Text_(userSymbols_FontId + d->sizeId);
665 if (sys != d && (*glyphIndex = glyphIndex_Font_(sys, ch)) != 0) {
666 return sys;
667 }
668 }
669 /* Final fallback. */
670 iFont *font = font_Text_(iosevka_FontId + d->sizeId);
671 if (d != font) {
672 *glyphIndex = glyphIndex_Font_(font, ch);
673 }
674#endif // 0
675 if (!*glyphIndex) { 582 if (!*glyphIndex) {
676 activeText_->missingGlyphs = iTrue; 583 activeText_->missingGlyphs = iTrue;
677 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr); 584 fprintf(stderr, "failed to find %08x (%lc)\n", ch, (int)ch); fflush(stderr);
@@ -680,7 +587,9 @@ iLocalDef iFont *characterFont_Font_(iFont *d, iChar ch, uint32_t *glyphIndex) {
680} 587}
681 588
682static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) { 589static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
683 iAssert(d->table); 590 if (!d->table) {
591 d->table = new_GlyphTable();
592 }
684 iGlyph* glyph = NULL; 593 iGlyph* glyph = NULL;
685 void * node = value_Hash(&d->table->glyphs, glyphIndex); 594 void * node = value_Hash(&d->table->glyphs, glyphIndex);
686 if (node) { 595 if (node) {
@@ -739,6 +648,7 @@ struct Impl_AttributedRun {
739 iTextAttrib attrib; 648 iTextAttrib attrib;
740 iFont *font; 649 iFont *font;
741 iColor fgColor_; /* any RGB color; A > 0 */ 650 iColor fgColor_; /* any RGB color; A > 0 */
651 iColor bgColor_; /* any RGB color; A > 0 */
742 struct { 652 struct {
743 uint8_t isLineBreak : 1; 653 uint8_t isLineBreak : 1;
744// uint8_t isRTL : 1; 654// uint8_t isRTL : 1;
@@ -750,29 +660,45 @@ static iColor fgColor_AttributedRun_(const iAttributedRun *d) {
750 if (d->fgColor_.a) { 660 if (d->fgColor_.a) {
751 return d->fgColor_; 661 return d->fgColor_;
752 } 662 }
753 if (d->attrib.colorId == none_ColorId) { 663 if (d->attrib.fgColorId == none_ColorId) {
754 return (iColor){ 255, 255, 255, 255 }; 664 return (iColor){ 255, 255, 255, 255 };
755 } 665 }
756 return get_Color(d->attrib.colorId); 666 return get_Color(d->attrib.fgColorId);
667}
668
669static iColor bgColor_AttributedRun_(const iAttributedRun *d) {
670 if (d->bgColor_.a) {
671 return d->bgColor_;
672 }
673 return (iColor){ 255, 255, 255, 0 };
674 if (d->attrib.bgColorId == none_ColorId) {
675 return (iColor){ 255, 255, 255, 0 };
676 }
677 return get_Color(d->attrib.bgColorId);
757} 678}
758 679
759static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) { 680static void setFgColor_AttributedRun_(iAttributedRun *d, int colorId) {
760 d->attrib.colorId = colorId; 681 d->attrib.fgColorId = colorId;
761 d->fgColor_.a = 0; 682 d->fgColor_.a = 0;
762} 683}
763 684
685static void setBgColor_AttributedRun_(iAttributedRun *d, int colorId) {
686 d->attrib.bgColorId = colorId;
687 d->bgColor_.a = 0;
688}
689
764iDeclareType(AttributedText) 690iDeclareType(AttributedText)
765iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font, 691iDeclareTypeConstructionArgs(AttributedText, iRangecc text, size_t maxLen, iFont *font,
766 int colorId, int baseDir, iFont *baseFont, int baseColorId, 692 int colorId, int baseDir, iFont *baseFont, int baseFgColorId,
767 iChar overrideChar) 693 iChar overrideChar)
768 694
769struct Impl_AttributedText { 695struct Impl_AttributedText {
770 iRangecc source; /* original source text */ 696 iRangecc source; /* original source text */
771 size_t maxLen; 697 size_t maxLen;
772 iFont * font; 698 iFont * font;
773 int colorId; 699 int fgColorId;
774 iFont * baseFont; 700 iFont * baseFont;
775 int baseColorId; 701 int baseFgColorId;
776 iBool isBaseRTL; 702 iBool isBaseRTL;
777 iArray runs; 703 iArray runs;
778 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */ 704 iArray logical; /* UTF-32 text in logical order (mixed directions; matches source) */
@@ -785,9 +711,9 @@ struct Impl_AttributedText {
785 711
786iDefineTypeConstructionArgs(AttributedText, 712iDefineTypeConstructionArgs(AttributedText,
787 (iRangecc text, size_t maxLen, iFont *font, int colorId, 713 (iRangecc text, size_t maxLen, iFont *font, int colorId,
788 int baseDir, iFont *baseFont, int baseColorId, 714 int baseDir, iFont *baseFont, int baseFgColorId,
789 iChar overrideChar), 715 iChar overrideChar),
790 text, maxLen, font, colorId, baseDir, baseFont, baseColorId, 716 text, maxLen, font, colorId, baseDir, baseFont, baseFgColorId,
791 overrideChar) 717 overrideChar)
792 718
793static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) { 719static const char *sourcePtr_AttributedText_(const iAttributedText *d, int logicalPos) {
@@ -897,7 +823,8 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
897 } 823 }
898 iAttributedRun run = { 824 iAttributedRun run = {
899 .logical = { 0, length }, 825 .logical = { 0, length },
900 .attrib = { .colorId = d->colorId, .isBaseRTL = d->isBaseRTL }, 826 .attrib = { .fgColorId = d->fgColorId, .bgColorId = none_ColorId,
827 .isBaseRTL = d->isBaseRTL },
901 .font = d->font, 828 .font = d->font,
902 }; 829 };
903 const int *logToSource = constData_Array(&d->logicalToSourceOffset); 830 const int *logToSource = constData_Array(&d->logicalToSourceOffset);
@@ -935,12 +862,13 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
935 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) { 862 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {
936 finishRun_AttributedText_(d, &run, pos - 1); 863 finishRun_AttributedText_(d, &run, pos - 1);
937 const int ansi = activeText_->ansiFlags; 864 const int ansi = activeText_->ansiFlags;
938 if (ansi) { 865 if (ansi && capturedRange_RegExpMatch(&m, 2).start[0] ==
866 'm' /* Select Graphic Rendition */) {
939 const iRangecc sequence = capturedRange_RegExpMatch(&m, 1); 867 const iRangecc sequence = capturedRange_RegExpMatch(&m, 1);
940 /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */ 868 /* Note: This styling is hardcoded to match `typesetOneLine_RunTypesetter_()`. */
941 if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) { 869 if (ansi & allowFontStyle_AnsiFlag && equal_Rangecc(sequence, "1")) {
942 run.attrib.bold = iTrue; 870 run.attrib.bold = iTrue;
943 if (d->baseColorId == tmParagraph_ColorId) { 871 if (d->baseFgColorId == tmParagraph_ColorId) {
944 setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId); 872 setFgColor_AttributedRun_(&run, tmFirstParagraph_ColorId);
945 } 873 }
946 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont), 874 attribFont = font_Text_(fontWithStyle_Text(fontId_Text_(d->baseFont),
@@ -962,10 +890,13 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
962 run.attrib.italic = iFalse; 890 run.attrib.italic = iFalse;
963 run.attrib.monospace = iFalse; 891 run.attrib.monospace = iFalse;
964 attribFont = run.font = d->baseFont; 892 attribFont = run.font = d->baseFont;
965 setFgColor_AttributedRun_(&run, d->baseColorId); 893 setFgColor_AttributedRun_(&run, d->baseFgColorId);
894 setBgColor_AttributedRun_(&run, none_ColorId);
966 } 895 }
967 else if (ansi & allowFg_AnsiFlag) { 896 else {
968 run.fgColor_ = ansiForeground_Color(sequence, tmParagraph_ColorId); 897 ansiColors_Color(sequence, d->baseFgColorId, none_ColorId,
898 ansi & allowFg_AnsiFlag ? &run.fgColor_ : NULL,
899 ansi & allowBg_AnsiFlag ? &run.bgColor_ : NULL);
969 } 900 }
970 } 901 }
971 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0)); 902 pos += length_Rangecc(capturedRange_RegExpMatch(&m, 0));
@@ -988,7 +919,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
988 colorNum = esc - asciiBase_ColorEscape; 919 colorNum = esc - asciiBase_ColorEscape;
989 } 920 }
990 run.logical.start = pos + 1; 921 run.logical.start = pos + 1;
991 setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->colorId); 922 setFgColor_AttributedRun_(&run, colorNum >= 0 ? colorNum : d->fgColorId);
992 continue; 923 continue;
993 } 924 }
994 if (ch == '\n') { 925 if (ch == '\n') {
@@ -1051,14 +982,15 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
1051} 982}
1052 983
1053void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId, 984void init_AttributedText(iAttributedText *d, iRangecc text, size_t maxLen, iFont *font, int colorId,
1054 int baseDir, iFont *baseFont, int baseColorId, iChar overrideChar) { 985 int baseDir, iFont *baseFont, int baseFgColorId,
1055 d->source = text; 986 iChar overrideChar) {
1056 d->maxLen = maxLen ? maxLen : iInvalidSize; 987 d->source = text;
1057 d->font = font; 988 d->maxLen = maxLen ? maxLen : iInvalidSize;
1058 d->colorId = colorId; 989 d->font = font;
1059 d->baseFont = baseFont; 990 d->fgColorId = colorId;
1060 d->baseColorId = baseColorId; 991 d->baseFont = baseFont;
1061 d->isBaseRTL = iFalse; 992 d->baseFgColorId = baseFgColorId;
993 d->isBaseRTL = iFalse;
1062 init_Array(&d->runs, sizeof(iAttributedRun)); 994 init_Array(&d->runs, sizeof(iAttributedRun));
1063 init_Array(&d->logical, sizeof(iChar)); 995 init_Array(&d->logical, sizeof(iChar));
1064 init_Array(&d->visual, sizeof(iChar)); 996 init_Array(&d->visual, sizeof(iChar));
@@ -1412,7 +1344,7 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1412 init_AttributedText(&attrText, args->text, args->maxLen, d, args->color, 1344 init_AttributedText(&attrText, args->text, args->maxLen, d, args->color,
1413 args->baseDir, 1345 args->baseDir,
1414 activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d, 1346 activeText_->baseFontId >= 0 ? font_Text_(activeText_->baseFontId) : d,
1415 activeText_->baseColorId, 1347 activeText_->baseFgColorId,
1416 wrap ? wrap->overrideChar : 0); 1348 wrap ? wrap->overrideChar : 0);
1417 if (wrap) { 1349 if (wrap) {
1418 wrap->baseDir = attrText.isBaseRTL ? -1 : +1; 1350 wrap->baseDir = attrText.isBaseRTL ? -1 : +1;
@@ -1464,7 +1396,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1464 iRangei wrapPosRange = { 0, textLen }; 1396 iRangei wrapPosRange = { 0, textLen };
1465 int wrapResumePos = textLen; /* logical position where next line resumes */ 1397 int wrapResumePos = textLen; /* logical position where next line resumes */
1466 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */ 1398 size_t wrapResumeRunIndex = runCount; /* index of run where next line resumes */
1467 iTextAttrib attrib = { .colorId = args->color, .isBaseRTL = attrText.isBaseRTL }; 1399 iTextAttrib attrib = { .fgColorId = args->color,
1400 .bgColorId = none_ColorId,
1401 .isBaseRTL = attrText.isBaseRTL };
1468 iTextAttrib wrapAttrib = attrib; 1402 iTextAttrib wrapAttrib = attrib;
1469 iTextAttrib lastAttrib = attrib; 1403 iTextAttrib lastAttrib = attrib;
1470 const int layoutBound = (wrap ? wrap->maxWidth : 0); 1404 const int layoutBound = (wrap ? wrap->maxWidth : 0);
@@ -1712,6 +1646,12 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1712 yCursor += d->height; 1646 yCursor += d->height;
1713 continue; 1647 continue;
1714 } 1648 }
1649 const iColor fgClr = fgColor_AttributedRun_(run);
1650 const iColor bgClr = bgColor_AttributedRun_(run);
1651 iBool isBgFilled = iFalse;
1652 if (~mode & permanentColorFlag_RunMode) {
1653 isBgFilled = (bgClr.a != 0) || (mode & fillBackground_RunMode);
1654 }
1715 iGlyphBuffer *buf = at_Array(&buffers, runIndex); 1655 iGlyphBuffer *buf = at_Array(&buffers, runIndex);
1716 shape_GlyphBuffer_(buf); 1656 shape_GlyphBuffer_(buf);
1717 iAssert(run->font == buf->font); 1657 iAssert(run->font == buf->font);
@@ -1772,34 +1712,46 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1772 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x); 1712 bounds.size.x = iMax(bounds.size.x, dst.x + dst.w - orig.x);
1773 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height); 1713 bounds.size.y = iMax(bounds.size.y, yCursor + glyph->font->height);
1774 } 1714 }
1775 if (mode & draw_RunMode && logicalText[logPos] > 0x20) { 1715 const iBool isSpace = (logicalText[logPos] == 0x20);
1716 if (mode & draw_RunMode && (isBgFilled || !isSpace)) {
1776 /* Draw the glyph. */ 1717 /* Draw the glyph. */
1777 if (!isRasterized_Glyph_(glyph, hoff)) { 1718 if (!isSpace && !isRasterized_Glyph_(glyph, hoff)) {
1778 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */ 1719 cacheSingleGlyph_Font_(run->font, glyphId); /* may cause cache reset */
1779 glyph = glyphByIndex_Font_(run->font, glyphId); 1720 glyph = glyphByIndex_Font_(run->font, glyphId);
1780 iAssert(isRasterized_Glyph_(glyph, hoff)); 1721 iAssert(isRasterized_Glyph_(glyph, hoff));
1781 } 1722 }
1782 if (~mode & permanentColorFlag_RunMode) { 1723 if (~mode & permanentColorFlag_RunMode) {
1783 const iColor clr = fgColor_AttributedRun_(run); 1724 SDL_SetTextureColorMod(activeText_->cache, fgClr.r, fgClr.g, fgClr.b);
1784 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
1785 if (args->mode & fillBackground_RunMode) {
1786 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
1787 }
1788 } 1725 }
1789 SDL_Rect src;
1790 memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect));
1791 dst.x += origin_Paint.x; 1726 dst.x += origin_Paint.x;
1792 dst.y += origin_Paint.y; 1727 dst.y += origin_Paint.y;
1793 if (args->mode & fillBackground_RunMode) { 1728 if (isBgFilled) {
1794 /* Alpha blending looks much better if the RGB components don't change in
1795 the partially transparent pixels. */
1796 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ 1729 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */
1797 SDL_RenderFillRect(activeText_->render, &dst); 1730 if (bgClr.a) {
1731 SDL_SetRenderDrawColor(activeText_->render, bgClr.r, bgClr.g, bgClr.b, 255);
1732 const SDL_Rect bgRect = {
1733 origin_Paint.x + orig.x + xCursor,
1734 origin_Paint.y + orig.y + yCursor,
1735 xAdvance,
1736 d->height,
1737 };
1738 SDL_RenderFillRect(activeText_->render, &bgRect);
1739 }
1740 else if (args->mode & fillBackground_RunMode) {
1741 /* Alpha blending looks much better if the RGB components don't change
1742 in the partially transparent pixels. */
1743 SDL_SetRenderDrawColor(activeText_->render, fgClr.r, fgClr.g, fgClr.b, 0);
1744 SDL_RenderFillRect(activeText_->render, &dst);
1745 }
1746 }
1747 if (!isSpace) {
1748 SDL_Rect src;
1749 memcpy(&src, &glyph->rect[hoff], sizeof(SDL_Rect));
1750 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
1798 } 1751 }
1799 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
1800#if 0 1752#if 0
1801 /* Show spaces and direction. */ 1753 /* Show spaces and direction. */
1802 if (logicalText[logPos] == 0x20) { 1754 if (isSpace) {
1803 const iColor debug = get_Color(run->flags.isRTL ? yellow_ColorId : red_ColorId); 1755 const iColor debug = get_Color(run->flags.isRTL ? yellow_ColorId : red_ColorId);
1804 SDL_SetRenderDrawColor(text_.render, debug.r, debug.g, debug.b, 255); 1756 SDL_SetRenderDrawColor(text_.render, debug.r, debug.g, debug.b, 255);
1805 dst.w = xAdvance; 1757 dst.w = xAdvance;
diff --git a/src/ui/text.h b/src/ui/text.h
index 13a636d4..de76ed09 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -157,7 +157,7 @@ enum iAnsiFlag {
157}; 157};
158 158
159void setOpacity_Text (float opacity); 159void setOpacity_Text (float opacity);
160void setBaseAttributes_Text (int fontId, int colorId); /* current "normal" text attributes */ 160void setBaseAttributes_Text (int fontId, int fgColorId); /* current "normal" text attributes */
161void setAnsiFlags_Text (int ansiFlags); 161void setAnsiFlags_Text (int ansiFlags);
162 162
163void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */ 163void cache_Text (int fontId, iRangecc text); /* pre-render glyphs */
@@ -187,7 +187,8 @@ iDeclareType(TextAttrib)
187/* Initial attributes at the start of a text string. These may be modified by control 187/* Initial attributes at the start of a text string. These may be modified by control
188 sequences inside a text run. */ 188 sequences inside a text run. */
189struct Impl_TextAttrib { 189struct Impl_TextAttrib {
190 int16_t colorId; 190 int16_t fgColorId;
191 int16_t bgColorId;
191 struct { 192 struct {
192 uint16_t bold : 1; 193 uint16_t bold : 1;
193 uint16_t italic : 1; 194 uint16_t italic : 1;
diff --git a/src/ui/util.c b/src/ui/util.c
index 0a9dde0c..e718631d 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -2181,6 +2181,14 @@ size_t findWidestLabel_MenuItem(const iMenuItem *items, size_t num) {
2181 return widestPos; 2181 return widestPos;
2182} 2182}
2183 2183
2184const char *widestLabel_MenuItemArray(const iArray *items) {
2185 size_t index = findWidestLabel_MenuItem(constData_Array(items), size_Array(items));
2186 if (index == iInvalidPos) {
2187 return "";
2188 }
2189 return constValue_Array(items, index, iMenuItem).label;
2190}
2191
2184iChar removeIconPrefix_String(iString *d) { 2192iChar removeIconPrefix_String(iString *d) {
2185 if (isEmpty_String(d)) { 2193 if (isEmpty_String(d)) {
2186 return 0; 2194 return 0;
@@ -2772,9 +2780,38 @@ iWidget *makePreferences_Widget(void) {
2772 return dlg; 2780 return dlg;
2773} 2781}
2774 2782
2783static iBool isBookmarkFolder_(void *context, const iBookmark *bm) {
2784 iUnused(context);
2785 return isFolder_Bookmark(bm);
2786}
2787
2788static const iArray *makeBookmarkFolderItems_(void) {
2789 iArray *folders = new_Array(sizeof(iMenuItem));
2790 pushBack_Array(folders, &(iMenuItem){ "\u2014", 0, 0, "dlg.bookmark.setfolder arg:0" });
2791 iConstForEach(
2792 PtrArray,
2793 i,
2794 list_Bookmarks(bookmarks_App(), cmpTree_Bookmark, isBookmarkFolder_, NULL)) {
2795 const iBookmark *bm = i.ptr;
2796 iString *title = collect_String(copy_String(&bm->title));
2797 for (const iBookmark *j = bm; j && j->parentId; ) {
2798 j = get_Bookmarks(bookmarks_App(), j->parentId);
2799 prependCStr_String(title, " > ");
2800 prepend_String(title, &j->title);
2801 }
2802 pushBack_Array(
2803 folders,
2804 &(iMenuItem){ cstr_String(title),
2805 0,
2806 0,
2807 format_CStr("dlg.bookmark.setfolder arg:%u", id_Bookmark(bm)) });
2808 }
2809 return collect_Array(folders);
2810}
2811
2775iWidget *makeBookmarkEditor_Widget(void) { 2812iWidget *makeBookmarkEditor_Widget(void) {
2776 const iMenuItem actions[] = { 2813 const iMenuItem actions[] = {
2777 { "${cancel}" }, 2814 { "${cancel}", 0, 0, "bmed.cancel" },
2778 { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" } 2815 { uiTextCaution_ColorEscape "${dlg.bookmark.save}", SDLK_RETURN, KMOD_PRIMARY, "bmed.accept" }
2779 }; 2816 };
2780 if (isUsingPanelLayout_Mobile()) { 2817 if (isUsingPanelLayout_Mobile()) {
@@ -2808,6 +2845,19 @@ iWidget *makeBookmarkEditor_Widget(void) {
2808 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0))); 2845 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.title}", "bmed.title", iClob(inputs[0] = new_InputWidget(0)));
2809 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0))); 2846 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.url}", "bmed.url", iClob(inputs[1] = new_InputWidget(0)));
2810 setUrlContent_InputWidget(inputs[1], iTrue); 2847 setUrlContent_InputWidget(inputs[1], iTrue);
2848 /* Folder to add to. */ {
2849 addChild_Widget(headings, iClob(makeHeading_Widget("${dlg.bookmark.folder}")));
2850 const iArray *folderItems = makeBookmarkFolderItems_();
2851 iLabelWidget *folderButton;
2852 setId_Widget(addChildFlags_Widget(values,
2853 iClob(folderButton = makeMenuButton_LabelWidget(
2854 widestLabel_MenuItemArray(folderItems),
2855 constData_Array(folderItems),
2856 size_Array(folderItems))), alignLeft_WidgetFlag),
2857 "bmed.folder");
2858 updateDropdownSelection_LabelWidget(
2859 folderButton, format_CStr(" arg:%u", recentFolder_Bookmarks(bookmarks_App())));
2860 }
2811 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0))); 2861 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.tags}", "bmed.tags", iClob(inputs[2] = new_InputWidget(0)));
2812 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1))); 2862 addDialogInputWithHeading_(headings, values, "${dlg.bookmark.icon}", "bmed.icon", iClob(inputs[3] = new_InputWidget(1)));
2813 /* Buttons for special tags. */ 2863 /* Buttons for special tags. */
@@ -2830,12 +2880,23 @@ iWidget *makeBookmarkEditor_Widget(void) {
2830 return dlg; 2880 return dlg;
2831} 2881}
2832 2882
2883void setBookmarkEditorFolder_Widget(iWidget *editor, uint32_t folderId) {
2884 iLabelWidget *button = findChild_Widget(editor, "bmed.folder");
2885 updateDropdownSelection_LabelWidget(button, format_CStr(" arg:%u", folderId));
2886 setUserData_Object(button, get_Bookmarks(bookmarks_App(), folderId));
2887}
2888
2833static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) { 2889static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, const char *cmd) {
2834 if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "cancel")) { 2890 if (equal_Command(cmd, "dlg.bookmark.setfolder")) {
2891 setBookmarkEditorFolder_Widget(editor, arg_Command(cmd));
2892 return iTrue;
2893 }
2894 if (equal_Command(cmd, "bmed.accept") || equal_Command(cmd, "bmed.cancel")) {
2835 if (equal_Command(cmd, "bmed.accept")) { 2895 if (equal_Command(cmd, "bmed.accept")) {
2836 const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title")); 2896 const iString *title = text_InputWidget(findChild_Widget(editor, "bmed.title"));
2837 const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url")); 2897 const iString *url = text_InputWidget(findChild_Widget(editor, "bmed.url"));
2838 const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags")); 2898 const iString *tags = text_InputWidget(findChild_Widget(editor, "bmed.tags"));
2899 const iBookmark *folder = userData_Object(findChild_Widget(editor, "bmed.folder"));
2839 const iString *icon = collect_String(trimmed_String(text_InputWidget(findChild_Widget(editor, "bmed.icon")))); 2900 const iString *icon = collect_String(trimmed_String(text_InputWidget(findChild_Widget(editor, "bmed.icon"))));
2840 const uint32_t id = add_Bookmarks(bookmarks_App(), url, title, tags, first_String(icon)); 2901 const uint32_t id = add_Bookmarks(bookmarks_App(), url, title, tags, first_String(icon));
2841 iBookmark * bm = get_Bookmarks(bookmarks_App(), id); 2902 iBookmark * bm = get_Bookmarks(bookmarks_App(), id);
@@ -2851,6 +2912,10 @@ static iBool handleBookmarkCreationCommands_SidebarWidget_(iWidget *editor, cons
2851 if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))) { 2912 if (isSelected_Widget(findChild_Widget(editor, "bmed.tag.linksplit"))) {
2852 addTag_Bookmark(bm, linkSplit_BookmarkTag); 2913 addTag_Bookmark(bm, linkSplit_BookmarkTag);
2853 } 2914 }
2915 bm->parentId = folder ? id_Bookmark(folder) : 0;
2916 if (bm->parentId) {
2917 setRecentFolder_Bookmarks(bookmarks_App(), bm->parentId);
2918 }
2854 postCommandf_App("bookmarks.changed added:%zu", id); 2919 postCommandf_App("bookmarks.changed added:%zu", id);
2855 } 2920 }
2856 setupSheetTransition_Mobile(editor, iFalse); 2921 setupSheetTransition_Mobile(editor, iFalse);
diff --git a/src/ui/util.h b/src/ui/util.h
index 52b3a692..81fb1cbd 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -311,6 +311,7 @@ iWidget * makePreferences_Widget (void);
311void updatePreferencesLayout_Widget (iWidget *prefs); 311void updatePreferencesLayout_Widget (iWidget *prefs);
312 312
313iWidget * makeBookmarkEditor_Widget (void); 313iWidget * makeBookmarkEditor_Widget (void);
314void setBookmarkEditorFolder_Widget(iWidget *editor, uint32_t folderId);
314iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon); 315iWidget * makeBookmarkCreation_Widget (const iString *url, const iString *title, iChar icon);
315iWidget * makeIdentityCreation_Widget (void); 316iWidget * makeIdentityCreation_Widget (void);
316iWidget * makeFeedSettings_Widget (uint32_t bookmarkId); 317iWidget * makeFeedSettings_Widget (uint32_t bookmarkId);
diff --git a/src/ui/window.c b/src/ui/window.c
index fa364cff..2f6fe430 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -26,7 +26,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
26#include "bookmarks.h" 26#include "bookmarks.h"
27#include "command.h" 27#include "command.h"
28#include "defs.h" 28#include "defs.h"
29#include "embedded.h" 29#include "resources.h"
30#include "keys.h" 30#include "keys.h"
31#include "labelwidget.h" 31#include "labelwidget.h"
32#include "documentwidget.h" 32#include "documentwidget.h"
@@ -583,7 +583,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
583#if defined (iPlatformLinux) 583#if defined (iPlatformLinux)
584 SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio); 584 SDL_SetWindowMinimumSize(d->base.win, minSize.x * d->base.pixelRatio, minSize.y * d->base.pixelRatio);
585 /* Load the window icon. */ { 585 /* Load the window icon. */ {
586 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, 0); 586 SDL_Surface *surf = loadImage_(&imageLagrange64_Resources, 0);
587 SDL_SetWindowIcon(d->base.win, surf); 587 SDL_SetWindowIcon(d->base.win, surf);
588 free(surf->pixels); 588 free(surf->pixels);
589 SDL_FreeSurface(surf); 589 SDL_FreeSurface(surf);
@@ -597,7 +597,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
597 setupUserInterface_MainWindow(d); 597 setupUserInterface_MainWindow(d);
598 postCommand_App("~bindings.changed"); /* update from bindings */ 598 postCommand_App("~bindings.changed"); /* update from bindings */
599 /* Load the border shadow texture. */ { 599 /* Load the border shadow texture. */ {
600 SDL_Surface *surf = loadImage_(&imageShadow_Embedded, 0); 600 SDL_Surface *surf = loadImage_(&imageShadow_Resources, 0);
601 d->base.borderShadow = SDL_CreateTextureFromSurface(d->base.render, surf); 601 d->base.borderShadow = SDL_CreateTextureFromSurface(d->base.render, surf);
602 SDL_SetTextureBlendMode(d->base.borderShadow, SDL_BLENDMODE_BLEND); 602 SDL_SetTextureBlendMode(d->base.borderShadow, SDL_BLENDMODE_BLEND);
603 free(surf->pixels); 603 free(surf->pixels);
@@ -607,7 +607,7 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
607#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 607#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
608 /* Load the app icon for drawing in the title bar. */ 608 /* Load the app icon for drawing in the title bar. */
609 if (prefs_App()->customFrame) { 609 if (prefs_App()->customFrame) {
610 SDL_Surface *surf = loadImage_(&imageLagrange64_Embedded, appIconSize_Root()); 610 SDL_Surface *surf = loadImage_(&imageLagrange64_Resources, appIconSize_Root());
611 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 611 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
612 d->appIcon = SDL_CreateTextureFromSurface(d->base.render, surf); 612 d->appIcon = SDL_CreateTextureFromSurface(d->base.render, surf);
613 free(surf->pixels); 613 free(surf->pixels);