summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt13
-rw-r--r--README.md2
m---------lib/the_Foundation0
-rw-r--r--res/about/version.gmi8
-rw-r--r--src/app.c191
-rw-r--r--src/app.h5
-rw-r--r--src/gmdocument.c52
-rw-r--r--src/gmdocument.h5
-rw-r--r--src/prefs.c23
-rw-r--r--src/prefs.h29
-rw-r--r--src/ui/documentwidget.c86
-rw-r--r--src/ui/text.c70
-rw-r--r--src/ui/text.h28
-rw-r--r--src/ui/util.c158
-rw-r--r--src/ui/util.h1
-rw-r--r--src/ui/widget.c22
-rw-r--r--src/ui/widget.h79
17 files changed, 507 insertions, 265 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f4277961..785d6b30 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -17,10 +17,9 @@
17# - `cat` is relied upon for merging all the resource files together. 17# - `cat` is relied upon for merging all the resource files together.
18 18
19cmake_minimum_required (VERSION 3.9) 19cmake_minimum_required (VERSION 3.9)
20set (CMAKE_OSX_DEPLOYMENT_TARGET 10.14)
21 20
22project (Lagrange 21project (Lagrange
23 VERSION 0.2.1 22 VERSION 0.3.0
24 DESCRIPTION "Beautiful Gemini Client" 23 DESCRIPTION "Beautiful Gemini Client"
25 LANGUAGES C 24 LANGUAGES C
26) 25)
@@ -98,6 +97,8 @@ set (SOURCES
98 src/history.h 97 src/history.h
99 src/lookup.c 98 src/lookup.c
100 src/lookup.h 99 src/lookup.h
100 src/prefs.c
101 src/prefs.h
101 src/stb_image.h 102 src/stb_image.h
102 src/stb_truetype.h 103 src/stb_truetype.h
103 src/visited.c 104 src/visited.c
@@ -177,8 +178,8 @@ if (ENABLE_X11_SWRENDER)
177endif () 178endif ()
178if (ENABLE_KERNING) 179if (ENABLE_KERNING)
179 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_KERNING=1) 180 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_KERNING=1)
180if (ENABLE_WINDOWPOS_FIX)
181endif () 181endif ()
182if (ENABLE_WINDOWPOS_FIX)
182 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_WINDOWPOS_FIX=1) 183 target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_WINDOWPOS_FIX=1)
183endif () 184endif ()
184target_link_libraries (app PUBLIC the_Foundation::the_Foundation) 185target_link_libraries (app PUBLIC the_Foundation::the_Foundation)
@@ -189,8 +190,10 @@ if (APPLE)
189 else () 190 else ()
190 target_link_libraries (app PUBLIC "-framework AppKit") 191 target_link_libraries (app PUBLIC "-framework AppKit")
191 endif () 192 endif ()
192 target_compile_options (app PUBLIC -mmacosx-version-min=10.14) 193 if (CMAKE_OSX_DEPLOYMENT_TARGET)
193 target_link_options (app PUBLIC -mmacosx-version-min=10.14) 194 target_compile_options (app PUBLIC -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
195 target_link_options (app PUBLIC -mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET})
196 endif ()
194 set_target_properties (app PROPERTIES 197 set_target_properties (app PROPERTIES
195 OUTPUT_NAME "Lagrange" 198 OUTPUT_NAME "Lagrange"
196 BUILD_RPATH ${SDL2_LIBRARY_DIRS} 199 BUILD_RPATH ${SDL2_LIBRARY_DIRS}
diff --git a/README.md b/README.md
index 5b577aee..89cd8e55 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
1# Lagrange 1# Lagrange
2 2
3Lagrange is a desktop GUI client for browsing Geminispace. It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, bookmarks, history, and page outlines. 3Lagrange is a desktop GUI client for browsing [Geminispace](https://gemini.circumlunar.space/). It offers modern conveniences familiar from web browsers, such as smooth scrolling, inline image viewing, multiple tabs, visual themes, Unicode fonts, bookmarks, history, and page outlines.
4 4
5Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a small number of essential libraries. It is written in C and uses [SDL](https://libsdl.org/) for hardware-accelerated graphics. [OpenSSL](https://openssl.org/) is used for secure communications. 5Like Gemini, Lagrange has been designed with minimalism in mind. It depends on a small number of essential libraries. It is written in C and uses [SDL](https://libsdl.org/) for hardware-accelerated graphics. [OpenSSL](https://openssl.org/) is used for secure communications.
6 6
diff --git a/lib/the_Foundation b/lib/the_Foundation
Subproject ab8f6e0769db2ec74846beea0f36561b85bf2c7 Subproject 4b041220b889a65e0dd5ec861e3c6982c968f0f
diff --git a/res/about/version.gmi b/res/about/version.gmi
index 776146db..94ddae60 100644
--- a/res/about/version.gmi
+++ b/res/about/version.gmi
@@ -6,6 +6,14 @@
6``` 6```
7# Release notes 7# Release notes
8 8
9## 0.3
10* Added "Open Link in Background Tab" to link context menu.
11* Added tabs in Preferences for better grouping.
12* Slightly smaller first paragraph font size.
13* Improved text selection behavior when starting on empty space.
14* Fixed: Centered popups do not remain centered when window is resized.
15* Fixed: Sizing and alignment of Unicode symbols in preformatted text.
16
9## 0.2.1 17## 0.2.1
10* Fixed window size/state restoration issues, e.g., after window is maximized. 18* Fixed window size/state restoration issues, e.g., after window is maximized.
11* Windows: Fixed text disappearing when window is resized. 19* Windows: Fixed text disappearing when window is resized.
diff --git a/src/app.c b/src/app.c
index 04928b84..839e8e65 100644
--- a/src/app.c
+++ b/src/app.c
@@ -94,17 +94,20 @@ struct Impl_App {
94 iTime lastDropTime; /* for detecting drops of multiple items */ 94 iTime lastDropTime; /* for detecting drops of multiple items */
95 /* Preferences: */ 95 /* Preferences: */
96 iBool commandEcho; /* --echo */ 96 iBool commandEcho; /* --echo */
97 iBool retainWindowSize; 97 iBool forceSoftwareRender; /* --sw */
98 iRect initialWindowRect; 98 iRect initialWindowRect;
99 iPrefs prefs;
100#if 0
101 iBool retainWindowSize;
99 float uiScale; 102 float uiScale;
100 int zoomPercent; 103 int zoomPercent;
101 iBool forceWrap; 104 iBool forceWrap;
102 iBool forceSoftwareRender;
103 enum iColorTheme theme; 105 enum iColorTheme theme;
104 iBool useSystemTheme; 106 iBool useSystemTheme;
105 iString gopherProxy; 107 iString gopherProxy;
106 iString httpProxy; 108 iString httpProxy;
107 iString downloadDir; 109 iString downloadDir;
110#endif
108}; 111};
109 112
110static iApp app_; 113static iApp app_;
@@ -134,8 +137,8 @@ const iString *dateStr_(const iDate *date) {
134static iString *serializePrefs_App_(const iApp *d) { 137static iString *serializePrefs_App_(const iApp *d) {
135 iString *str = new_String(); 138 iString *str = new_String();
136 const iSidebarWidget *sidebar = findWidget_App("sidebar"); 139 const iSidebarWidget *sidebar = findWidget_App("sidebar");
137 appendFormat_String(str, "window.retain arg:%d\n", d->retainWindowSize); 140 appendFormat_String(str, "window.retain arg:%d\n", d->prefs.retainWindowSize);
138 if (d->retainWindowSize) { 141 if (d->prefs.retainWindowSize) {
139 const iBool isMaximized = (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) != 0; 142 const iBool isMaximized = (SDL_GetWindowFlags(d->window->win) & SDL_WINDOW_MAXIMIZED) != 0;
140 int w, h, x, y; 143 int w, h, x, y;
141 x = d->window->lastRect.pos.x; 144 x = d->window->lastRect.pos.x;
@@ -155,17 +158,18 @@ static iString *serializePrefs_App_(const iApp *d) {
155 if (isVisible_Widget(sidebar)) { 158 if (isVisible_Widget(sidebar)) {
156 appendCStr_String(str, "sidebar.toggle\n"); 159 appendCStr_String(str, "sidebar.toggle\n");
157 } 160 }
158 if (d->forceWrap) { 161 if (d->prefs.forceLineWrap) {
159 appendFormat_String(str, "forcewrap.toggle\n"); 162 appendFormat_String(str, "forcewrap.toggle\n");
160 } 163 }
161 appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar)); 164 appendFormat_String(str, "sidebar.mode arg:%d\n", mode_SidebarWidget(sidebar));
162 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window)); 165 appendFormat_String(str, "uiscale arg:%f\n", uiScale_Window(d->window));
163 appendFormat_String(str, "zoom.set arg:%d\n", d->zoomPercent); 166 appendFormat_String(str, "zoom.set arg:%d\n", d->prefs.zoomPercent);
164 appendFormat_String(str, "theme.set arg:%d auto:1\n", d->theme); 167 appendFormat_String(str, "theme.set arg:%d auto:1\n", d->prefs.theme);
165 appendFormat_String(str, "ostheme arg:%d\n", d->useSystemTheme); 168 appendFormat_String(str, "ostheme arg:%d\n", d->prefs.useSystemTheme);
166 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->gopherProxy)); 169 appendFormat_String(str, "saturation.set arg:%d\n", (int) ((d->prefs.saturation * 100) + 0.5f));
167 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->httpProxy)); 170 appendFormat_String(str, "proxy.gopher address:%s\n", cstr_String(&d->prefs.gopherProxy));
168 appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->downloadDir)); 171 appendFormat_String(str, "proxy.http address:%s\n", cstr_String(&d->prefs.httpProxy));
172 appendFormat_String(str, "downloads path:%s\n", cstr_String(&d->prefs.downloadDir));
169 return str; 173 return str;
170} 174}
171 175
@@ -244,7 +248,7 @@ static iBool loadState_App_(iApp *d) {
244 readData_File(f, 4, magic); 248 readData_File(f, 4, magic);
245 if (!memcmp(magic, magicTabDocument_App_, 4)) { 249 if (!memcmp(magic, magicTabDocument_App_, 4)) {
246 if (!doc) { 250 if (!doc) {
247 doc = newTab_App(NULL); 251 doc = newTab_App(NULL, iTrue);
248 } 252 }
249 if (read8_File(f)) { 253 if (read8_File(f)) {
250 current = doc; 254 current = doc;
@@ -301,24 +305,18 @@ static void init_App_(iApp *d, int argc, char **argv) {
301 d->lastTickerTime = SDL_GetTicks(); 305 d->lastTickerTime = SDL_GetTicks();
302 d->elapsedSinceLastTicker = 0; 306 d->elapsedSinceLastTicker = 0;
303 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL; 307 d->commandEcho = checkArgument_CommandLine(&d->args, "echo") != NULL;
308 d->forceSoftwareRender = checkArgument_CommandLine(&d->args, "sw") != NULL;
304 d->initialWindowRect = init_Rect(-1, -1, 900, 560); 309 d->initialWindowRect = init_Rect(-1, -1, 900, 560);
305 d->theme = dark_ColorTheme; 310 init_Prefs(&d->prefs);
306 d->useSystemTheme = iTrue; 311 setCStr_String(&d->prefs.downloadDir, downloadDir_App_);
307 d->running = iFalse; 312 d->running = iFalse;
308 d->window = NULL; 313 d->window = NULL;
309 d->retainWindowSize = iTrue;
310 d->pendingRefresh = iFalse; 314 d->pendingRefresh = iFalse;
311 d->zoomPercent = 100;
312 d->forceWrap = iFalse;
313 d->forceSoftwareRender = checkArgument_CommandLine(&d->args, "sw") != NULL;
314 d->certs = new_GmCerts(dataDir_App_); 315 d->certs = new_GmCerts(dataDir_App_);
315 d->visited = new_Visited(); 316 d->visited = new_Visited();
316 d->bookmarks = new_Bookmarks(); 317 d->bookmarks = new_Bookmarks();
317 d->tabEnum = 0; /* generates unique IDs for tab pages */ 318 d->tabEnum = 0; /* generates unique IDs for tab pages */
318 init_String(&d->gopherProxy); 319 setThemePalette_Color(d->prefs.theme);
319 init_String(&d->httpProxy);
320 initCStr_String(&d->downloadDir, downloadDir_App_);
321 setThemePalette_Color(d->theme);
322#if defined (iPlatformApple) 320#if defined (iPlatformApple)
323 setupApplication_MacOS(); 321 setupApplication_MacOS();
324#endif 322#endif
@@ -392,9 +390,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
392static void deinit_App(iApp *d) { 390static void deinit_App(iApp *d) {
393 saveState_App_(d); 391 saveState_App_(d);
394 savePrefs_App_(d); 392 savePrefs_App_(d);
395 deinit_String(&d->downloadDir); 393 deinit_Prefs(&d->prefs);
396 deinit_String(&d->httpProxy);
397 deinit_String(&d->gopherProxy);
398 save_Bookmarks(d->bookmarks, dataDir_App_); 394 save_Bookmarks(d->bookmarks, dataDir_App_);
399 delete_Bookmarks(d->bookmarks); 395 delete_Bookmarks(d->bookmarks);
400 save_Visited(d->visited, dataDir_App_); 396 save_Visited(d->visited, dataDir_App_);
@@ -416,7 +412,7 @@ const iString *dataDir_App(void) {
416} 412}
417 413
418const iString *downloadDir_App(void) { 414const iString *downloadDir_App(void) {
419 return collect_String(cleaned_Path(&app_.downloadDir)); 415 return collect_String(cleaned_Path(&app_.prefs.downloadDir));
420} 416}
421 417
422const iString *debugInfo_App(void) { 418const iString *debugInfo_App(void) {
@@ -540,12 +536,12 @@ uint32_t elapsedSinceLastTicker_App(void) {
540 return app_.elapsedSinceLastTicker; 536 return app_.elapsedSinceLastTicker;
541} 537}
542 538
543int zoom_App(void) { 539const iPrefs *prefs_App(void) {
544 return app_.zoomPercent; 540 return &app_.prefs;
545} 541}
546 542
547iBool forceLineWrap_App(void) { 543iBool forceLineWrap_App(void) {
548 return app_.forceWrap; 544 return app_.prefs.forceLineWrap;
549} 545}
550 546
551iBool forceSoftwareRender_App(void) { 547iBool forceSoftwareRender_App(void) {
@@ -561,16 +557,16 @@ iBool forceSoftwareRender_App(void) {
561} 557}
562 558
563enum iColorTheme colorTheme_App(void) { 559enum iColorTheme colorTheme_App(void) {
564 return app_.theme; 560 return app_.prefs.theme;
565} 561}
566 562
567const iString *schemeProxy_App(iRangecc scheme) { 563const iString *schemeProxy_App(iRangecc scheme) {
568 iApp *d = &app_; 564 iApp *d = &app_;
569 if (equalCase_Rangecc(scheme, "gopher")) { 565 if (equalCase_Rangecc(scheme, "gopher")) {
570 return &d->gopherProxy; 566 return &d->prefs.gopherProxy;
571 } 567 }
572 if (equalCase_Rangecc(scheme, "http") || equalCase_Rangecc(scheme, "https")) { 568 if (equalCase_Rangecc(scheme, "http") || equalCase_Rangecc(scheme, "https")) {
573 return &d->httpProxy; 569 return &d->prefs.httpProxy;
574 } 570 }
575 return NULL; 571 return NULL;
576} 572}
@@ -713,7 +709,7 @@ iDocumentWidget *document_Command(const char *cmd) {
713 return document_App(); 709 return document_App();
714} 710}
715 711
716iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf) { 712iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNew) {
717 iApp *d = &app_; 713 iApp *d = &app_;
718 iWidget *tabs = findWidget_App("doctabs"); 714 iWidget *tabs = findWidget_App("doctabs");
719 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse); 715 setFlags_Widget(tabs, hidden_WidgetFlag, iFalse);
@@ -729,7 +725,9 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf) {
729 setId_Widget(as_Widget(doc), format_CStr("document%03d", ++d->tabEnum)); 725 setId_Widget(as_Widget(doc), format_CStr("document%03d", ++d->tabEnum));
730 appendTabPage_Widget(tabs, iClob(doc), "", 0, 0); 726 appendTabPage_Widget(tabs, iClob(doc), "", 0, 0);
731 addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton)); 727 addChild_Widget(findChild_Widget(tabs, "tabs.buttons"), iClob(newTabButton));
732 postCommandf_App("tabs.switch page:%p", doc); 728 if (switchToNew) {
729 postCommandf_App("tabs.switch page:%p", doc);
730 }
733 arrange_Widget(tabs); 731 arrange_Widget(tabs);
734 refresh_Widget(tabs); 732 refresh_Widget(tabs);
735 return doc; 733 return doc;
@@ -801,15 +799,66 @@ static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
801iBool handleCommand_App(const char *cmd) { 799iBool handleCommand_App(const char *cmd) {
802 iApp *d = &app_; 800 iApp *d = &app_;
803 if (equal_Command(cmd, "window.retain")) { 801 if (equal_Command(cmd, "window.retain")) {
804 d->retainWindowSize = arg_Command(cmd); 802 d->prefs.retainWindowSize = arg_Command(cmd);
805 return iTrue; 803 return iTrue;
806 } 804 }
807 else if (equal_Command(cmd, "window.maximize")) { 805 else if (equal_Command(cmd, "window.maximize")) {
808 SDL_MaximizeWindow(d->window->win); 806 SDL_MaximizeWindow(d->window->win);
809 return iTrue; 807 return iTrue;
810 } 808 }
811 else if (equal_Command(cmd, "downloads")) { 809 else if (equal_Command(cmd, "zoom.set")) {
812 setCStr_String(&d->downloadDir, suffixPtr_Command(cmd, "path")); 810 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */
811 d->prefs.zoomPercent = arg_Command(cmd);
812 setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);
813 postCommand_App("font.changed");
814 postCommand_App("window.unfreeze");
815 return iTrue;
816 }
817 else if (equal_Command(cmd, "zoom.delta")) {
818 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */
819 int delta = arg_Command(cmd);
820 if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {
821 delta /= 2;
822 }
823 d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);
824 setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f);
825 postCommand_App("font.changed");
826 postCommand_App("window.unfreeze");
827 return iTrue;
828 }
829 else if (equal_Command(cmd, "forcewrap.toggle")) {
830 d->prefs.forceLineWrap = !d->prefs.forceLineWrap;
831 updateSize_DocumentWidget(document_App());
832 return iTrue;
833 }
834 else if (equal_Command(cmd, "theme.set")) {
835 const int isAuto = argLabel_Command(cmd, "auto");
836 d->prefs.theme = arg_Command(cmd);
837 if (!isAuto) {
838 postCommand_App("ostheme arg:0");
839 }
840 setThemePalette_Color(d->prefs.theme);
841 postCommandf_App("theme.changed auto:%d", isAuto);
842 return iTrue;
843 }
844 else if (equal_Command(cmd, "ostheme")) {
845 d->prefs.useSystemTheme = arg_Command(cmd);
846 return iTrue;
847 }
848 else if (equal_Command(cmd, "saturation.set")) {
849 d->prefs.saturation = (float) arg_Command(cmd) / 100.0f;
850 postCommandf_App("theme.changed auto:1");
851 return iTrue;
852 }
853 else if (equal_Command(cmd, "proxy.gopher")) {
854 setCStr_String(&d->prefs.gopherProxy, suffixPtr_Command(cmd, "address"));
855 return iTrue;
856 }
857 else if (equal_Command(cmd, "proxy.http")) {
858 setCStr_String(&d->prefs.httpProxy, suffixPtr_Command(cmd, "address"));
859 return iTrue;
860 } else if (equal_Command(cmd, "downloads")) {
861 setCStr_String(&d->prefs.downloadDir, suffixPtr_Command(cmd, "path"));
813 return iTrue; 862 return iTrue;
814 } 863 }
815 else if (equal_Command(cmd, "open")) { 864 else if (equal_Command(cmd, "open")) {
@@ -817,14 +866,15 @@ iBool handleCommand_App(const char *cmd) {
817 iUrl parts; 866 iUrl parts;
818 init_Url(&parts, url); 867 init_Url(&parts, url);
819 if (equalCase_Rangecc(parts.scheme, "mailto") || 868 if (equalCase_Rangecc(parts.scheme, "mailto") ||
820 (isEmpty_String(&d->httpProxy) && (equalCase_Rangecc(parts.scheme, "http") || 869 (isEmpty_String(&d->prefs.httpProxy) && (equalCase_Rangecc(parts.scheme, "http") ||
821 equalCase_Rangecc(parts.scheme, "https")))) { 870 equalCase_Rangecc(parts.scheme, "https")))) {
822 openInDefaultBrowser_App(url); 871 openInDefaultBrowser_App(url);
823 return iTrue; 872 return iTrue;
824 } 873 }
825 iDocumentWidget *doc = document_Command(cmd); 874 iDocumentWidget *doc = document_Command(cmd);
826 if (argLabel_Command(cmd, "newtab")) { 875 const int newTab = argLabel_Command(cmd, "newtab");
827 doc = newTab_App(NULL); 876 if (newTab) {
877 doc = newTab_App(NULL, (newTab & 1) != 0); /* newtab:2 to open in background */
828 } 878 }
829 iHistory *history = history_DocumentWidget(doc); 879 iHistory *history = history_DocumentWidget(doc);
830 const iBool isHistory = argLabel_Command(cmd, "history") != 0; 880 const iBool isHistory = argLabel_Command(cmd, "history") != 0;
@@ -859,7 +909,7 @@ iBool handleCommand_App(const char *cmd) {
859 } 909 }
860 else if (equal_Command(cmd, "tabs.new")) { 910 else if (equal_Command(cmd, "tabs.new")) {
861 const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0; 911 const iBool isDuplicate = argLabel_Command(cmd, "duplicate") != 0;
862 newTab_App(isDuplicate ? document_App() : NULL); 912 newTab_App(isDuplicate ? document_App() : NULL, iTrue);
863 if (!isDuplicate) { 913 if (!isDuplicate) {
864 postCommand_App("navigate.home"); 914 postCommand_App("navigate.home");
865 } 915 }
@@ -908,9 +958,9 @@ iBool handleCommand_App(const char *cmd) {
908 else if (equal_Command(cmd, "preferences")) { 958 else if (equal_Command(cmd, "preferences")) {
909 iWidget *dlg = makePreferences_Widget(); 959 iWidget *dlg = makePreferences_Widget();
910 updatePrefsThemeButtons_(dlg); 960 updatePrefsThemeButtons_(dlg);
911 setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->downloadDir); 961 setText_InputWidget(findChild_Widget(dlg, "prefs.downloads"), &d->prefs.downloadDir);
912 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->useSystemTheme); 962 setToggle_Widget(findChild_Widget(dlg, "prefs.ostheme"), d->prefs.useSystemTheme);
913 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->retainWindowSize); 963 setToggle_Widget(findChild_Widget(dlg, "prefs.retainwindow"), d->prefs.retainWindowSize);
914 setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"), 964 setText_InputWidget(findChild_Widget(dlg, "prefs.uiscale"),
915 collectNewFormat_String("%g", uiScale_Window(d->window))); 965 collectNewFormat_String("%g", uiScale_Window(d->window)));
916 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"), 966 setText_InputWidget(findChild_Widget(dlg, "prefs.proxy.http"),
@@ -925,7 +975,7 @@ iBool handleCommand_App(const char *cmd) {
925 const iPtrArray *homepages = 975 const iPtrArray *homepages =
926 list_Bookmarks(d->bookmarks, NULL, filterTagsRegExp_Bookmarks, pattern); 976 list_Bookmarks(d->bookmarks, NULL, filterTagsRegExp_Bookmarks, pattern);
927 if (isEmpty_PtrArray(homepages)) { 977 if (isEmpty_PtrArray(homepages)) {
928 postCommand_App("open url:about:lagrange"); 978 postCommand_App("open url:about:lagrange");
929 } 979 }
930 else { 980 else {
931 iStringSet *urls = iClob(new_StringSet()); 981 iStringSet *urls = iClob(new_StringSet());
@@ -944,31 +994,6 @@ iBool handleCommand_App(const char *cmd) {
944 } 994 }
945 return iTrue; 995 return iTrue;
946 } 996 }
947 else if (equal_Command(cmd, "zoom.set")) {
948 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */
949 d->zoomPercent = arg_Command(cmd);
950 setContentFontSize_Text((float) d->zoomPercent / 100.0f);
951 postCommand_App("font.changed");
952 postCommand_App("window.unfreeze");
953 return iTrue;
954 }
955 else if (equal_Command(cmd, "zoom.delta")) {
956 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */
957 int delta = arg_Command(cmd);
958 if (d->zoomPercent < 100 || (delta < 0 && d->zoomPercent == 100)) {
959 delta /= 2;
960 }
961 d->zoomPercent = iClamp(d->zoomPercent + delta, 50, 200);
962 setContentFontSize_Text((float) d->zoomPercent / 100.0f);
963 postCommand_App("font.changed");
964 postCommand_App("window.unfreeze");
965 return iTrue;
966 }
967 else if (equal_Command(cmd, "forcewrap.toggle")) {
968 d->forceWrap = !d->forceWrap;
969 updateSize_DocumentWidget(document_App());
970 return iTrue;
971 }
972 else if (equal_Command(cmd, "bookmark.add")) { 997 else if (equal_Command(cmd, "bookmark.add")) {
973 iDocumentWidget *doc = document_App(); 998 iDocumentWidget *doc = document_App();
974 makeBookmarkCreation_Widget(url_DocumentWidget(doc), 999 makeBookmarkCreation_Widget(url_DocumentWidget(doc),
@@ -1003,22 +1028,8 @@ iBool handleCommand_App(const char *cmd) {
1003 postCommand_App("idents.changed"); 1028 postCommand_App("idents.changed");
1004 return iTrue; 1029 return iTrue;
1005 } 1030 }
1006 else if (equal_Command(cmd, "theme.set")) {
1007 const int isAuto = argLabel_Command(cmd, "auto");
1008 d->theme = arg_Command(cmd);
1009 if (!isAuto) {
1010 postCommand_App("ostheme arg:0");
1011 }
1012 setThemePalette_Color(d->theme);
1013 postCommandf_App("theme.changed auto:%d", isAuto);
1014 return iTrue;
1015 }
1016 else if (equal_Command(cmd, "ostheme")) {
1017 d->useSystemTheme = arg_Command(cmd);
1018 return iTrue;
1019 }
1020 else if (equal_Command(cmd, "os.theme.changed")) { 1031 else if (equal_Command(cmd, "os.theme.changed")) {
1021 if (d->useSystemTheme) { 1032 if (d->prefs.useSystemTheme) {
1022 const int dark = argLabel_Command(cmd, "dark"); 1033 const int dark = argLabel_Command(cmd, "dark");
1023 const int contrast = argLabel_Command(cmd, "contrast"); 1034 const int contrast = argLabel_Command(cmd, "contrast");
1024 postCommandf_App("theme.set arg:%d auto:1", 1035 postCommandf_App("theme.set arg:%d auto:1",
@@ -1027,14 +1038,6 @@ iBool handleCommand_App(const char *cmd) {
1027 } 1038 }
1028 return iFalse; 1039 return iFalse;
1029 } 1040 }
1030 else if (equal_Command(cmd, "proxy.gopher")) {
1031 setCStr_String(&d->gopherProxy, suffixPtr_Command(cmd, "address"));
1032 return iTrue;
1033 }
1034 else if (equal_Command(cmd, "proxy.http")) {
1035 setCStr_String(&d->httpProxy, suffixPtr_Command(cmd, "address"));
1036 return iTrue;
1037 }
1038 else { 1041 else {
1039 return iFalse; 1042 return iFalse;
1040 } 1043 }
diff --git a/src/app.h b/src/app.h
index acc3e952..db22230e 100644
--- a/src/app.h
+++ b/src/app.h
@@ -28,6 +28,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
28#include <the_Foundation/string.h> 28#include <the_Foundation/string.h>
29#include <the_Foundation/time.h> 29#include <the_Foundation/time.h>
30 30
31#include "prefs.h"
31#include "ui/color.h" 32#include "ui/color.h"
32 33
33iDeclareType(Bookmarks) 34iDeclareType(Bookmarks)
@@ -58,7 +59,7 @@ void refresh_App (void);
58iBool isRefreshPending_App (void); 59iBool isRefreshPending_App (void);
59uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ 60uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */
60 61
61int zoom_App (void); 62const iPrefs * prefs_App (void);
62iBool forceLineWrap_App (void); 63iBool forceLineWrap_App (void);
63iBool forceSoftwareRender_App(void); 64iBool forceSoftwareRender_App(void);
64enum iColorTheme colorTheme_App (void); 65enum iColorTheme colorTheme_App (void);
@@ -70,7 +71,7 @@ iBookmarks * bookmarks_App (void);
70iDocumentWidget * document_App (void); 71iDocumentWidget * document_App (void);
71iObjectList * listDocuments_App (void); 72iObjectList * listDocuments_App (void);
72iDocumentWidget * document_Command (const char *cmd); 73iDocumentWidget * document_Command (const char *cmd);
73iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf); 74iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew);
74 75
75iAny * findWidget_App (const char *id); 76iAny * findWidget_App (const char *id);
76void addTicker_App (void (*ticker)(iAny *), iAny *context); 77void addTicker_App (void (*ticker)(iAny *), iAny *context);
diff --git a/src/gmdocument.c b/src/gmdocument.c
index 4d4ba603..4414b04f 100644
--- a/src/gmdocument.c
+++ b/src/gmdocument.c
@@ -331,6 +331,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
331 static const char *globe = "\U0001f310"; 331 static const char *globe = "\U0001f310";
332 static const char *quote = "\u201c"; 332 static const char *quote = "\u201c";
333 const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */ 333 const float midRunSkip = 0; /*0.120f;*/ /* extra space between wrapped text/quote lines */
334 const iPrefs *prefs = prefs_App();
334 clear_Array(&d->layout); 335 clear_Array(&d->layout);
335 clearLinks_GmDocument_(d); 336 clearLinks_GmDocument_(d);
336 clear_Array(&d->headings); 337 clear_Array(&d->headings);
@@ -341,7 +342,7 @@ static void doLayout_GmDocument_(iGmDocument *d) {
341 const iRangecc content = range_String(&d->source); 342 const iRangecc content = range_String(&d->source);
342 iRangecc contentLine = iNullRange; 343 iRangecc contentLine = iNullRange;
343 iInt2 pos = zero_I2(); 344 iInt2 pos = zero_I2();
344 iBool isFirstText = isGemini; 345 iBool isFirstText = isGemini && prefs->bigFirstParagraph;
345 iBool addQuoteIcon = iTrue; 346 iBool addQuoteIcon = iTrue;
346 iBool isPreformat = iFalse; 347 iBool isPreformat = iFalse;
347 iRangecc preAltText = iNullRange; 348 iRangecc preAltText = iNullRange;
@@ -620,7 +621,8 @@ void init_GmDocument(iGmDocument *d) {
620 init_String(&d->title); 621 init_String(&d->title);
621 init_Array(&d->headings, sizeof(iGmHeading)); 622 init_Array(&d->headings, sizeof(iGmHeading));
622 init_PtrArray(&d->images); 623 init_PtrArray(&d->images);
623 setThemeSeed_GmDocument(d, NULL); 624 d->themeSeed = 0;
625 d->siteIcon = 0;
624} 626}
625 627
626void deinit_GmDocument(iGmDocument *d) { 628void deinit_GmDocument(iGmDocument *d) {
@@ -649,9 +651,11 @@ void reset_GmDocument(iGmDocument *d) {
649} 651}
650 652
651void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { 653void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
652 const float saturationLevel = 1.0f; /* TODO: user setting */ 654 const iPrefs * prefs = prefs_App();
653 const iBool isLightMode = isLight_ColorTheme(colorTheme_App()); 655 enum iGmDocumentTheme theme =
654 const iBool isDarkMode = !isLightMode; 656 (isDark_ColorTheme(colorTheme_App()) ? prefs->docThemeDark : prefs->docThemeLight);
657// const iBool isLightMode = isLight_ColorTheme(colorTheme_App());
658// const iBool isDarkMode = !isLightMode;
655 static const iChar siteIcons[] = { 659 static const iChar siteIcons[] = {
656 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606, 660 0x203b, 0x2042, 0x205c, 0x2182, 0x25ed, 0x2600, 0x2601, 0x2604, 0x2605, 0x2606,
657 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728, 661 0x265c, 0x265e, 0x2690, 0x2691, 0x2693, 0x2698, 0x2699, 0x26f0, 0x270e, 0x2728,
@@ -661,7 +665,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
661 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c, 665 0x1f533, 0x1f657, 0x1f659, 0x1f665, 0x1f668, 0x1f66b, 0x1f78b, 0x1f796, 0x1f79c,
662 }; 666 };
663 /* Default colors. */ { 667 /* Default colors. */ {
664 if (isDarkMode) { 668 if (theme == colorfulDark_GmDocumentTheme) {
665 const iHSLColor base = { 200, 0, 0.15f, 1.0f }; 669 const iHSLColor base = { 200, 0, 0.15f, 1.0f };
666 setHsl_Color(tmBackground_ColorId, base); 670 setHsl_Color(tmBackground_ColorId, base);
667 set_Color(tmParagraph_ColorId, get_Color(gray75_ColorId)); 671 set_Color(tmParagraph_ColorId, get_Color(gray75_ColorId));
@@ -783,7 +787,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
783 // printf("background: %d %f %f\n", (int) base.hue, base.sat, base.lum); 787 // printf("background: %d %f %f\n", (int) base.hue, base.sat, base.lum);
784 // printf("isDarkBgSat: %d\n", isDarkBgSat); 788 // printf("isDarkBgSat: %d\n", isDarkBgSat);
785 789
786 if (isDarkMode) { 790 if (theme == colorfulDark_GmDocumentTheme) {
787 iHSLColor base = { hues[primIndex], 791 iHSLColor base = { hues[primIndex],
788 0.8f * (d->themeSeed >> 24) / 255.0f, 792 0.8f * (d->themeSeed >> 24) / 255.0f,
789 0.06f + 0.09f * ((d->themeSeed >> 5) & 0x7) / 7.0f, 793 0.06f + 0.09f * ((d->themeSeed >> 5) & 0x7) / 7.0f,
@@ -835,7 +839,7 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
835 /* Adjust colors based on light/dark mode. */ 839 /* Adjust colors based on light/dark mode. */
836 for (int i = tmFirst_ColorId; i < max_ColorId; i++) { 840 for (int i = tmFirst_ColorId; i < max_ColorId; i++) {
837 iHSLColor color = hsl_Color(get_Color(i)); 841 iHSLColor color = hsl_Color(get_Color(i));
838 if (isLightMode) { 842 if (theme == white_GmDocumentTheme) {
839#if 0 843#if 0
840 if (isLink_ColorId(i)) continue; 844 if (isLink_ColorId(i)) continue;
841 color.lum = 1.0f - color.lum; /* All colors invert lightness. */ 845 color.lum = 1.0f - color.lum; /* All colors invert lightness. */
@@ -893,7 +897,9 @@ void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) {
893 } 897 }
894 } 898 }
895 /* Modify overall saturation. */ 899 /* Modify overall saturation. */
896 color.sat *= saturationLevel; 900 if (!isLink_ColorId(i)) {
901 color.sat *= prefs->saturation;
902 }
897 setHsl_Color(i, color); 903 setHsl_Color(i, color);
898 } 904 }
899 } 905 }
@@ -1082,13 +1088,29 @@ iRangecc findTextBefore_GmDocument(const iGmDocument *d, const iString *text, co
1082 1088
1083const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { 1089const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) {
1084 /* TODO: Perf optimization likely needed; use a block map? */ 1090 /* TODO: Perf optimization likely needed; use a block map? */
1091 const iGmRun *last = NULL;
1092 iBool isFirstNonDecoration = iTrue;
1085 iConstForEach(Array, i, &d->layout) { 1093 iConstForEach(Array, i, &d->layout) {
1086 const iGmRun *run = i.value; 1094 const iGmRun *run = i.value;
1087 if (contains_Rect(run->bounds, pos)) { 1095 if (run->flags & decoration_GmRunFlag) continue;
1088 return run; 1096 const iRangei span = ySpan_Rect(run->bounds);
1097 if (contains_Range(&span, pos.y)) {
1098 last = run;
1099 break;
1089 } 1100 }
1101 if (isFirstNonDecoration && pos.y < top_Rect(run->bounds)) {
1102 last = run;
1103 break;
1104 }
1105 if (top_Rect(run->bounds) > pos.y) break; /* Below the point. */
1106 last = run;
1107 isFirstNonDecoration = iFalse;
1090 } 1108 }
1091 return NULL; 1109// if (last) {
1110// printf("found run at (%d,%d): %p [%s]\n", pos.x, pos.y, last, cstr_Rangecc(last->text));
1111// fflush(stdout);
1112// }
1113 return last;
1092} 1114}
1093 1115
1094const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) { 1116const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) {
@@ -1228,7 +1250,13 @@ iChar siteIcon_GmDocument(const iGmDocument *d) {
1228} 1250}
1229 1251
1230const char *findLoc_GmRun(const iGmRun *d, iInt2 pos) { 1252const char *findLoc_GmRun(const iGmRun *d, iInt2 pos) {
1253 if (pos.y < top_Rect(d->bounds)) {
1254 return d->text.start;
1255 }
1231 const int x = pos.x - left_Rect(d->bounds); 1256 const int x = pos.x - left_Rect(d->bounds);
1257 if (x <= 0) {
1258 return d->text.start;
1259 }
1232 const char *loc; 1260 const char *loc;
1233 tryAdvanceNoWrap_Text(d->font, d->text, x, &loc); 1261 tryAdvanceNoWrap_Text(d->font, d->text, x, &loc);
1234 return loc; 1262 return loc;
diff --git a/src/gmdocument.h b/src/gmdocument.h
index b453fba9..b16df677 100644
--- a/src/gmdocument.h
+++ b/src/gmdocument.h
@@ -35,6 +35,11 @@ iDeclareType(GmImageInfo)
35iDeclareType(GmHeading) 35iDeclareType(GmHeading)
36iDeclareType(GmRun) 36iDeclareType(GmRun)
37 37
38enum iGmDocumentTheme {
39 colorfulDark_GmDocumentTheme,
40 white_GmDocumentTheme,
41};
42
38typedef uint16_t iGmLinkId; 43typedef uint16_t iGmLinkId;
39 44
40enum iGmLinkFlags { 45enum iGmLinkFlags {
diff --git a/src/prefs.c b/src/prefs.c
new file mode 100644
index 00000000..d773acbd
--- /dev/null
+++ b/src/prefs.c
@@ -0,0 +1,23 @@
1#include "prefs.h"
2
3void init_Prefs(iPrefs *d) {
4 d->theme = dark_ColorTheme;
5 d->useSystemTheme = iTrue;
6 d->retainWindowSize = iTrue;
7 d->zoomPercent = 100;
8 d->forceLineWrap = iFalse;
9 d->lineWidth = 40;
10 d->bigFirstParagraph = iTrue;
11 d->docThemeDark = colorfulDark_GmDocumentTheme;
12 d->docThemeLight = white_GmDocumentTheme;
13 d->saturation = 1.0f;
14 init_String(&d->gopherProxy);
15 init_String(&d->httpProxy);
16 init_String(&d->downloadDir);
17}
18
19void deinit_Prefs(iPrefs *d) {
20 deinit_String(&d->gopherProxy);
21 deinit_String(&d->httpProxy);
22 deinit_String(&d->downloadDir);
23}
diff --git a/src/prefs.h b/src/prefs.h
new file mode 100644
index 00000000..d4e300ec
--- /dev/null
+++ b/src/prefs.h
@@ -0,0 +1,29 @@
1#pragma once
2#include <the_Foundation/string.h>
3
4#include "ui/color.h"
5#include "gmdocument.h"
6
7/* User preferences */
8
9iDeclareType(Prefs)
10
11struct Impl_Prefs {
12 iBool retainWindowSize;
13 float uiScale;
14 int zoomPercent;
15 iBool useSystemTheme;
16 enum iColorTheme theme;
17 iString gopherProxy;
18 iString httpProxy;
19 iString downloadDir;
20 /* Content */
21 int lineWidth;
22 iBool bigFirstParagraph;
23 iBool forceLineWrap;
24 enum iGmDocumentTheme docThemeDark;
25 enum iGmDocumentTheme docThemeLight;
26 float saturation;
27};
28
29iDeclareTypeConstruction(Prefs)
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index 70e66180..69dde8c7 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -60,8 +60,8 @@ struct Impl_MediaRequest {
60 60
61static void updated_MediaRequest_(iAnyObject *obj) { 61static void updated_MediaRequest_(iAnyObject *obj) {
62 iMediaRequest *d = obj; 62 iMediaRequest *d = obj;
63 postCommandf_App("media.updated link:%u request:%p", d->linkId, d); 63 postCommandf_App("media.updated link:%u request:%p", d->linkId, d);
64 } 64}
65 65
66static void finished_MediaRequest_(iAnyObject *obj) { 66static void finished_MediaRequest_(iAnyObject *obj) {
67 iMediaRequest *d = obj; 67 iMediaRequest *d = obj;
@@ -243,10 +243,11 @@ static void resetSmoothScroll_DocumentWidget_(iDocumentWidget *d) {
243} 243}
244 244
245static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { 245static int documentWidth_DocumentWidget_(const iDocumentWidget *d) {
246 const iWidget *w = constAs_Widget(d); 246 const iWidget *w = constAs_Widget(d);
247 const iRect bounds = bounds_Widget(w); 247 const iRect bounds = bounds_Widget(w);
248 const iPrefs * prefs = prefs_App();
248 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2, 249 return iMini(bounds.size.x - gap_UI * d->pageMargin * 2,
249 fontSize_UI * 40 * zoom_App() / 100); /* TODO: Add user preference .*/ 250 fontSize_UI * prefs->lineWidth * prefs->zoomPercent / 100);
250} 251}
251 252
252static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { 253static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) {
@@ -369,9 +370,9 @@ static void updateHover_DocumentWidget_(iDocumentWidget *d, iInt2 mouse) {
369 if (d->hoverLink && 370 if (d->hoverLink &&
370 linkFlags_GmDocument(d->doc, d->hoverLink->linkId) & permanent_GmLinkFlag) { 371 linkFlags_GmDocument(d->doc, d->hoverLink->linkId) & permanent_GmLinkFlag) {
371 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); /* not dismissable */ 372 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); /* not dismissable */
373 }
372 } 374 }
373} 375}
374}
375 376
376static void updateVisible_DocumentWidget_(iDocumentWidget *d) { 377static void updateVisible_DocumentWidget_(iDocumentWidget *d) {
377 const iRangei visRange = visibleRange_DocumentWidget_(d); 378 const iRangei visRange = visibleRange_DocumentWidget_(d);
@@ -573,7 +574,9 @@ static void updateDocument_DocumentWidget_(iDocumentWidget *d, const iGmResponse
573 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) { 574 if (category_GmStatusCode(statusCode) != categoryInput_GmStatusCode) {
574 iString str; 575 iString str;
575 invalidate_DocumentWidget_(d); 576 invalidate_DocumentWidget_(d);
576 updateTheme_DocumentWidget_(d); 577 if (document_App() == d) {
578 updateTheme_DocumentWidget_(d);
579 }
577 clear_String(&d->sourceMime); 580 clear_String(&d->sourceMime);
578// set_Block(&d->sourceContent, &response->body); 581// set_Block(&d->sourceContent, &response->body);
579 initBlock_String(&str, &response->body); 582 initBlock_String(&str, &response->body);
@@ -1208,7 +1211,7 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1208 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url)); 1211 "document.request.cancelled doc:%p url:%s", d, cstr_String(d->mod.url));
1209 iReleasePtr(&d->request); 1212 iReleasePtr(&d->request);
1210 if (d->state != ready_RequestState) { 1213 if (d->state != ready_RequestState) {
1211 d->state = ready_RequestState; 1214 d->state = ready_RequestState;
1212 postCommand_App("navigate.back"); 1215 postCommand_App("navigate.back");
1213 } 1216 }
1214 updateFetchProgress_DocumentWidget_(d); 1217 updateFetchProgress_DocumentWidget_(d);
@@ -1256,9 +1259,9 @@ static iBool handleCommand_DocumentWidget_(iDocumentWidget *d, const char *cmd)
1256 } 1259 }
1257 else if (startsWith_String(&d->sourceMime, "image/")) { 1260 else if (startsWith_String(&d->sourceMime, "image/")) {
1258 appendCStr_String(savePath, cstr_String(&d->sourceMime) + 6); 1261 appendCStr_String(savePath, cstr_String(&d->sourceMime) + 6);
1262 }
1263 }
1259 if (fileExists_FileInfo(savePath)) { 1264 if (fileExists_FileInfo(savePath)) {
1260 }
1261 }
1262 /* Make it unique. */ 1265 /* Make it unique. */
1263 iDate now; 1266 iDate now;
1264 initCurrent_Date(&now); 1267 initCurrent_Date(&now);
@@ -1550,38 +1553,39 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1550 iArray items; 1553 iArray items;
1551 init_Array(&items, sizeof(iMenuItem)); 1554 init_Array(&items, sizeof(iMenuItem));
1552 if (d->contextLink) { 1555 if (d->contextLink) {
1556 const iString *linkUrl = linkUrl_GmDocument(d->doc, d->contextLink->linkId);
1553 pushBackN_Array( 1557 pushBackN_Array(
1554 &items, 1558 &items,
1555 (iMenuItem[]){ { "Open Link in New Tab", 1559 (iMenuItem[]){
1560 { "Open Link in New Tab",
1556 0, 1561 0,
1557 0, 1562 0,
1558 format_CStr("!open newtab:1 url:%s", 1563 format_CStr("!open newtab:1 url:%s", cstr_String(linkUrl)) },
1559 cstr_String(linkUrl_GmDocument( 1564 { "Open Link in Background Tab",
1560 d->doc, d->contextLink->linkId))) },
1561 { "---", 0, 0, NULL },
1562 { "Copy Link",
1563 0, 1565 0,
1564 0, 1566 0,
1565 "document.copylink" }}, 1567 format_CStr("!open newtab:2 url:%s", cstr_String(linkUrl)) },
1566 3); 1568 { "---", 0, 0, NULL },
1569 { "Copy Link", 0, 0, "document.copylink" } },
1570 4);
1567 } 1571 }
1568 else { 1572 else {
1569 if (!isEmpty_Range(&d->selectMark)) { 1573 if (!isEmpty_Range(&d->selectMark)) {
1570 pushBackN_Array( 1574 pushBackN_Array(
1571 &items, 1575 &items,
1572 (iMenuItem[]){ { "Copy", 0, 0, "copy" }, { "---", 0, 0, NULL } }, 1576 (iMenuItem[]){ { "Copy", 0, 0, "copy" }, { "---", 0, 0, NULL } },
1573 2); 1577 2);
1574 } 1578 }
1575 pushBackN_Array( 1579 pushBackN_Array(
1576 &items, 1580 &items,
1577 (iMenuItem[]){ 1581 (iMenuItem[]){
1578 { "Go Back", navigateBack_KeyShortcut, "navigate.back" }, 1582 { "Go Back", navigateBack_KeyShortcut, "navigate.back" },
1579 { "Go Forward", navigateForward_KeyShortcut, "navigate.forward" }, 1583 { "Go Forward", navigateForward_KeyShortcut, "navigate.forward" },
1580 { "Reload Page", reload_KeyShortcut, "navigate.reload" }, 1584 { "Reload Page", reload_KeyShortcut, "navigate.reload" },
1581 { "---", 0, 0, NULL }, 1585 { "---", 0, 0, NULL },
1582 { "Copy Page URL", 0, 0, "document.copylink" }, 1586 { "Copy Page URL", 0, 0, "document.copylink" },
1583 { "---", 0, 0, NULL } }, 1587 { "---", 0, 0, NULL } },
1584 6); 1588 6);
1585 if (isEmpty_Range(&d->selectMark)) { 1589 if (isEmpty_Range(&d->selectMark)) {
1586 pushBackN_Array( 1590 pushBackN_Array(
1587 &items, 1591 &items,
@@ -1617,6 +1621,9 @@ static iBool processEvent_DocumentWidget_(iDocumentWidget *d, const SDL_Event *e
1617 else if (loc) { 1621 else if (loc) {
1618 d->selectMark.end = loc; 1622 d->selectMark.end = loc;
1619 } 1623 }
1624// printf("mark %zu ... %zu\n", d->selectMark.start - cstr_String(source_GmDocument(d->doc)),
1625// d->selectMark.end - cstr_String(source_GmDocument(d->doc)));
1626// fflush(stdout);
1620 refresh_Widget(w); 1627 refresh_Widget(w);
1621 return iTrue; 1628 return iTrue;
1622 } 1629 }
@@ -1702,7 +1709,8 @@ static void fillRange_DrawContext_(iDrawContext *d, const iGmRun *run, enum iCol
1702 /* Selection may be done in either direction. */ 1709 /* Selection may be done in either direction. */
1703 iSwap(const char *, mark.start, mark.end); 1710 iSwap(const char *, mark.start, mark.end);
1704 } 1711 }
1705 if ((!*isInside && contains_Range(&run->text, mark.start)) || *isInside) { 1712 if ((!*isInside && (contains_Range(&run->text, mark.start) || mark.start == run->text.end)) ||
1713 *isInside) {
1706 int x = 0; 1714 int x = 0;
1707 if (!*isInside) { 1715 if (!*isInside) {
1708 x = advanceRange_Text(run->font, (iRangecc){ run->text.start, mark.start }).x; 1716 x = advanceRange_Text(run->font, (iRangecc){ run->text.start, mark.start }).x;
@@ -1873,23 +1881,23 @@ static void drawRun_DrawContext_(void *context, const iGmRun *run) {
1873 (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) || 1881 (flags & (imageFileExtension_GmLinkFlag | audioFileExtension_GmLinkFlag) ||
1874 showHost)) { 1882 showHost)) {
1875 format_String(&str, 1883 format_String(&str,
1876 " \u2014%s%s%s\r%c%s", 1884 " \u2014%s%s%s\r%c%s",
1877 showHost ? " " : "", 1885 showHost ? " " : "",
1878 showHost ? (flags & mailto_GmLinkFlag 1886 showHost ? (flags & mailto_GmLinkFlag
1879 ? cstr_String(url) 1887 ? cstr_String(url)
1880 : ~flags & gemini_GmLinkFlag 1888 : ~flags & gemini_GmLinkFlag
1881 ? format_CStr("%s://%s", 1889 ? format_CStr("%s://%s",
1882 cstr_Rangecc(parts.scheme), 1890 cstr_Rangecc(parts.scheme),
1883 cstr_Rangecc(parts.host)) 1891 cstr_Rangecc(parts.host))
1884 : cstr_Rangecc(parts.host)) 1892 : cstr_Rangecc(parts.host))
1885 : "", 1893 : "",
1886 showHost && (showImage || showAudio) ? " \u2014" : "", 1894 showHost && (showImage || showAudio) ? " \u2014" : "",
1887 showImage || showAudio 1895 showImage || showAudio
1888 ? asciiBase_ColorEscape + fg 1896 ? asciiBase_ColorEscape + fg
1889 : (asciiBase_ColorEscape + 1897 : (asciiBase_ColorEscape +
1890 linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)), 1898 linkColor_GmDocument(doc, run->linkId, domain_GmLinkPart)),
1891 showImage ? " View Image \U0001f5bc" 1899 showImage ? " View Image \U0001f5bc"
1892 : showAudio ? " Play Audio \U0001f3b5" : ""); 1900 : showAudio ? " Play Audio \U0001f3b5" : "");
1893 } 1901 }
1894 if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) { 1902 if (run->flags & endOfLine_GmRunFlag && flags & visited_GmLinkFlag) {
1895 iDate date; 1903 iDate date;
diff --git a/src/ui/text.c b/src/ui/text.c
index a48d0e0e..8d4661ac 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -167,33 +167,45 @@ static void initFonts_Text_(iText *d) {
167 { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, 167 { &fontSourceSansProRegular_Embedded, fontSize_UI, defaultSymbols_FontId },
168 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, 168 { &fontSourceSansProRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId },
169 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId }, 169 { &fontFiraMonoRegular_Embedded, fontSize_UI * 0.866f, defaultSymbols_FontId },
170 /* content fonts */
170 { &fontNunitoRegular_Embedded, textSize, symbols_FontId }, 171 { &fontNunitoRegular_Embedded, textSize, symbols_FontId },
171 { &fontFiraMonoRegular_Embedded, monoSize, smallSymbols_FontId }, 172 { &fontFiraMonoRegular_Embedded, monoSize, monospaceSymbols_FontId },
172 { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, smallSymbols_FontId }, 173 { &fontFiraMonoRegular_Embedded, monoSize * 0.750f, monospaceSmallSymbols_FontId },
173 { &fontNunitoRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 174 { &fontNunitoRegular_Embedded, textSize * 1.200f, mediumSymbols_FontId },
175 { &fontNunitoRegular_Embedded, textSize * 1.333f, bigSymbols_FontId },
174 { &fontNunitoLightItalic_Embedded, textSize, symbols_FontId }, 176 { &fontNunitoLightItalic_Embedded, textSize, symbols_FontId },
175 { &fontNunitoExtraBold_Embedded, textSize, symbols_FontId }, 177 { &fontNunitoExtraBold_Embedded, textSize, symbols_FontId },
176 { &fontNunitoExtraBold_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 178 { &fontNunitoExtraBold_Embedded, textSize * 1.333f, mediumSymbols_FontId },
177 { &fontNunitoExtraBold_Embedded, textSize * 1.666f, largeSymbols_FontId }, 179 { &fontNunitoExtraBold_Embedded, textSize * 1.666f, largeSymbols_FontId },
178 { &fontNunitoExtraBold_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 180 { &fontNunitoExtraBold_Embedded, textSize * 2.000f, hugeSymbols_FontId },
179 { &fontNunitoExtraLight_Embedded, textSize * 1.666f, largeSymbols_FontId }, 181 { &fontNunitoExtraLight_Embedded, textSize * 1.666f, largeSymbols_FontId },
182 /* symbol fonts */
180 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId }, 183 { &fontSymbola_Embedded, fontSize_UI, defaultSymbols_FontId },
181 { &fontSymbola_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, 184 { &fontSymbola_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId },
182 { &fontSymbola_Embedded, textSize, symbols_FontId }, 185 { &fontSymbola_Embedded, textSize, symbols_FontId },
183 { &fontSymbola_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 186 { &fontSymbola_Embedded, textSize * 1.200f, mediumSymbols_FontId },
187 { &fontSymbola_Embedded, textSize * 1.333f, bigSymbols_FontId },
184 { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId }, 188 { &fontSymbola_Embedded, textSize * 1.666f, largeSymbols_FontId },
185 { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 189 { &fontSymbola_Embedded, textSize * 2.000f, hugeSymbols_FontId },
186 { &fontSymbola_Embedded, textSize * 0.866f, smallSymbols_FontId }, 190 { &fontSymbola_Embedded, monoSize, monospaceSymbols_FontId },
191 { &fontSymbola_Embedded, monoSize * 0.750f, monospaceSmallSymbols_FontId },
192 /* emoji fonts */
187 { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId }, 193 { &fontNotoEmojiRegular_Embedded, fontSize_UI, defaultSymbols_FontId },
188 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId }, 194 { &fontNotoEmojiRegular_Embedded, fontSize_UI * 1.125f, defaultMediumSymbols_FontId },
189 { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId }, 195 { &fontNotoEmojiRegular_Embedded, textSize, symbols_FontId },
190 { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 196 { &fontNotoEmojiRegular_Embedded, textSize * 1.200f, mediumSymbols_FontId },
197 { &fontNotoEmojiRegular_Embedded, textSize * 1.333f, bigSymbols_FontId },
191 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId }, 198 { &fontNotoEmojiRegular_Embedded, textSize * 1.666f, largeSymbols_FontId },
192 { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 199 { &fontNotoEmojiRegular_Embedded, textSize * 2.000f, hugeSymbols_FontId },
193 { &fontNotoEmojiRegular_Embedded, textSize * 0.866f, smallSymbols_FontId }, 200 { &fontNotoEmojiRegular_Embedded, monoSize, monospaceSymbols_FontId },
194 { &fontKosugiMaruRegular_Embedded, textSize * 0.666f, smallSymbols_FontId }, 201 { &fontNotoEmojiRegular_Embedded, monoSize * 0.750f, monospaceSmallSymbols_FontId },
202 /* japanese fonts */
203 { &fontKosugiMaruRegular_Embedded, fontSize_UI, defaultSymbols_FontId },
204 { &fontKosugiMaruRegular_Embedded, monoSize * 0.750, monospaceSmallSymbols_FontId },
205 { &fontKosugiMaruRegular_Embedded, monoSize, monospaceSymbols_FontId },
195 { &fontKosugiMaruRegular_Embedded, textSize, symbols_FontId }, 206 { &fontKosugiMaruRegular_Embedded, textSize, symbols_FontId },
196 { &fontKosugiMaruRegular_Embedded, textSize * 1.333f, mediumSymbols_FontId }, 207 { &fontKosugiMaruRegular_Embedded, textSize * 1.200f, mediumSymbols_FontId },
208 { &fontKosugiMaruRegular_Embedded, textSize * 1.333f, bigSymbols_FontId },
197 { &fontKosugiMaruRegular_Embedded, textSize * 1.666f, largeSymbols_FontId }, 209 { &fontKosugiMaruRegular_Embedded, textSize * 1.666f, largeSymbols_FontId },
198 { &fontKosugiMaruRegular_Embedded, textSize * 2.000f, hugeSymbols_FontId }, 210 { &fontKosugiMaruRegular_Embedded, textSize * 2.000f, hugeSymbols_FontId },
199 }; 211 };
@@ -211,13 +223,17 @@ static void initFonts_Text_(iText *d) {
211 /* Everything defaults to the regular sized japanese font, so these are just 223 /* Everything defaults to the regular sized japanese font, so these are just
212 the other sizes. */ 224 the other sizes. */
213 /* TODO: Add these to the table above... */ 225 /* TODO: Add these to the table above... */
214 font_Text_(monospace_FontId)->japaneseFont = smallJapanese_FontId; 226 font_Text_(default_FontId)->japaneseFont = defaultJapanese_FontId;
215 font_Text_(monospaceSmall_FontId)->japaneseFont = smallJapanese_FontId; 227 font_Text_(defaultMedium_FontId)->japaneseFont = defaultJapanese_FontId;
216 font_Text_(medium_FontId)->japaneseFont = mediumJapanese_FontId; 228 font_Text_(defaultMonospace_FontId)->japaneseFont = defaultJapanese_FontId;
217 font_Text_(mediumBold_FontId)->japaneseFont = mediumJapanese_FontId; 229 font_Text_(monospaceSmall_FontId)->japaneseFont = monospaceSmallJapanese_FontId;
218 font_Text_(largeBold_FontId)->japaneseFont = largeJapanese_FontId; 230 font_Text_(monospace_FontId)->japaneseFont = monospaceJapanese_FontId;
219 font_Text_(largeLight_FontId)->japaneseFont = largeJapanese_FontId; 231 font_Text_(medium_FontId)->japaneseFont = mediumJapanese_FontId;
220 font_Text_(hugeBold_FontId)->japaneseFont = hugeJapanese_FontId; 232 font_Text_(big_FontId)->japaneseFont = bigJapanese_FontId;
233 font_Text_(bigBold_FontId)->japaneseFont = bigJapanese_FontId;
234 font_Text_(largeBold_FontId)->japaneseFont = largeJapanese_FontId;
235 font_Text_(largeLight_FontId)->japaneseFont = largeJapanese_FontId;
236 font_Text_(hugeBold_FontId)->japaneseFont = hugeJapanese_FontId;
221 } 237 }
222 gap_Text = iRound(gap_UI * d->contentFontSize); 238 gap_Text = iRound(gap_UI * d->contentFontSize);
223} 239}
@@ -520,8 +536,8 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
520 } 536 }
521 } 537 }
522 iChar ch = nextChar_(&chPos, text.end); 538 iChar ch = nextChar_(&chPos, text.end);
523 if (ch == variationSelectorEmoji_Char) { 539 if (isVariationSelector_Char(ch)) {
524 /* TODO: Should peek ahead for this and prefer the Emoji font. */ 540 /* TODO: VS15: Should peek ahead for this and prefer the Emoji font. */
525 ch = nextChar_(&chPos, text.end); /* just ignore */ 541 ch = nextChar_(&chPos, text.end); /* just ignore */
526 } 542 }
527 /* Special instructions. */ { 543 /* Special instructions. */ {
@@ -555,10 +571,10 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
555 } 571 }
556 break; 572 break;
557 } 573 }
558 const SDL_Rect dst = { x1 + glyph->d[hoff].x, 574 SDL_Rect dst = { x1 + glyph->d[hoff].x,
559 pos.y + glyph->font->baseline + glyph->d[hoff].y, 575 pos.y + glyph->font->baseline + glyph->d[hoff].y,
560 glyph->rect[hoff].size.x, 576 glyph->rect[hoff].size.x,
561 glyph->rect[hoff].size.y }; 577 glyph->rect[hoff].size.y };
562 /* Update the bounding box. */ 578 /* Update the bounding box. */
563 if (mode == measureVisual_RunMode) { 579 if (mode == measureVisual_RunMode) {
564 if (isEmpty_Rect(bounds)) { 580 if (isEmpty_Rect(bounds)) {
@@ -572,13 +588,19 @@ static iRect run_Font_(iFont *d, enum iRunMode mode, iRangecc text, size_t maxLe
572 bounds.size.x = iMax(bounds.size.x, x2 - orig.x); 588 bounds.size.x = iMax(bounds.size.x, x2 - orig.x);
573 bounds.size.y = iMax(bounds.size.y, pos.y + glyph->font->height - orig.y); 589 bounds.size.y = iMax(bounds.size.y, pos.y + glyph->font->height - orig.y);
574 } 590 }
591 const iBool useMonoAdvance =
592 monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font));
593 const float advance = (useMonoAdvance ? monoAdvance : glyph->advance);
575 if (!isMeasuring_(mode)) { 594 if (!isMeasuring_(mode)) {
595 if (useMonoAdvance && dst.w > advance) {
596 dst.x -= (dst.w - advance) / 2;
597
598 }
576 SDL_RenderCopy(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect[hoff], &dst); 599 SDL_RenderCopy(text_.render, text_.cache, (const SDL_Rect *) &glyph->rect[hoff], &dst);
577 } 600 }
578 /* Symbols and emojis are NOT monospaced, so must conform when the primary font 601 /* Symbols and emojis are NOT monospaced, so must conform when the primary font
579 is monospaced. Except with Japanese script, that's larger than the normal monospace. */ 602 is monospaced. Except with Japanese script, that's larger than the normal monospace. */
580 xpos += (monoAdvance > 0 && !isJapanese_FontId(fontId_Text_(glyph->font)) ? monoAdvance 603 xpos += advance;
581 : glyph->advance);
582 xposMax = iMax(xposMax, xpos); 604 xposMax = iMax(xposMax, xpos);
583 if (continueFrom_out && (mode == measureNoWrap_RunMode || isWrapBoundary_(prevCh, ch))) { 605 if (continueFrom_out && (mode == measureNoWrap_RunMode || isWrapBoundary_(prevCh, ch))) {
584 lastWordEnd = chPos; 606 lastWordEnd = chPos;
diff --git a/src/ui/text.h b/src/ui/text.h
index 9a22620f..7dc4aa38 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -27,17 +27,21 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
27 27
28#include <SDL_render.h> 28#include <SDL_render.h>
29 29
30/* Size names: regular (1x) -> medium (1.2x) -> big (1.33x) -> large (1.67x) -> huge (2x) */
31
30enum iFontId { 32enum iFontId {
31 default_FontId, 33 default_FontId,
32 defaultMedium_FontId, 34 defaultMedium_FontId,
33 defaultMonospace_FontId, 35 defaultMonospace_FontId,
36 /* content fonts */
34 regular_FontId, 37 regular_FontId,
35 monospace_FontId, 38 monospace_FontId,
36 monospaceSmall_FontId, 39 monospaceSmall_FontId,
37 medium_FontId, 40 medium_FontId,
41 big_FontId,
38 italic_FontId, 42 italic_FontId,
39 bold_FontId, 43 bold_FontId,
40 mediumBold_FontId, 44 bigBold_FontId,
41 largeBold_FontId, 45 largeBold_FontId,
42 hugeBold_FontId, 46 hugeBold_FontId,
43 largeLight_FontId, 47 largeLight_FontId,
@@ -46,27 +50,34 @@ enum iFontId {
46 defaultMediumSymbols_FontId, 50 defaultMediumSymbols_FontId,
47 symbols_FontId, 51 symbols_FontId,
48 mediumSymbols_FontId, 52 mediumSymbols_FontId,
53 bigSymbols_FontId,
49 largeSymbols_FontId, 54 largeSymbols_FontId,
50 hugeSymbols_FontId, 55 hugeSymbols_FontId,
51 smallSymbols_FontId, 56 monospaceSymbols_FontId,
57 monospaceSmallSymbols_FontId,
52 /* emoji fonts */ 58 /* emoji fonts */
53 defaultEmoji_FontId, 59 defaultEmoji_FontId,
54 defaultMediumEmoji_FontId, 60 defaultMediumEmoji_FontId,
55 emoji_FontId, 61 emoji_FontId,
56 mediumEmoji_FontId, 62 mediumEmoji_FontId,
63 bigEmoji_FontId,
57 largeEmoji_FontId, 64 largeEmoji_FontId,
58 hugeEmoji_FontId, 65 hugeEmoji_FontId,
59 smallEmoji_FontId, 66 monospaceEmoji_FontId,
67 monospaceSmallEmoji_FontId,
60 /* japanese script */ 68 /* japanese script */
61 smallJapanese_FontId, 69 defaultJapanese_FontId,
70 monospaceSmallJapanese_FontId,
71 monospaceJapanese_FontId,
62 regularJapanese_FontId, 72 regularJapanese_FontId,
63 mediumJapanese_FontId, 73 mediumJapanese_FontId,
74 bigJapanese_FontId,
64 largeJapanese_FontId, 75 largeJapanese_FontId,
65 hugeJapanese_FontId, 76 hugeJapanese_FontId,
66 max_FontId, 77 max_FontId,
67 78
68 /* Meta: */ 79 /* Meta: */
69 fromSymbolsToEmojiOffset_FontId = 7, 80 fromSymbolsToEmojiOffset_FontId = 9,
70 81
71 /* UI fonts: */ 82 /* UI fonts: */
72 uiLabel_FontId = default_FontId, 83 uiLabel_FontId = default_FontId,
@@ -81,12 +92,15 @@ enum iFontId {
81 quote_FontId = italic_FontId, 92 quote_FontId = italic_FontId,
82 heading1_FontId = hugeBold_FontId, 93 heading1_FontId = hugeBold_FontId,
83 heading2_FontId = largeBold_FontId, 94 heading2_FontId = largeBold_FontId,
84 heading3_FontId = medium_FontId, 95 heading3_FontId = big_FontId,
85 banner_FontId = largeLight_FontId, 96 banner_FontId = largeLight_FontId,
86}; 97};
87 98
88iLocalDef iBool isJapanese_FontId(enum iFontId id) { 99iLocalDef iBool isJapanese_FontId(enum iFontId id) {
89 return id >= smallJapanese_FontId && id <= hugeJapanese_FontId; 100 return id >= defaultJapanese_FontId && id <= hugeJapanese_FontId;
101}
102iLocalDef iBool isVariationSelector_Char(iChar ch) {
103 return ch >= 0xfe00 && ch <= 0xfe0f;
90} 104}
91 105
92#define variationSelectorEmoji_Char ((iChar) 0xfe0f) 106#define variationSelectorEmoji_Char ((iChar) 0xfe0f)
diff --git a/src/ui/util.c b/src/ui/util.c
index ff6f8822..c341a11d 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -471,6 +471,19 @@ iWidget *removeTabPage_Widget(iWidget *tabs, size_t index) {
471 return page; 471 return page;
472} 472}
473 473
474void resizeToLargestPage_Widget(iWidget *tabs) {
475 arrange_Widget(tabs);
476 iInt2 largest = zero_I2();
477 iWidget *pages = findChild_Widget(tabs, "tabs.pages");
478 iConstForEach(ObjectList, i, children_Widget(pages)) {
479 largest = max_I2(largest, ((const iWidget *) i.object)->rect.size);
480 }
481 iForEach(ObjectList, j, children_Widget(pages)) {
482 setSize_Widget(j.object, largest);
483 }
484 setSize_Widget(tabs, addY_I2(largest, height_Widget(findChild_Widget(tabs, "tabs.buttons"))));
485}
486
474iLabelWidget *tabButtonForPage_Widget_(iWidget *tabs, const iWidget *page) { 487iLabelWidget *tabButtonForPage_Widget_(iWidget *tabs, const iWidget *page) {
475 iWidget *buttons = findChild_Widget(tabs, "tabs.buttons"); 488 iWidget *buttons = findChild_Widget(tabs, "tabs.buttons");
476 iForEach(ObjectList, i, buttons->children) { 489 iForEach(ObjectList, i, buttons->children) {
@@ -591,18 +604,18 @@ iWidget *makeSheet_Widget(const char *id) {
591 setBackgroundColor_Widget(sheet, uiBackground_ColorId); 604 setBackgroundColor_Widget(sheet, uiBackground_ColorId);
592 setFlags_Widget(sheet, 605 setFlags_Widget(sheet,
593 mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag | 606 mouseModal_WidgetFlag | keepOnTop_WidgetFlag | arrangeVertical_WidgetFlag |
594 arrangeSize_WidgetFlag, 607 arrangeSize_WidgetFlag | centerHorizontal_WidgetFlag,
595 iTrue); 608 iTrue);
596 return sheet; 609 return sheet;
597} 610}
598 611
599void centerSheet_Widget(iWidget *sheet) { 612void centerSheet_Widget(iWidget *sheet) {
600 arrange_Widget(sheet); 613 arrange_Widget(sheet->parent);
601 const iInt2 rootSize = rootSize_Window(get_Window()); 614// const iInt2 rootSize = rootSize_Window(get_Window());
602 const iInt2 orig = localCoord_Widget( 615// const iInt2 orig = localCoord_Widget(
603 sheet->parent, 616// sheet->parent,
604 init_I2(rootSize.x / 2 - sheet->rect.size.x / 2, bounds_Widget(sheet).pos.y)); 617// init_I2(rootSize.x / 2 - sheet->rect.size.x / 2, bounds_Widget(sheet).pos.y));
605 sheet->rect.pos = orig; 618// sheet->rect.pos = orig;
606 postRefresh_App(); 619 postRefresh_App();
607} 620}
608 621
@@ -803,49 +816,110 @@ iWidget *makeToggle_Widget(const char *id) {
803 return toggle; 816 return toggle;
804} 817}
805 818
819static iWidget *appendTwoColumnPage_(iWidget *tabs, const char *title, iWidget **headings,
820 iWidget **values) {
821 iWidget *page = new_Widget();
822 setFlags_Widget(page, arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag |
823 resizeHeightOfChildren_WidgetFlag, iTrue);
824 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
825 iWidget *columns = new_Widget();
826 addChildFlags_Widget(page, iClob(columns), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
827 *headings = addChildFlags_Widget(
828 columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
829 *values = addChildFlags_Widget(
830 columns, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag);
831 addChildFlags_Widget(page, iClob(new_Widget()), expand_WidgetFlag);
832 appendTabPage_Widget(tabs, page, title, 0, 0);
833 return page;
834}
835
836static void makeTwoColumnHeading_(const char *title, iWidget *headings, iWidget *values) {
837 addChild_Widget(headings,
838 iClob(makeHeading_Widget(format_CStr(uiHeading_ColorEscape "%s", title))));
839 addChild_Widget(values, iClob(makeHeading_Widget("")));
840}
841
842static void expandInputFieldWidth_(iInputWidget *input) {
843 iWidget *page = as_Widget(input)->parent->parent->parent->parent; /* tabs > page > values > input */
844 as_Widget(input)->rect.size.x =
845 right_Rect(bounds_Widget(page)) - left_Rect(bounds_Widget(constAs_Widget(input)));
846}
847
806iWidget *makePreferences_Widget(void) { 848iWidget *makePreferences_Widget(void) {
807 iWidget *dlg = makeSheet_Widget("prefs"); 849 iWidget *dlg = makeSheet_Widget("prefs");
808 addChildFlags_Widget(dlg, 850 addChildFlags_Widget(dlg,
809 iClob(new_LabelWidget(uiHeading_ColorEscape "PREFERENCES", 0, 0, NULL)), 851 iClob(new_LabelWidget(uiHeading_ColorEscape "PREFERENCES", 0, 0, NULL)),
810 frameless_WidgetFlag); 852 frameless_WidgetFlag);
811 iWidget *page = new_Widget(); 853 iWidget *tabs = makeTabs_Widget(dlg);
812 addChild_Widget(dlg, iClob(page)); 854 iWidget *headings, *values;
813 setFlags_Widget(page, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); 855 /* General preferences. */ {
814 iWidget *headings = addChildFlags_Widget( 856 appendTwoColumnPage_(tabs, "General", &headings, &values);
815 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); 857 addChild_Widget(headings, iClob(makeHeading_Widget("Downloads folder:")));
816 iWidget *values = addChildFlags_Widget( 858 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads");
817 page, iClob(new_Widget()), arrangeVertical_WidgetFlag | arrangeSize_WidgetFlag); 859 makeTwoColumnHeading_("WINDOW", headings, values);
818 addChild_Widget(headings, iClob(makeHeading_Widget("Downloads folder:")));
819 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.downloads");
820#if defined (iPlatformApple) || defined (iPlatformMSys) 860#if defined (iPlatformApple) || defined (iPlatformMSys)
821 addChild_Widget(headings, iClob(makeHeading_Widget("Use system theme:"))); 861 addChild_Widget(headings, iClob(makeHeading_Widget("Use system theme:")));
822 addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme"))); 862 addChild_Widget(values, iClob(makeToggle_Widget("prefs.ostheme")));
823#endif 863#endif
824 addChild_Widget(headings, iClob(makeHeading_Widget("Theme:"))); 864 addChild_Widget(headings, iClob(makeHeading_Widget("Theme:")));
825 iWidget *themes = new_Widget(); 865 iWidget *themes = new_Widget();
826 /* Themes. */ { 866 /* Themes. */ {
827 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure Black", 0, 0, "theme.set arg:0"))), "prefs.theme.0"); 867 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure Black", 0, 0, "theme.set arg:0"))), "prefs.theme.0");
828 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Dark", 0, 0, "theme.set arg:1"))), "prefs.theme.1"); 868 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Dark", 0, 0, "theme.set arg:1"))), "prefs.theme.1");
829 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Light", 0, 0, "theme.set arg:2"))), "prefs.theme.2"); 869 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Light", 0, 0, "theme.set arg:2"))), "prefs.theme.2");
830 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure White", 0, 0, "theme.set arg:3"))), "prefs.theme.3"); 870 setId_Widget(addChild_Widget(themes, iClob(new_LabelWidget("Pure White", 0, 0, "theme.set arg:3"))), "prefs.theme.3");
831 } 871 }
832 addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag); 872 addChildFlags_Widget(values, iClob(themes), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
833 addChild_Widget(headings, iClob(makeHeading_Widget("Retain window size:"))); 873 addChild_Widget(headings, iClob(makeHeading_Widget("Retain window size:")));
834 addChild_Widget(values, iClob(makeToggle_Widget("prefs.retainwindow"))); 874 addChild_Widget(values, iClob(makeToggle_Widget("prefs.retainwindow")));
835 addChild_Widget(headings, iClob(makeHeading_Widget("UI scale factor:"))); 875 addChild_Widget(headings, iClob(makeHeading_Widget("UI scale factor:")));
836 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(8))), "prefs.uiscale"); 876 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(8))), "prefs.uiscale");
837 addChild_Widget(headings, iClob(makeHeading_Widget(uiHeading_ColorEscape "Proxies"))); 877 }
838 addChild_Widget(values, iClob(makeHeading_Widget(""))); 878 /* Layout. */ {
839 addChild_Widget(headings, iClob(makeHeading_Widget("Gopher proxy:"))); 879 appendTwoColumnPage_(tabs, "Layout", &headings, &values);
840 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gopher"); 880 addChild_Widget(headings, iClob(makeHeading_Widget("Line width:")));
841 addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:"))); 881 iWidget *widths = new_Widget();
842 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http"); 882 /* Line widths. */ {
883 addChild_Widget(widths, iClob(new_LabelWidget("\u20132", 0, 0, "linewidth.set arg:-2")));
884 addChild_Widget(widths, iClob(new_LabelWidget("\u20131", 0, 0, "linewidth.set arg:-1")));
885 addChild_Widget(widths, iClob(new_LabelWidget("Normal", 0, 0, "linewidth.set arg:0")));
886 addChild_Widget(widths, iClob(new_LabelWidget("+1", 0, 0, "linewidth.set arg:1")));
887 addChild_Widget(widths, iClob(new_LabelWidget("+2", 0, 0, "linewidth.set arg:2")));
888 addChild_Widget(widths, iClob(new_LabelWidget("Window", 0, 0, "linewidth.set arg:1000")));
889 }
890 addChildFlags_Widget(values, iClob(widths), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
891 addChild_Widget(headings, iClob(makeHeading_Widget("Big 1st paragaph:")));
892 addChild_Widget(values, iClob(makeToggle_Widget("prefs.biglede")));
893 }
894 /* Colors. */ {
895 appendTwoColumnPage_(tabs, "Colors", &headings, &values);
896 addChild_Widget(headings, iClob(makeHeading_Widget("Dark theme:")));
897 addChild_Widget(values, iClob(new_LabelWidget("Colorful", 0, 0, 0)));
898 addChild_Widget(headings, iClob(makeHeading_Widget("Light theme:")));
899 addChild_Widget(values, iClob(new_LabelWidget("White", 0, 0, 0)));
900 addChild_Widget(headings, iClob(makeHeading_Widget("Saturation:")));
901 iWidget *sats = new_Widget();
902 /* Saturation levels. */ {
903 addChild_Widget(sats, iClob(new_LabelWidget("Full", 0, 0, "saturation.set arg:100")));
904 addChild_Widget(sats, iClob(new_LabelWidget("Reduced", 0, 0, "saturation.set arg:66")));
905 addChild_Widget(sats, iClob(new_LabelWidget("Minimal", 0, 0, "saturation.set arg:33")));
906 addChild_Widget(sats, iClob(new_LabelWidget("Monochrome", 0, 0, "saturation.set arg:0")));
907 }
908 addChildFlags_Widget(values, iClob(sats), arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag);
909 }
910 /* Proxies. */ {
911 appendTwoColumnPage_(tabs, "Proxies", &headings, &values);
912 addChild_Widget(headings, iClob(makeHeading_Widget("Gopher proxy:")));
913 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.gopher");
914 addChild_Widget(headings, iClob(makeHeading_Widget("HTTP proxy:")));
915 setId_Widget(addChild_Widget(values, iClob(new_InputWidget(0))), "prefs.proxy.http");
916 }
917 resizeToLargestPage_Widget(tabs);
843 arrange_Widget(dlg); 918 arrange_Widget(dlg);
844 /* Set text input widths. */ { 919 /* Set input field sizes. */ {
845 const int inputWidth = width_Rect(page->rect) - width_Rect(headings->rect); 920 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.downloads"));
846 as_Widget(findChild_Widget(values, "prefs.downloads"))->rect.size.x = inputWidth; 921 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.http"));
847 as_Widget(findChild_Widget(values, "prefs.proxy.http"))->rect.size.x = inputWidth; 922 expandInputFieldWidth_(findChild_Widget(tabs, "prefs.proxy.gopher"));
848 as_Widget(findChild_Widget(values, "prefs.proxy.gopher"))->rect.size.x = inputWidth;
849 } 923 }
850 iWidget *div = new_Widget(); { 924 iWidget *div = new_Widget(); {
851 setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue); 925 setFlags_Widget(div, arrangeHorizontal_WidgetFlag | arrangeSize_WidgetFlag, iTrue);
diff --git a/src/ui/util.h b/src/ui/util.h
index 5590d008..754310ad 100644
--- a/src/ui/util.h
+++ b/src/ui/util.h
@@ -136,6 +136,7 @@ iWidget * makeTabs_Widget (iWidget *parent);
136void appendTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); 136void appendTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods);
137void prependTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); 137void prependTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods);
138iWidget * removeTabPage_Widget (iWidget *tabs, size_t index); /* returns the page */ 138iWidget * removeTabPage_Widget (iWidget *tabs, size_t index); /* returns the page */
139void resizeToLargestPage_Widget (iWidget *tabs);
139void showTabPage_Widget (iWidget *tabs, const iWidget *page); 140void showTabPage_Widget (iWidget *tabs, const iWidget *page);
140void setTabPageLabel_Widget (iWidget *tabs, const iAnyObject *page, const iString *label); 141void setTabPageLabel_Widget (iWidget *tabs, const iAnyObject *page, const iString *label);
141iWidget * tabPage_Widget (iWidget *tabs, size_t index); 142iWidget * tabPage_Widget (iWidget *tabs, size_t index);
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 05bb62cc..d218d7ee 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -119,11 +119,11 @@ const iString *id_Widget(const iWidget *d) {
119 return &d->id; 119 return &d->id;
120} 120}
121 121
122int flags_Widget(const iWidget *d) { 122int64_t flags_Widget(const iWidget *d) {
123 return d->flags; 123 return d->flags;
124} 124}
125 125
126void setFlags_Widget(iWidget *d, int flags, iBool set) { 126void setFlags_Widget(iWidget *d, int64_t flags, iBool set) {
127 iChangeFlags(d->flags, flags, set); 127 iChangeFlags(d->flags, flags, set);
128 if (flags & keepOnTop_WidgetFlag) { 128 if (flags & keepOnTop_WidgetFlag) {
129 if (set) { 129 if (set) {
@@ -228,6 +228,13 @@ static size_t numArrangedChildren_Widget_(const iWidget *d) {
228 return count; 228 return count;
229} 229}
230 230
231static void centerHorizontal_Widget_(iWidget *d) {
232 d->rect.pos.x = ((d->parent ? width_Rect(innerRect_Widget_(d->parent))
233 : rootSize_Window(get_Window()).x) -
234 width_Rect(d->rect)) /
235 2;
236}
237
231void arrange_Widget(iWidget *d) { 238void arrange_Widget(iWidget *d) {
232 if (isCollapsed_Widget_(d)) { 239 if (isCollapsed_Widget_(d)) {
233 setFlags_Widget(d, wasCollapsed_WidgetFlag, iTrue); 240 setFlags_Widget(d, wasCollapsed_WidgetFlag, iTrue);
@@ -236,6 +243,9 @@ void arrange_Widget(iWidget *d) {
236 if (d->flags & moveToParentRightEdge_WidgetFlag) { 243 if (d->flags & moveToParentRightEdge_WidgetFlag) {
237 d->rect.pos.x = width_Rect(innerRect_Widget_(d->parent)) - width_Rect(d->rect); 244 d->rect.pos.x = width_Rect(innerRect_Widget_(d->parent)) - width_Rect(d->rect);
238 } 245 }
246 if (d->flags & centerHorizontal_WidgetFlag) {
247 centerHorizontal_Widget_(d);
248 }
239 if (d->flags & resizeToParentWidth_WidgetFlag) { 249 if (d->flags & resizeToParentWidth_WidgetFlag) {
240 setWidth_Widget_(d, width_Rect(innerRect_Widget_(d->parent))); 250 setWidth_Widget_(d, width_Rect(innerRect_Widget_(d->parent)));
241 } 251 }
@@ -351,6 +361,9 @@ void arrange_Widget(iWidget *d) {
351 if (child->flags & fixedPosition_WidgetFlag) { 361 if (child->flags & fixedPosition_WidgetFlag) {
352 continue; 362 continue;
353 } 363 }
364 if (child->flags & centerHorizontal_WidgetFlag) {
365 continue;
366 }
354 if (d->flags & (arrangeHorizontal_WidgetFlag | arrangeVertical_WidgetFlag)) { 367 if (d->flags & (arrangeHorizontal_WidgetFlag | arrangeVertical_WidgetFlag)) {
355 if (child->flags & moveToParentRightEdge_WidgetFlag) { 368 if (child->flags & moveToParentRightEdge_WidgetFlag) {
356 continue; /* Not part of the sequential arrangement .*/ 369 continue; /* Not part of the sequential arrangement .*/
@@ -401,6 +414,9 @@ void arrange_Widget(iWidget *d) {
401 } 414 }
402 } 415 }
403 } 416 }
417 if (d->flags & centerHorizontal_WidgetFlag) {
418 centerHorizontal_Widget_(d);
419 }
404 } 420 }
405} 421}
406 422
@@ -616,7 +632,7 @@ iAny *addChildPos_Widget(iWidget *d, iAnyObject *child, enum iWidgetAddPos addPo
616 return child; 632 return child;
617} 633}
618 634
619iAny *addChildFlags_Widget(iWidget *d, iAnyObject *child, int childFlags) { 635iAny *addChildFlags_Widget(iWidget *d, iAnyObject *child, int64_t childFlags) {
620 setFlags_Widget(child, childFlags, iTrue); 636 setFlags_Widget(child, childFlags, iTrue);
621 return addChild_Widget(d, child); 637 return addChild_Widget(d, child);
622} 638}
diff --git a/src/ui/widget.h b/src/ui/widget.h
index fa4fbe0f..a2be30e9 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -44,44 +44,47 @@ iBeginDeclareClass(Widget)
44iEndDeclareClass(Widget) 44iEndDeclareClass(Widget)
45 45
46enum iWidgetFlag { 46enum iWidgetFlag {
47 hidden_WidgetFlag = iBit(1), 47 hidden_WidgetFlag = iBit(1),
48 disabled_WidgetFlag = iBit(2), 48 disabled_WidgetFlag = iBit(2),
49 hover_WidgetFlag = iBit(3), /* eligible for mouse hover */ 49 hover_WidgetFlag = iBit(3), /* eligible for mouse hover */
50 selected_WidgetFlag = iBit(4), 50 selected_WidgetFlag = iBit(4),
51 pressed_WidgetFlag = iBit(5), 51 pressed_WidgetFlag = iBit(5),
52 alignLeft_WidgetFlag = iBit(6), 52 alignLeft_WidgetFlag = iBit(6),
53 alignRight_WidgetFlag = iBit(7), 53 alignRight_WidgetFlag = iBit(7),
54 frameless_WidgetFlag = iBit(8), 54 frameless_WidgetFlag = iBit(8),
55 commandOnClick_WidgetFlag = iBit(9), 55 commandOnClick_WidgetFlag = iBit(9),
56 drawKey_WidgetFlag = iBit(10), 56 commandOnMouseMiss_WidgetFlag = iBit(10),
57 focusable_WidgetFlag = iBit(11), 57 drawKey_WidgetFlag = iBit(11),
58 tight_WidgetFlag = iBit(12), /* smaller padding */ 58 focusable_WidgetFlag = iBit(12),
59 keepOnTop_WidgetFlag = iBit(13), /* gets events first; drawn last */ 59 tight_WidgetFlag = iBit(13), /* smaller padding */
60 mouseModal_WidgetFlag = iBit(14), /* eats all unprocessed mouse events */ 60 keepOnTop_WidgetFlag = iBit(14), /* gets events first; drawn last */
61 commandOnMouseMiss_WidgetFlag = iBit(15), 61 mouseModal_WidgetFlag = iBit(15), /* eats all unprocessed mouse events */
62 /* arrange behavior */ 62 /* arrangement */
63 fixedPosition_WidgetFlag = iBit(16), 63 fixedPosition_WidgetFlag = iBit(17),
64 arrangeHorizontal_WidgetFlag = iBit(17), /* arrange children horizontally */ 64 arrangeHorizontal_WidgetFlag = iBit(18), /* arrange children horizontally */
65 arrangeVertical_WidgetFlag = iBit(18), /* arrange children vertically */ 65 arrangeVertical_WidgetFlag = iBit(19), /* arrange children vertically */
66 arrangeWidth_WidgetFlag = iBit(19), /* area of children becomes parent size */ 66 arrangeWidth_WidgetFlag = iBit(20), /* area of children becomes parent size */
67 arrangeHeight_WidgetFlag = iBit(20), /* area of children becomes parent size */ 67 arrangeHeight_WidgetFlag = iBit(21), /* area of children becomes parent size */
68 resizeWidthOfChildren_WidgetFlag = iBit(21), 68 resizeWidthOfChildren_WidgetFlag = iBit(22),
69 resizeHeightOfChildren_WidgetFlag = iBit(22), 69 resizeHeightOfChildren_WidgetFlag = iBit(23),
70 expand_WidgetFlag = iBit(23), 70 expand_WidgetFlag = iBit(24),
71 fixedWidth_WidgetFlag = iBit(24), 71 fixedWidth_WidgetFlag = iBit(25),
72 fixedHeight_WidgetFlag = iBit(25), 72 fixedHeight_WidgetFlag = iBit(26),
73 resizeChildrenToWidestChild_WidgetFlag = iBit(26), 73 resizeChildrenToWidestChild_WidgetFlag = iBit(27),
74 resizeToParentWidth_WidgetFlag = iBit(27), 74 resizeToParentWidth_WidgetFlag = iBit(28),
75 resizeToParentHeight_WidgetFlag = iBit(28), 75 resizeToParentHeight_WidgetFlag = iBit(29),
76 moveToParentRightEdge_WidgetFlag = iBit(29), 76 collapse_WidgetFlag = iBit(30), /* if hidden, arrange size to zero */
77 collapse_WidgetFlag = iBit(30), /* when hidden, arrange size to zero */
78 wasCollapsed_WidgetFlag = iBit(31),
79 /* combinations */ 77 /* combinations */
80 arrangeSize_WidgetFlag = arrangeWidth_WidgetFlag | arrangeHeight_WidgetFlag, 78 arrangeSize_WidgetFlag = arrangeWidth_WidgetFlag | arrangeHeight_WidgetFlag,
81 resizeChildren_WidgetFlag = resizeWidthOfChildren_WidgetFlag | resizeHeightOfChildren_WidgetFlag, 79 resizeChildren_WidgetFlag = resizeWidthOfChildren_WidgetFlag | resizeHeightOfChildren_WidgetFlag,
82 fixedSize_WidgetFlag = fixedWidth_WidgetFlag | fixedHeight_WidgetFlag, 80 fixedSize_WidgetFlag = fixedWidth_WidgetFlag | fixedHeight_WidgetFlag,
83}; 81};
84 82
83/* 64-bit extended flags */
84#define wasCollapsed_WidgetFlag iBit64(32)
85#define centerHorizontal_WidgetFlag iBit64(33)
86#define moveToParentRightEdge_WidgetFlag iBit64(34)
87
85enum iWidgetAddPos { 88enum iWidgetAddPos {
86 back_WidgetAddPos, 89 back_WidgetAddPos,
87 front_WidgetAddPos, 90 front_WidgetAddPos,
@@ -95,7 +98,7 @@ enum iWidgetFocusDir {
95struct Impl_Widget { 98struct Impl_Widget {
96 iObject object; 99 iObject object;
97 iString id; 100 iString id;
98 int flags; 101 int64_t flags;
99 iRect rect; 102 iRect rect;
100 int padding[4]; /* left, top, right, bottom */ 103 int padding[4]; /* left, top, right, bottom */
101 int bgColor; 104 int bgColor;
@@ -131,7 +134,7 @@ void destroy_Widget (iWidget *); /* widget removed and deleted later */
131void destroyPending_Widget(void); 134void destroyPending_Widget(void);
132 135
133const iString *id_Widget (const iWidget *); 136const iString *id_Widget (const iWidget *);
134int flags_Widget (const iWidget *); 137int64_t flags_Widget (const iWidget *);
135iRect bounds_Widget (const iWidget *); /* outer bounds */ 138iRect bounds_Widget (const iWidget *); /* outer bounds */
136iRect innerBounds_Widget (const iWidget *); 139iRect innerBounds_Widget (const iWidget *);
137iInt2 localCoord_Widget (const iWidget *, iInt2 coord); 140iInt2 localCoord_Widget (const iWidget *, iInt2 coord);
@@ -148,6 +151,10 @@ iLocalDef int width_Widget(const iAnyObject *d) {
148 iAssert(isInstance_Object(d, &Class_Widget)); 151 iAssert(isInstance_Object(d, &Class_Widget));
149 return ((const iWidget *) d)->rect.size.x; 152 return ((const iWidget *) d)->rect.size.x;
150} 153}
154iLocalDef int height_Widget(const iAnyObject *d) {
155 iAssert(isInstance_Object(d, &Class_Widget));
156 return ((const iWidget *) d)->rect.size.y;
157}
151iLocalDef iObjectList *children_Widget(iAnyObject *d) { 158iLocalDef iObjectList *children_Widget(iAnyObject *d) {
152 iAssert(isInstance_Object(d, &Class_Widget)); 159 iAssert(isInstance_Object(d, &Class_Widget));
153 return ((iWidget *) d)->children; 160 return ((iWidget *) d)->children;
@@ -161,7 +168,7 @@ iBool isSelected_Widget (const iAnyObject *);
161iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); 168iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd);
162iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); 169iBool hasParent_Widget (const iWidget *d, const iWidget *someParent);
163void setId_Widget (iWidget *, const char *id); 170void setId_Widget (iWidget *, const char *id);
164void setFlags_Widget (iWidget *, int flags, iBool set); 171void setFlags_Widget (iWidget *, int64_t flags, iBool set);
165void setPos_Widget (iWidget *, iInt2 pos); 172void setPos_Widget (iWidget *, iInt2 pos);
166void setSize_Widget (iWidget *, iInt2 size); 173void setSize_Widget (iWidget *, iInt2 size);
167void setPadding_Widget (iWidget *, int left, int top, int right, int bottom); 174void setPadding_Widget (iWidget *, int left, int top, int right, int bottom);
@@ -171,7 +178,7 @@ void setFrameColor_Widget (iWidget *, int frameColor);
171void setCommandHandler_Widget (iWidget *, iBool (*handler)(iWidget *, const char *)); 178void setCommandHandler_Widget (iWidget *, iBool (*handler)(iWidget *, const char *));
172iAny * addChild_Widget (iWidget *, iAnyObject *child); /* holds a ref */ 179iAny * addChild_Widget (iWidget *, iAnyObject *child); /* holds a ref */
173iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos); 180iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos);
174iAny * addChildFlags_Widget(iWidget *, iAnyObject *child, int childFlags); /* holds a ref */ 181iAny * addChildFlags_Widget(iWidget *, iAnyObject *child, int64_t childFlags); /* holds a ref */
175iAny * removeChild_Widget (iWidget *, iAnyObject *child); /* returns a ref */ 182iAny * removeChild_Widget (iWidget *, iAnyObject *child); /* returns a ref */
176iAny * child_Widget (iWidget *, size_t index); /* O(n) */ 183iAny * child_Widget (iWidget *, size_t index); /* O(n) */
177size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ 184size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */