diff options
-rw-r--r-- | CMakeLists.txt | 13 | ||||
-rw-r--r-- | README.md | 2 | ||||
m--------- | lib/the_Foundation | 0 | ||||
-rw-r--r-- | res/about/version.gmi | 8 | ||||
-rw-r--r-- | src/app.c | 191 | ||||
-rw-r--r-- | src/app.h | 5 | ||||
-rw-r--r-- | src/gmdocument.c | 52 | ||||
-rw-r--r-- | src/gmdocument.h | 5 | ||||
-rw-r--r-- | src/prefs.c | 23 | ||||
-rw-r--r-- | src/prefs.h | 29 | ||||
-rw-r--r-- | src/ui/documentwidget.c | 86 | ||||
-rw-r--r-- | src/ui/text.c | 70 | ||||
-rw-r--r-- | src/ui/text.h | 28 | ||||
-rw-r--r-- | src/ui/util.c | 158 | ||||
-rw-r--r-- | src/ui/util.h | 1 | ||||
-rw-r--r-- | src/ui/widget.c | 22 | ||||
-rw-r--r-- | src/ui/widget.h | 79 |
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 | ||
19 | cmake_minimum_required (VERSION 3.9) | 19 | cmake_minimum_required (VERSION 3.9) |
20 | set (CMAKE_OSX_DEPLOYMENT_TARGET 10.14) | ||
21 | 20 | ||
22 | project (Lagrange | 21 | project (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) | |||
177 | endif () | 178 | endif () |
178 | if (ENABLE_KERNING) | 179 | if (ENABLE_KERNING) |
179 | target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_KERNING=1) | 180 | target_compile_definitions (app PUBLIC LAGRANGE_ENABLE_KERNING=1) |
180 | if (ENABLE_WINDOWPOS_FIX) | ||
181 | endif () | 181 | endif () |
182 | if (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) |
183 | endif () | 184 | endif () |
184 | target_link_libraries (app PUBLIC the_Foundation::the_Foundation) | 185 | target_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} |
@@ -1,6 +1,6 @@ | |||
1 | # Lagrange | 1 | # Lagrange |
2 | 2 | ||
3 | Lagrange 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. | 3 | Lagrange 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 | ||
5 | Like 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. | 5 | Like 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. |
@@ -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 | ||
110 | static iApp app_; | 113 | static iApp app_; |
@@ -134,8 +137,8 @@ const iString *dateStr_(const iDate *date) { | |||
134 | static iString *serializePrefs_App_(const iApp *d) { | 137 | static 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) { | |||
392 | static void deinit_App(iApp *d) { | 390 | static 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 | ||
418 | const iString *downloadDir_App(void) { | 414 | const 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 | ||
422 | const iString *debugInfo_App(void) { | 418 | const 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 | ||
543 | int zoom_App(void) { | 539 | const iPrefs *prefs_App(void) { |
544 | return app_.zoomPercent; | 540 | return &app_.prefs; |
545 | } | 541 | } |
546 | 542 | ||
547 | iBool forceLineWrap_App(void) { | 543 | iBool forceLineWrap_App(void) { |
548 | return app_.forceWrap; | 544 | return app_.prefs.forceLineWrap; |
549 | } | 545 | } |
550 | 546 | ||
551 | iBool forceSoftwareRender_App(void) { | 547 | iBool forceSoftwareRender_App(void) { |
@@ -561,16 +557,16 @@ iBool forceSoftwareRender_App(void) { | |||
561 | } | 557 | } |
562 | 558 | ||
563 | enum iColorTheme colorTheme_App(void) { | 559 | enum iColorTheme colorTheme_App(void) { |
564 | return app_.theme; | 560 | return app_.prefs.theme; |
565 | } | 561 | } |
566 | 562 | ||
567 | const iString *schemeProxy_App(iRangecc scheme) { | 563 | const 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 | ||
716 | iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf) { | 712 | iDocumentWidget *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) { | |||
801 | iBool handleCommand_App(const char *cmd) { | 799 | iBool 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 | } |
@@ -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 | ||
33 | iDeclareType(Bookmarks) | 34 | iDeclareType(Bookmarks) |
@@ -58,7 +59,7 @@ void refresh_App (void); | |||
58 | iBool isRefreshPending_App (void); | 59 | iBool isRefreshPending_App (void); |
59 | uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ | 60 | uint32_t elapsedSinceLastTicker_App (void); /* milliseconds */ |
60 | 61 | ||
61 | int zoom_App (void); | 62 | const iPrefs * prefs_App (void); |
62 | iBool forceLineWrap_App (void); | 63 | iBool forceLineWrap_App (void); |
63 | iBool forceSoftwareRender_App(void); | 64 | iBool forceSoftwareRender_App(void); |
64 | enum iColorTheme colorTheme_App (void); | 65 | enum iColorTheme colorTheme_App (void); |
@@ -70,7 +71,7 @@ iBookmarks * bookmarks_App (void); | |||
70 | iDocumentWidget * document_App (void); | 71 | iDocumentWidget * document_App (void); |
71 | iObjectList * listDocuments_App (void); | 72 | iObjectList * listDocuments_App (void); |
72 | iDocumentWidget * document_Command (const char *cmd); | 73 | iDocumentWidget * document_Command (const char *cmd); |
73 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf); | 74 | iDocumentWidget * newTab_App (const iDocumentWidget *duplicateOf, iBool switchToNew); |
74 | 75 | ||
75 | iAny * findWidget_App (const char *id); | 76 | iAny * findWidget_App (const char *id); |
76 | void addTicker_App (void (*ticker)(iAny *), iAny *context); | 77 | void 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 | ||
626 | void deinit_GmDocument(iGmDocument *d) { | 628 | void deinit_GmDocument(iGmDocument *d) { |
@@ -649,9 +651,11 @@ void reset_GmDocument(iGmDocument *d) { | |||
649 | } | 651 | } |
650 | 652 | ||
651 | void setThemeSeed_GmDocument(iGmDocument *d, const iBlock *seed) { | 653 | void 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 | ||
1083 | const iGmRun *findRun_GmDocument(const iGmDocument *d, iInt2 pos) { | 1089 | const 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 | ||
1094 | const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) { | 1116 | const char *findLoc_GmDocument(const iGmDocument *d, iInt2 pos) { |
@@ -1228,7 +1250,13 @@ iChar siteIcon_GmDocument(const iGmDocument *d) { | |||
1228 | } | 1250 | } |
1229 | 1251 | ||
1230 | const char *findLoc_GmRun(const iGmRun *d, iInt2 pos) { | 1252 | const 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) | |||
35 | iDeclareType(GmHeading) | 35 | iDeclareType(GmHeading) |
36 | iDeclareType(GmRun) | 36 | iDeclareType(GmRun) |
37 | 37 | ||
38 | enum iGmDocumentTheme { | ||
39 | colorfulDark_GmDocumentTheme, | ||
40 | white_GmDocumentTheme, | ||
41 | }; | ||
42 | |||
38 | typedef uint16_t iGmLinkId; | 43 | typedef uint16_t iGmLinkId; |
39 | 44 | ||
40 | enum iGmLinkFlags { | 45 | enum 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 | |||
3 | void 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 | |||
19 | void 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 | |||
9 | iDeclareType(Prefs) | ||
10 | |||
11 | struct 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 | |||
29 | iDeclareTypeConstruction(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 | ||
61 | static void updated_MediaRequest_(iAnyObject *obj) { | 61 | static 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 | ||
66 | static void finished_MediaRequest_(iAnyObject *obj) { | 66 | static 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 | ||
245 | static int documentWidth_DocumentWidget_(const iDocumentWidget *d) { | 245 | static 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 | ||
252 | static iRect documentBounds_DocumentWidget_(const iDocumentWidget *d) { | 253 | static 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 | ||
376 | static void updateVisible_DocumentWidget_(iDocumentWidget *d) { | 377 | static 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 | |||
30 | enum iFontId { | 32 | enum 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 | ||
88 | iLocalDef iBool isJapanese_FontId(enum iFontId id) { | 99 | iLocalDef iBool isJapanese_FontId(enum iFontId id) { |
89 | return id >= smallJapanese_FontId && id <= hugeJapanese_FontId; | 100 | return id >= defaultJapanese_FontId && id <= hugeJapanese_FontId; |
101 | } | ||
102 | iLocalDef 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 | ||
474 | void 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 | |||
474 | iLabelWidget *tabButtonForPage_Widget_(iWidget *tabs, const iWidget *page) { | 487 | iLabelWidget *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 | ||
599 | void centerSheet_Widget(iWidget *sheet) { | 612 | void 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 | ||
819 | static 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 | |||
836 | static 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 | |||
842 | static 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 | |||
806 | iWidget *makePreferences_Widget(void) { | 848 | iWidget *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); | |||
136 | void appendTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); | 136 | void appendTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); |
137 | void prependTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); | 137 | void prependTabPage_Widget (iWidget *tabs, iWidget *page, const char *label, int key, int kmods); |
138 | iWidget * removeTabPage_Widget (iWidget *tabs, size_t index); /* returns the page */ | 138 | iWidget * removeTabPage_Widget (iWidget *tabs, size_t index); /* returns the page */ |
139 | void resizeToLargestPage_Widget (iWidget *tabs); | ||
139 | void showTabPage_Widget (iWidget *tabs, const iWidget *page); | 140 | void showTabPage_Widget (iWidget *tabs, const iWidget *page); |
140 | void setTabPageLabel_Widget (iWidget *tabs, const iAnyObject *page, const iString *label); | 141 | void setTabPageLabel_Widget (iWidget *tabs, const iAnyObject *page, const iString *label); |
141 | iWidget * tabPage_Widget (iWidget *tabs, size_t index); | 142 | iWidget * 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 | ||
122 | int flags_Widget(const iWidget *d) { | 122 | int64_t flags_Widget(const iWidget *d) { |
123 | return d->flags; | 123 | return d->flags; |
124 | } | 124 | } |
125 | 125 | ||
126 | void setFlags_Widget(iWidget *d, int flags, iBool set) { | 126 | void 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 | ||
231 | static 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 | |||
231 | void arrange_Widget(iWidget *d) { | 238 | void 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 | ||
619 | iAny *addChildFlags_Widget(iWidget *d, iAnyObject *child, int childFlags) { | 635 | iAny *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) | |||
44 | iEndDeclareClass(Widget) | 44 | iEndDeclareClass(Widget) |
45 | 45 | ||
46 | enum iWidgetFlag { | 46 | enum 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 | |||
85 | enum iWidgetAddPos { | 88 | enum iWidgetAddPos { |
86 | back_WidgetAddPos, | 89 | back_WidgetAddPos, |
87 | front_WidgetAddPos, | 90 | front_WidgetAddPos, |
@@ -95,7 +98,7 @@ enum iWidgetFocusDir { | |||
95 | struct Impl_Widget { | 98 | struct 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 */ | |||
131 | void destroyPending_Widget(void); | 134 | void destroyPending_Widget(void); |
132 | 135 | ||
133 | const iString *id_Widget (const iWidget *); | 136 | const iString *id_Widget (const iWidget *); |
134 | int flags_Widget (const iWidget *); | 137 | int64_t flags_Widget (const iWidget *); |
135 | iRect bounds_Widget (const iWidget *); /* outer bounds */ | 138 | iRect bounds_Widget (const iWidget *); /* outer bounds */ |
136 | iRect innerBounds_Widget (const iWidget *); | 139 | iRect innerBounds_Widget (const iWidget *); |
137 | iInt2 localCoord_Widget (const iWidget *, iInt2 coord); | 140 | iInt2 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 | } |
154 | iLocalDef int height_Widget(const iAnyObject *d) { | ||
155 | iAssert(isInstance_Object(d, &Class_Widget)); | ||
156 | return ((const iWidget *) d)->rect.size.y; | ||
157 | } | ||
151 | iLocalDef iObjectList *children_Widget(iAnyObject *d) { | 158 | iLocalDef 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 *); | |||
161 | iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); | 168 | iBool isCommand_Widget (const iWidget *d, const SDL_Event *ev, const char *cmd); |
162 | iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); | 169 | iBool hasParent_Widget (const iWidget *d, const iWidget *someParent); |
163 | void setId_Widget (iWidget *, const char *id); | 170 | void setId_Widget (iWidget *, const char *id); |
164 | void setFlags_Widget (iWidget *, int flags, iBool set); | 171 | void setFlags_Widget (iWidget *, int64_t flags, iBool set); |
165 | void setPos_Widget (iWidget *, iInt2 pos); | 172 | void setPos_Widget (iWidget *, iInt2 pos); |
166 | void setSize_Widget (iWidget *, iInt2 size); | 173 | void setSize_Widget (iWidget *, iInt2 size); |
167 | void setPadding_Widget (iWidget *, int left, int top, int right, int bottom); | 174 | void setPadding_Widget (iWidget *, int left, int top, int right, int bottom); |
@@ -171,7 +178,7 @@ void setFrameColor_Widget (iWidget *, int frameColor); | |||
171 | void setCommandHandler_Widget (iWidget *, iBool (*handler)(iWidget *, const char *)); | 178 | void setCommandHandler_Widget (iWidget *, iBool (*handler)(iWidget *, const char *)); |
172 | iAny * addChild_Widget (iWidget *, iAnyObject *child); /* holds a ref */ | 179 | iAny * addChild_Widget (iWidget *, iAnyObject *child); /* holds a ref */ |
173 | iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos); | 180 | iAny * addChildPos_Widget (iWidget *, iAnyObject *child, enum iWidgetAddPos addPos); |
174 | iAny * addChildFlags_Widget(iWidget *, iAnyObject *child, int childFlags); /* holds a ref */ | 181 | iAny * addChildFlags_Widget(iWidget *, iAnyObject *child, int64_t childFlags); /* holds a ref */ |
175 | iAny * removeChild_Widget (iWidget *, iAnyObject *child); /* returns a ref */ | 182 | iAny * removeChild_Widget (iWidget *, iAnyObject *child); /* returns a ref */ |
176 | iAny * child_Widget (iWidget *, size_t index); /* O(n) */ | 183 | iAny * child_Widget (iWidget *, size_t index); /* O(n) */ |
177 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ | 184 | size_t childIndex_Widget (const iWidget *, const iAnyObject *child); /* O(n) */ |