summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJaakko Keränen <jaakko.keranen@iki.fi>2021-09-20 11:37:23 +0300
committerJaakko Keränen <jaakko.keranen@iki.fi>2021-09-20 11:37:23 +0300
commit2d81addf78d6a8b0fb2f2959b04a385c4adffdf2 (patch)
tree5e0f45b9c945499bc6a6669563de13c5203981a6
parent201021092d204680b353c82ce9e9beb76f3044e8 (diff)
Experimenting with independent popup windows
Toe dipping into multiple window support by allowing popup menu widgets to be displayed in independent windows. This is not a 100% replacement for native menus, but it gets pretty close.
-rw-r--r--src/app.c186
-rw-r--r--src/app.h10
-rw-r--r--src/ios.m10
-rw-r--r--src/macos.h3
-rw-r--r--src/macos.m17
-rw-r--r--src/ui/documentwidget.c2
-rw-r--r--src/ui/inputwidget.c2
-rw-r--r--src/ui/root.c10
-rw-r--r--src/ui/root.h2
-rw-r--r--src/ui/text.c93
-rw-r--r--src/ui/text.h17
-rw-r--r--src/ui/text_simple.c16
-rw-r--r--src/ui/util.c42
-rw-r--r--src/ui/widget.c38
-rw-r--r--src/ui/widget.h4
-rw-r--r--src/ui/window.c257
-rw-r--r--src/ui/window.h34
17 files changed, 511 insertions, 232 deletions
diff --git a/src/app.c b/src/app.c
index 91b3a06d..c1c1da27 100644
--- a/src/app.c
+++ b/src/app.c
@@ -118,6 +118,7 @@ struct Impl_App {
118 iVisited * visited; 118 iVisited * visited;
119 iBookmarks * bookmarks; 119 iBookmarks * bookmarks;
120 iMainWindow *window; 120 iMainWindow *window;
121 iPtrArray popupWindows;
121 iSortedArray tickers; /* per-frame callbacks, used for animations */ 122 iSortedArray tickers; /* per-frame callbacks, used for animations */
122 uint32_t lastTickerTime; 123 uint32_t lastTickerTime;
123 uint32_t elapsedSinceLastTicker; 124 uint32_t elapsedSinceLastTicker;
@@ -801,6 +802,7 @@ static void init_App_(iApp *d, int argc, char **argv) {
801 d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0)); 802 d->initialWindowRect.size.y = toInt_String(value_CommandLineArg(arg, 0));
802 } 803 }
803 } 804 }
805 init_PtrArray(&d->popupWindows);
804 d->window = new_MainWindow(d->initialWindowRect); 806 d->window = new_MainWindow(d->initialWindowRect);
805 load_Visited(d->visited, dataDir_App_()); 807 load_Visited(d->visited, dataDir_App_());
806 load_Bookmarks(d->bookmarks, dataDir_App_()); 808 load_Bookmarks(d->bookmarks, dataDir_App_());
@@ -853,6 +855,11 @@ static void init_App_(iApp *d, int argc, char **argv) {
853} 855}
854 856
855static void deinit_App(iApp *d) { 857static void deinit_App(iApp *d) {
858 iReverseForEach(PtrArray, i, &d->popupWindows) {
859 delete_Window(i.ptr);
860 }
861 iAssert(isEmpty_PtrArray(&d->popupWindows));
862 deinit_PtrArray(&d->popupWindows);
856#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 863#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
857 SDL_RemoveTimer(d->sleepTimer); 864 SDL_RemoveTimer(d->sleepTimer);
858#endif 865#endif
@@ -1086,6 +1093,15 @@ static iBool nextEvent_App_(iApp *d, enum iAppEventMode eventMode, SDL_Event *ev
1086 return SDL_PollEvent(event); 1093 return SDL_PollEvent(event);
1087} 1094}
1088 1095
1096static const iPtrArray *listWindows_App_(const iApp *d) {
1097 iPtrArray *list = collectNew_PtrArray();
1098 iReverseConstForEach(PtrArray, i, &d->popupWindows) {
1099 pushBack_PtrArray(list, i.ptr);
1100 }
1101 pushBack_PtrArray(list, d->window);
1102 return list;
1103}
1104
1089void processEvents_App(enum iAppEventMode eventMode) { 1105void processEvents_App(enum iAppEventMode eventMode) {
1090 iApp *d = &app_; 1106 iApp *d = &app_;
1091 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */ 1107 iRoot *oldCurrentRoot = current_Root(); /* restored afterwards */
@@ -1125,17 +1141,17 @@ void processEvents_App(enum iAppEventMode eventMode) {
1125#if defined (iPlatformAppleMobile) 1141#if defined (iPlatformAppleMobile)
1126 updateNowPlayingInfo_iOS(); 1142 updateNowPlayingInfo_iOS();
1127#endif 1143#endif
1128 setFreezeDraw_Window(as_Window(d), iTrue); 1144 setFreezeDraw_MainWindow(d->window, iTrue);
1129 savePrefs_App_(d); 1145 savePrefs_App_(d);
1130 saveState_App_(d); 1146 saveState_App_(d);
1131 break; 1147 break;
1132 case SDL_APP_TERMINATING: 1148 case SDL_APP_TERMINATING:
1133 setFreezeDraw_Window(as_Window(d), iTrue); 1149 setFreezeDraw_MainWindow(d->window, iTrue);
1134 savePrefs_App_(d); 1150 savePrefs_App_(d);
1135 saveState_App_(d); 1151 saveState_App_(d);
1136 break; 1152 break;
1137 case SDL_DROPFILE: { 1153 case SDL_DROPFILE: {
1138 iBool wasUsed = processEvent_MainWindow(d->window, &ev); 1154 iBool wasUsed = processEvent_Window(as_Window(d->window), &ev);
1139 if (!wasUsed) { 1155 if (!wasUsed) {
1140 iBool newTab = iFalse; 1156 iBool newTab = iFalse;
1141 if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) { 1157 if (elapsedSeconds_Time(&d->lastDropTime) < 0.1) {
@@ -1175,23 +1191,6 @@ void processEvents_App(enum iAppEventMode eventMode) {
1175 } 1191 }
1176 d->isIdling = iFalse; 1192 d->isIdling = iFalse;
1177#endif 1193#endif
1178 if (ev.type == SDL_USEREVENT && ev.user.code == arrange_UserEventCode) {
1179 printf("[App] rearrange\n");
1180 resize_MainWindow(d->window, -1, -1);
1181 iForIndices(i, d->window->base.roots) {
1182 if (d->window->base.roots[i]) {
1183 d->window->base.roots[i]->pendingArrange = iFalse;
1184 }
1185 }
1186// if (ev.user.data2 == d->window->roots[0]) {
1187// arrange_Widget(d->window->roots[0]->widget);
1188// }
1189// else if (d->window->roots[1]) {
1190// arrange_Widget(d->window->roots[1]->widget);
1191// }
1192// postRefresh_App();
1193 continue;
1194 }
1195 gotEvents = iTrue; 1194 gotEvents = iTrue;
1196 /* Keyboard modifier mapping. */ 1195 /* Keyboard modifier mapping. */
1197 if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) { 1196 if (ev.type == SDL_KEYDOWN || ev.type == SDL_KEYUP) {
@@ -1268,10 +1267,22 @@ void processEvents_App(enum iAppEventMode eventMode) {
1268 } 1267 }
1269 } 1268 }
1270#endif 1269#endif
1271 d->window->base.lastHover = d->window->base.hover; 1270 /* Per-window processing. */
1272 iBool wasUsed = processEvent_MainWindow(d->window, &ev); 1271 iBool wasUsed = iFalse;
1272 const iPtrArray *windows = listWindows_App_(d);
1273 iConstForEach(PtrArray, iter, windows) {
1274 iWindow *window = iter.ptr;
1275 setCurrent_Window(window);
1276 window->lastHover = window->hover;
1277 wasUsed = processEvent_Window(window, &ev);
1278 if (ev.type == SDL_MOUSEMOTION) {
1279 break;
1280 }
1281 if (wasUsed) break;
1282 }
1283 setCurrent_Window(d->window);
1273 if (!wasUsed) { 1284 if (!wasUsed) {
1274 /* There may be a key bindings for this. */ 1285 /* There may be a key binding for this. */
1275 wasUsed = processEvent_Keys(&ev); 1286 wasUsed = processEvent_Keys(&ev);
1276 } 1287 }
1277 if (!wasUsed) { 1288 if (!wasUsed) {
@@ -1289,24 +1300,32 @@ void processEvents_App(enum iAppEventMode eventMode) {
1289 handleCommand_MacOS(command_UserEvent(&ev)); 1300 handleCommand_MacOS(command_UserEvent(&ev));
1290#endif 1301#endif
1291 if (isMetricsChange_UserEvent(&ev)) { 1302 if (isMetricsChange_UserEvent(&ev)) {
1292 iForIndices(i, d->window->base.roots) { 1303 iConstForEach(PtrArray, iter, windows) {
1293 iRoot *root = d->window->base.roots[i]; 1304 iWindow *window = iter.ptr;
1294 if (root) { 1305 iForIndices(i, window->roots) {
1295 arrange_Widget(root->widget); 1306 iRoot *root = window->roots[i];
1296 } 1307 if (root) {
1308 arrange_Widget(root->widget);
1309 }
1310 }
1297 } 1311 }
1298 } 1312 }
1299 if (!wasUsed) { 1313 if (!wasUsed) {
1300 /* No widget handled the command, so we'll do it. */ 1314 /* No widget handled the command, so we'll do it. */
1315 setCurrent_Window(d->window);
1301 handleCommand_App(ev.user.data1); 1316 handleCommand_App(ev.user.data1);
1302 } 1317 }
1303 /* Allocated by postCommand_Apps(). */ 1318 /* Allocated by postCommand_Apps(). */
1304 free(ev.user.data1); 1319 free(ev.user.data1);
1305 } 1320 }
1306 /* Update when hover has changed. */ 1321 /* Refresh after hover changes. */ {
1307 if (d->window->base.lastHover != d->window->base.hover) { 1322 iConstForEach(PtrArray, iter, windows) {
1308 refresh_Widget(d->window->base.lastHover); 1323 iWindow *window = iter.ptr;
1309 refresh_Widget(d->window->base.hover); 1324 if (window->lastHover != window->hover) {
1325 refresh_Widget(window->lastHover);
1326 refresh_Widget(window->hover);
1327 }
1328 }
1310 } 1329 }
1311 break; 1330 break;
1312 } 1331 }
@@ -1394,25 +1413,46 @@ static int run_App_(iApp *d) {
1394 1413
1395void refresh_App(void) { 1414void refresh_App(void) {
1396 iApp *d = &app_; 1415 iApp *d = &app_;
1397 iForIndices(i, d->window->base.roots) {
1398 iRoot *root = d->window->base.roots[i];
1399 if (root) {
1400 destroyPending_Root(root);
1401 }
1402 }
1403#if defined (LAGRANGE_ENABLE_IDLE_SLEEP) 1416#if defined (LAGRANGE_ENABLE_IDLE_SLEEP)
1404 if (d->warmupFrames == 0 && d->isIdling) { 1417 if (d->warmupFrames == 0 && d->isIdling) {
1405 return; 1418 return;
1406 } 1419 }
1407#endif 1420#endif
1421 const iPtrArray *windows = listWindows_App_(d);
1422 /* Destroy pending widgets. */ {
1423 iConstForEach(PtrArray, j, windows) {
1424 iWindow *win = j.ptr;
1425 setCurrent_Window(win);
1426 iForIndices(i, win->roots) {
1427 iRoot *root = win->roots[i];
1428 if (root) {
1429 destroyPending_Root(root);
1430 }
1431 }
1432 }
1433 }
1434 /* TODO: Pending refresh is window-specific. */
1408 if (!exchange_Atomic(&d->pendingRefresh, iFalse)) { 1435 if (!exchange_Atomic(&d->pendingRefresh, iFalse)) {
1409 return; 1436 return;
1410 } 1437 }
1411// iTime draw; 1438 /* Draw each window. */ {
1412// initCurrent_Time(&draw); 1439 iConstForEach(PtrArray, j, windows) {
1413 draw_MainWindow(d->window); 1440 iWindow *win = j.ptr;
1414// printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000)); 1441 setCurrent_Window(win);
1415// fflush(stdout); 1442 switch (win->type) {
1443 case main_WindowType:
1444 // iTime draw;
1445 // initCurrent_Time(&draw);
1446 draw_MainWindow(as_MainWindow(win));
1447 // printf("draw: %lld \u03bcs\n", (long long) (elapsedSeconds_Time(&draw) * 1000000));
1448 // fflush(stdout);
1449 break;
1450 default:
1451 draw_Window(win);
1452 break;
1453 }
1454 }
1455 }
1416 if (d->warmupFrames > 0) { 1456 if (d->warmupFrames > 0) {
1417 d->warmupFrames--; 1457 d->warmupFrames--;
1418 } 1458 }
@@ -1485,12 +1525,6 @@ void postRefresh_App(void) {
1485 } 1525 }
1486} 1526}
1487 1527
1488void postImmediateRefresh_App(void) {
1489 SDL_Event ev = { .type = SDL_USEREVENT };
1490 ev.user.code = immediateRefresh_UserEventCode;
1491 SDL_PushEvent(&ev);
1492}
1493
1494void postCommand_Root(iRoot *d, const char *command) { 1528void postCommand_Root(iRoot *d, const char *command) {
1495 iAssert(command); 1529 iAssert(command);
1496 if (strlen(command) == 0) { 1530 if (strlen(command) == 0) {
@@ -1546,7 +1580,7 @@ void postCommandf_App(const char *command, ...) {
1546} 1580}
1547 1581
1548void rootOrder_App(iRoot *roots[2]) { 1582void rootOrder_App(iRoot *roots[2]) {
1549 const iWindow *win = as_Window(app_.window); 1583 const iWindow *win = get_Window();
1550 roots[0] = win->keyRoot; 1584 roots[0] = win->keyRoot;
1551 roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]); 1585 roots[1] = (roots[0] == win->roots[0] ? win->roots[1] : win->roots[0]);
1552} 1586}
@@ -1583,6 +1617,16 @@ void removeTicker_App(iTickerFunc ticker, iAny *context) {
1583 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker }); 1617 remove_SortedArray(&d->tickers, &(iTicker){ context, NULL, ticker });
1584} 1618}
1585 1619
1620void addPopup_App(iWindow *popup) {
1621 iApp *d = &app_;
1622 pushBack_PtrArray(&d->popupWindows, popup);
1623}
1624
1625void removePopup_App(iWindow *popup) {
1626 iApp *d = &app_;
1627 removeOne_PtrArray(&d->popupWindows, popup);
1628}
1629
1586iMimeHooks *mimeHooks_App(void) { 1630iMimeHooks *mimeHooks_App(void) {
1587 return app_.mimehooks; 1631 return app_.mimehooks;
1588} 1632}
@@ -1836,8 +1880,10 @@ iDocumentWidget *newTab_App(const iDocumentWidget *duplicateOf, iBool switchToNe
1836static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) { 1880static iBool handleIdentityCreationCommands_(iWidget *dlg, const char *cmd) {
1837 iApp *d = &app_; 1881 iApp *d = &app_;
1838 if (equal_Command(cmd, "ident.showmore")) { 1882 if (equal_Command(cmd, "ident.showmore")) {
1839 iForEach(ObjectList, i, 1883 iForEach(ObjectList,
1840 children_Widget(findChild_Widget(dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) { 1884 i,
1885 children_Widget(findChild_Widget(
1886 dlg, isUsingPanelLayout_Mobile() ? "panel.top" : "headings"))) {
1841 if (flags_Widget(i.object) & collapse_WidgetFlag) { 1887 if (flags_Widget(i.object) & collapse_WidgetFlag) {
1842 setFlags_Widget(i.object, hidden_WidgetFlag, iFalse); 1888 setFlags_Widget(i.object, hidden_WidgetFlag, iFalse);
1843 } 1889 }
@@ -1978,9 +2024,15 @@ const iString *searchQueryUrl_App(const iString *queryStringUnescaped) {
1978 return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped)); 2024 return collectNewFormat_String("%s?%s", cstr_String(&d->prefs.searchUrl), cstr_String(escaped));
1979} 2025}
1980 2026
2027static void resetFonts_App_(iApp *d) {
2028 iConstForEach(PtrArray, win, listWindows_App_(d)) {
2029 resetFonts_Text(text_Window(win.ptr));
2030 }
2031}
2032
1981iBool handleCommand_App(const char *cmd) { 2033iBool handleCommand_App(const char *cmd) {
1982 iApp *d = &app_; 2034 iApp *d = &app_;
1983 const iBool isFrozen = !d->window || d->window->base.isDrawFrozen; 2035 const iBool isFrozen = !d->window || d->window->isDrawFrozen;
1984 if (equal_Command(cmd, "config.error")) { 2036 if (equal_Command(cmd, "config.error")) {
1985 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR", 2037 makeSimpleMessage_Widget(uiTextCaution_ColorEscape "CONFIG ERROR",
1986 format_CStr("Error in config file: %s\n" 2038 format_CStr("Error in config file: %s\n"
@@ -2047,18 +2099,18 @@ iBool handleCommand_App(const char *cmd) {
2047 return iTrue; 2099 return iTrue;
2048 } 2100 }
2049 else if (equal_Command(cmd, "font.reset")) { 2101 else if (equal_Command(cmd, "font.reset")) {
2050 resetFonts_Text(); 2102 resetFonts_App_(d);
2051 return iTrue; 2103 return iTrue;
2052 } 2104 }
2053 else if (equal_Command(cmd, "font.user")) { 2105 else if (equal_Command(cmd, "font.user")) {
2054 const char *path = suffixPtr_Command(cmd, "path"); 2106 const char *path = suffixPtr_Command(cmd, "path");
2055 if (cmp_String(&d->prefs.symbolFontPath, path)) { 2107 if (cmp_String(&d->prefs.symbolFontPath, path)) {
2056 if (!isFrozen) { 2108 if (!isFrozen) {
2057 setFreezeDraw_Window(get_Window(), iTrue); 2109 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
2058 } 2110 }
2059 setCStr_String(&d->prefs.symbolFontPath, path); 2111 setCStr_String(&d->prefs.symbolFontPath, path);
2060 loadUserFonts_Text(); 2112 loadUserFonts_Text();
2061 resetFonts_Text(); 2113 resetFonts_App_(d);
2062 if (!isFrozen) { 2114 if (!isFrozen) {
2063 postCommand_App("font.changed"); 2115 postCommand_App("font.changed");
2064 postCommand_App("window.unfreeze"); 2116 postCommand_App("window.unfreeze");
@@ -2068,10 +2120,10 @@ iBool handleCommand_App(const char *cmd) {
2068 } 2120 }
2069 else if (equal_Command(cmd, "font.set")) { 2121 else if (equal_Command(cmd, "font.set")) {
2070 if (!isFrozen) { 2122 if (!isFrozen) {
2071 setFreezeDraw_Window(get_Window(), iTrue); 2123 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
2072 } 2124 }
2073 d->prefs.font = arg_Command(cmd); 2125 d->prefs.font = arg_Command(cmd);
2074 setContentFont_Text(d->prefs.font); 2126 setContentFont_Text(text_Window(d->window), d->prefs.font);
2075 if (!isFrozen) { 2127 if (!isFrozen) {
2076 postCommand_App("font.changed"); 2128 postCommand_App("font.changed");
2077 postCommand_App("window.unfreeze"); 2129 postCommand_App("window.unfreeze");
@@ -2080,10 +2132,10 @@ iBool handleCommand_App(const char *cmd) {
2080 } 2132 }
2081 else if (equal_Command(cmd, "headingfont.set")) { 2133 else if (equal_Command(cmd, "headingfont.set")) {
2082 if (!isFrozen) { 2134 if (!isFrozen) {
2083 setFreezeDraw_Window(get_Window(), iTrue); 2135 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
2084 } 2136 }
2085 d->prefs.headingFont = arg_Command(cmd); 2137 d->prefs.headingFont = arg_Command(cmd);
2086 setHeadingFont_Text(d->prefs.headingFont); 2138 setHeadingFont_Text(text_Window(d->window), d->prefs.headingFont);
2087 if (!isFrozen) { 2139 if (!isFrozen) {
2088 postCommand_App("font.changed"); 2140 postCommand_App("font.changed");
2089 postCommand_App("window.unfreeze"); 2141 postCommand_App("window.unfreeze");
@@ -2092,10 +2144,10 @@ iBool handleCommand_App(const char *cmd) {
2092 } 2144 }
2093 else if (equal_Command(cmd, "zoom.set")) { 2145 else if (equal_Command(cmd, "zoom.set")) {
2094 if (!isFrozen) { 2146 if (!isFrozen) {
2095 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ 2147 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */
2096 } 2148 }
2097 d->prefs.zoomPercent = arg_Command(cmd); 2149 d->prefs.zoomPercent = arg_Command(cmd);
2098 setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); 2150 setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);
2099 if (!isFrozen) { 2151 if (!isFrozen) {
2100 postCommand_App("font.changed"); 2152 postCommand_App("font.changed");
2101 postCommand_App("window.unfreeze"); 2153 postCommand_App("window.unfreeze");
@@ -2104,14 +2156,14 @@ iBool handleCommand_App(const char *cmd) {
2104 } 2156 }
2105 else if (equal_Command(cmd, "zoom.delta")) { 2157 else if (equal_Command(cmd, "zoom.delta")) {
2106 if (!isFrozen) { 2158 if (!isFrozen) {
2107 setFreezeDraw_Window(get_Window(), iTrue); /* no intermediate draws before docs updated */ 2159 setFreezeDraw_MainWindow(get_MainWindow(), iTrue); /* no intermediate draws before docs updated */
2108 } 2160 }
2109 int delta = arg_Command(cmd); 2161 int delta = arg_Command(cmd);
2110 if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) { 2162 if (d->prefs.zoomPercent < 100 || (delta < 0 && d->prefs.zoomPercent == 100)) {
2111 delta /= 2; 2163 delta /= 2;
2112 } 2164 }
2113 d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200); 2165 d->prefs.zoomPercent = iClamp(d->prefs.zoomPercent + delta, 50, 200);
2114 setContentFontSize_Text((float) d->prefs.zoomPercent / 100.0f); 2166 setContentFontSize_Text(text_Window(d->window), (float) d->prefs.zoomPercent / 100.0f);
2115 if (!isFrozen) { 2167 if (!isFrozen) {
2116 postCommand_App("font.changed"); 2168 postCommand_App("font.changed");
2117 postCommand_App("window.unfreeze"); 2169 postCommand_App("window.unfreeze");
@@ -2211,7 +2263,7 @@ iBool handleCommand_App(const char *cmd) {
2211 equal_Command(cmd, "prefs.mono.gopher.changed")) { 2263 equal_Command(cmd, "prefs.mono.gopher.changed")) {
2212 const iBool isSet = (arg_Command(cmd) != 0); 2264 const iBool isSet = (arg_Command(cmd) != 0);
2213 if (!isFrozen) { 2265 if (!isFrozen) {
2214 setFreezeDraw_Window(as_Window(d->window), iTrue); 2266 setFreezeDraw_MainWindow(get_MainWindow(), iTrue);
2215 } 2267 }
2216 if (startsWith_CStr(cmd, "prefs.mono.gemini")) { 2268 if (startsWith_CStr(cmd, "prefs.mono.gemini")) {
2217 d->prefs.monospaceGemini = isSet; 2269 d->prefs.monospaceGemini = isSet;
@@ -2936,3 +2988,7 @@ iStringSet *listOpenURLs_App(void) {
2936 iRelease(docs); 2988 iRelease(docs);
2937 return set; 2989 return set;
2938} 2990}
2991
2992iMainWindow *mainWindow_App(void) {
2993 return app_.window;
2994}
diff --git a/src/app.h b/src/app.h
index 08589000..8966e8c7 100644
--- a/src/app.h
+++ b/src/app.h
@@ -22,8 +22,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
22 22
23#pragma once 23#pragma once
24 24
25/* Application core: event loop, base event processing, audio synth. */
26
27#include <the_Foundation/objectlist.h> 25#include <the_Foundation/objectlist.h>
28#include <the_Foundation/string.h> 26#include <the_Foundation/string.h>
29#include <the_Foundation/stringset.h> 27#include <the_Foundation/stringset.h>
@@ -35,6 +33,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
35iDeclareType(Bookmarks) 33iDeclareType(Bookmarks)
36iDeclareType(DocumentWidget) 34iDeclareType(DocumentWidget)
37iDeclareType(GmCerts) 35iDeclareType(GmCerts)
36iDeclareType(MainWindow)
38iDeclareType(MimeHooks) 37iDeclareType(MimeHooks)
39iDeclareType(Periodic) 38iDeclareType(Periodic)
40iDeclareType(Root) 39iDeclareType(Root)
@@ -61,14 +60,12 @@ enum iAppEventMode {
61enum iUserEventCode { 60enum iUserEventCode {
62 command_UserEventCode = 1, 61 command_UserEventCode = 1,
63 refresh_UserEventCode, 62 refresh_UserEventCode,
64 arrange_UserEventCode,
65 asleep_UserEventCode, 63 asleep_UserEventCode,
66 /* The start of a potential touch tap event is notified via a custom event because 64 /* The start of a potential touch tap event is notified via a custom event because
67 sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will 65 sending SDL_MOUSEBUTTONDOWN would be premature: we don't know how long the tap will
68 take, it could turn into a tap-and-hold for example. */ 66 take, it could turn into a tap-and-hold for example. */
69 widgetTapBegins_UserEventCode, 67 widgetTapBegins_UserEventCode,
70 widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */ 68 widgetTouchEnds_UserEventCode, /* finger lifted, but momentum may continue */
71 immediateRefresh_UserEventCode, /* refresh even though more events are pending */
72}; 69};
73 70
74const iString *execPath_App (void); 71const iString *execPath_App (void);
@@ -119,8 +116,9 @@ iAny * findWidget_App (const char *id);
119void addTicker_App (iTickerFunc ticker, iAny *context); 116void addTicker_App (iTickerFunc ticker, iAny *context);
120void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context); 117void addTickerRoot_App (iTickerFunc ticker, iRoot *root, iAny *context);
121void removeTicker_App (iTickerFunc ticker, iAny *context); 118void removeTicker_App (iTickerFunc ticker, iAny *context);
119void addPopup_App (iWindow *popup);
120void removePopup_App (iWindow *popup);
122void postRefresh_App (void); 121void postRefresh_App (void);
123void postImmediateRefresh_App(void);
124void postCommand_Root (iRoot *, const char *command); 122void postCommand_Root (iRoot *, const char *command);
125void postCommandf_Root (iRoot *, const char *command, ...); 123void postCommandf_Root (iRoot *, const char *command, ...);
126void postCommandf_App (const char *command, ...); 124void postCommandf_App (const char *command, ...);
@@ -138,3 +136,5 @@ iDocumentWidget * document_Command (const char *cmd);
138 136
139void openInDefaultBrowser_App (const iString *url); 137void openInDefaultBrowser_App (const iString *url);
140void revealPath_App (const iString *path); 138void revealPath_App (const iString *path);
139
140iMainWindow *mainWindow_App(void);
diff --git a/src/ios.m b/src/ios.m
index 3fb0af48..b46fb8dc 100644
--- a/src/ios.m
+++ b/src/ios.m
@@ -247,14 +247,14 @@ didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
247 UIView *view = [viewController_(get_Window()) view]; 247 UIView *view = [viewController_(get_Window()) view];
248 CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil]; 248 CGRect keyboardFrame = [view convertRect:rawFrame fromView:nil];
249// NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame)); 249// NSLog(@"keyboardFrame: %@", NSStringFromCGRect(keyboardFrame));
250 iWindow *window = get_Window(); 250 iMainWindow *window = get_MainWindow();
251 const iInt2 rootSize = size_Root(window->roots[0]); 251 const iInt2 rootSize = size_Root(window->base.roots[0]);
252 const int keyTop = keyboardFrame.origin.y * window->pixelRatio; 252 const int keyTop = keyboardFrame.origin.y * window->base.pixelRatio;
253 setKeyboardHeight_Window(window, rootSize.y - keyTop); 253 setKeyboardHeight_MainWindow(window, rootSize.y - keyTop);
254} 254}
255 255
256-(void)keyboardOffScreen:(NSNotification *)notification { 256-(void)keyboardOffScreen:(NSNotification *)notification {
257 setKeyboardHeight_Window(get_Window(), 0); 257 setKeyboardHeight_MainWindow(get_MainWindow(), 0);
258} 258}
259@end 259@end
260 260
diff --git a/src/macos.h b/src/macos.h
index 0d3f097a..20b95943 100644
--- a/src/macos.h
+++ b/src/macos.h
@@ -24,6 +24,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
24 24
25#include "ui/util.h" 25#include "ui/util.h"
26 26
27iDeclareType(Window)
28
27/* Platform-specific functionality for macOS */ 29/* Platform-specific functionality for macOS */
28 30
29iBool shouldDefaultToMetalRenderer_MacOS (void); 31iBool shouldDefaultToMetalRenderer_MacOS (void);
@@ -31,6 +33,7 @@ iBool shouldDefaultToMetalRenderer_MacOS (void);
31void enableMomentumScroll_MacOS (void); 33void enableMomentumScroll_MacOS (void);
32void registerURLHandler_MacOS (void); 34void registerURLHandler_MacOS (void);
33void setupApplication_MacOS (void); 35void setupApplication_MacOS (void);
36void hideTitleBar_MacOS (iWindow *window);
34void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count); 37void insertMenuItems_MacOS (const char *menuLabel, int atIndex, const iMenuItem *items, size_t count);
35void removeMenu_MacOS (int atIndex); 38void removeMenu_MacOS (int atIndex);
36void enableMenu_MacOS (const char *menuLabel, iBool enable); 39void enableMenu_MacOS (const char *menuLabel, iBool enable);
diff --git a/src/macos.m b/src/macos.m
index d588fa4a..298db0f8 100644
--- a/src/macos.m
+++ b/src/macos.m
@@ -30,6 +30,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
30#include "ui/window.h" 30#include "ui/window.h"
31 31
32#include <SDL_timer.h> 32#include <SDL_timer.h>
33#include <SDL_syswm.h>
33 34
34#import <AppKit/AppKit.h> 35#import <AppKit/AppKit.h>
35 36
@@ -51,6 +52,16 @@ static iInt2 macVer_(void) {
51 return init_I2(10, 10); 52 return init_I2(10, 10);
52} 53}
53 54
55static NSWindow *nsWindow_(SDL_Window *window) {
56 SDL_SysWMinfo wm;
57 SDL_VERSION(&wm.version);
58 if (SDL_GetWindowWMInfo(window, &wm)) {
59 return wm.info.cocoa.window;
60 }
61 iAssert(false);
62 return nil;
63}
64
54static NSString *currentSystemAppearance_(void) { 65static NSString *currentSystemAppearance_(void) {
55 /* This API does not exist on 10.13. */ 66 /* This API does not exist on 10.13. */
56 if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) { 67 if ([NSApp respondsToSelector:@selector(effectiveAppearance)]) {
@@ -370,6 +381,11 @@ void setupApplication_MacOS(void) {
370 windowCloseItem.action = @selector(closeTab); 381 windowCloseItem.action = @selector(closeTab);
371} 382}
372 383
384void hideTitleBar_MacOS(iWindow *window) {
385 NSWindow *w = nsWindow_(window->win);
386 w.styleMask = 0; /* borderless */
387}
388
373void enableMenu_MacOS(const char *menuLabel, iBool enable) { 389void enableMenu_MacOS(const char *menuLabel, iBool enable) {
374 menuLabel = translateCStr_Lang(menuLabel); 390 menuLabel = translateCStr_Lang(menuLabel);
375 NSApplication *app = [NSApplication sharedApplication]; 391 NSApplication *app = [NSApplication sharedApplication];
@@ -377,7 +393,6 @@ void enableMenu_MacOS(const char *menuLabel, iBool enable) {
377 NSString *label = [NSString stringWithUTF8String:menuLabel]; 393 NSString *label = [NSString stringWithUTF8String:menuLabel];
378 NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]]; 394 NSMenuItem *menuItem = [appMenu itemAtIndex:[appMenu indexOfItemWithTitle:label]];
379 [menuItem setEnabled:enable]; 395 [menuItem setEnabled:enable];
380 [label release];
381} 396}
382 397
383void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) { 398void enableMenuItem_MacOS(const char *menuItemCommand, iBool enable) {
diff --git a/src/ui/documentwidget.c b/src/ui/documentwidget.c
index ed9e41d6..6f9824de 100644
--- a/src/ui/documentwidget.c
+++ b/src/ui/documentwidget.c
@@ -750,7 +750,7 @@ static uint32_t mediaUpdateInterval_DocumentWidget_(const iDocumentWidget *d) {
750 if (document_App() != d) { 750 if (document_App() != d) {
751 return 0; 751 return 0;
752 } 752 }
753 if (get_Window()->isDrawFrozen) { 753 if (as_MainWindow(window_Widget(d))->isDrawFrozen) {
754 return 0; 754 return 0;
755 } 755 }
756 static const uint32_t invalidInterval_ = ~0u; 756 static const uint32_t invalidInterval_ = ~0u;
diff --git a/src/ui/inputwidget.c b/src/ui/inputwidget.c
index a561d5bd..f02bf408 100644
--- a/src/ui/inputwidget.c
+++ b/src/ui/inputwidget.c
@@ -2352,7 +2352,7 @@ static void draw_InputWidget_(const iInputWidget *d) {
2352 } 2352 }
2353 /* Draw the insertion point. */ 2353 /* Draw the insertion point. */
2354 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) && 2354 if (isFocused && d->cursorVis && contains_Range(&visLines, d->cursor.y) &&
2355 isEmpty_Range(&d->mark)) { 2355 (deviceType_App() == desktop_AppDeviceType || isEmpty_Range(&d->mark))) {
2356 iInt2 curSize; 2356 iInt2 curSize;
2357 iRangecc cursorChar = iNullRange; 2357 iRangecc cursorChar = iNullRange;
2358 int visWrapsAbove = 0; 2358 int visWrapsAbove = 0;
diff --git a/src/ui/root.c b/src/ui/root.c
index 52a08eca..9e290b05 100644
--- a/src/ui/root.c
+++ b/src/ui/root.c
@@ -298,16 +298,6 @@ void destroyPending_Root(iRoot *d) {
298 setCurrent_Root(oldRoot); 298 setCurrent_Root(oldRoot);
299} 299}
300 300
301void postArrange_Root(iRoot *d) {
302 if (!d->pendingArrange) {
303 d->pendingArrange = iTrue;
304 SDL_Event ev = { .type = SDL_USEREVENT };
305 ev.user.code = arrange_UserEventCode;
306 ev.user.data2 = d;
307 SDL_PushEvent(&ev);
308 }
309}
310
311iPtrArray *onTop_Root(iRoot *d) { 301iPtrArray *onTop_Root(iRoot *d) {
312 if (!d->onTop) { 302 if (!d->onTop) {
313 d->onTop = new_PtrArray(); 303 d->onTop = new_PtrArray();
diff --git a/src/ui/root.h b/src/ui/root.h
index 740e97c9..04dd5e16 100644
--- a/src/ui/root.h
+++ b/src/ui/root.h
@@ -9,6 +9,7 @@ iDeclareType(Root)
9 9
10struct Impl_Root { 10struct Impl_Root {
11 iWidget * widget; 11 iWidget * widget;
12 iWindow * window;
12 iPtrArray *onTop; /* order is important; last one is topmost */ 13 iPtrArray *onTop; /* order is important; last one is topmost */
13 iPtrSet * pendingDestruction; 14 iPtrSet * pendingDestruction;
14 iBool pendingArrange; 15 iBool pendingArrange;
@@ -29,7 +30,6 @@ iAnyObject *findWidget_Root (const char *id); /* under curre
29 30
30iPtrArray * onTop_Root (iRoot *); 31iPtrArray * onTop_Root (iRoot *);
31void destroyPending_Root (iRoot *); 32void destroyPending_Root (iRoot *);
32void postArrange_Root (iRoot *);
33 33
34void updateMetrics_Root (iRoot *); 34void updateMetrics_Root (iRoot *);
35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */ 35void updatePadding_Root (iRoot *); /* TODO: is part of metrics? */
diff --git a/src/ui/text.c b/src/ui/text.c
index f7fff4bc..bf71b0e9 100644
--- a/src/ui/text.c
+++ b/src/ui/text.c
@@ -290,7 +290,9 @@ struct Impl_Text {
290 iRegExp * ansiEscape; 290 iRegExp * ansiEscape;
291}; 291};
292 292
293static iText text_; 293iDefineTypeConstructionArgs(Text, (SDL_Renderer *render), render)
294
295static iText *activeText_;
294static iBlock *userFont_; 296static iBlock *userFont_;
295 297
296static void initFonts_Text_(iText *d) { 298static void initFonts_Text_(iText *d) {
@@ -501,8 +503,7 @@ void loadUserFonts_Text(void) {
501 } 503 }
502} 504}
503 505
504void init_Text(SDL_Renderer *render) { 506void init_Text(iText *d, SDL_Renderer *render) {
505 iText *d = &text_;
506 loadUserFonts_Text(); 507 loadUserFonts_Text();
507 d->contentFont = nunito_TextFont; 508 d->contentFont = nunito_TextFont;
508 d->headingFont = nunito_TextFont; 509 d->headingFont = nunito_TextFont;
@@ -521,8 +522,7 @@ void init_Text(SDL_Renderer *render) {
521 initFonts_Text_(d); 522 initFonts_Text_(d);
522} 523}
523 524
524void deinit_Text(void) { 525void deinit_Text(iText *d) {
525 iText *d = &text_;
526 SDL_FreePalette(d->grayscale); 526 SDL_FreePalette(d->grayscale);
527 deinitFonts_Text_(d); 527 deinitFonts_Text_(d);
528 deinitCache_Text_(d); 528 deinitCache_Text_(d);
@@ -530,30 +530,34 @@ void deinit_Text(void) {
530 iRelease(d->ansiEscape); 530 iRelease(d->ansiEscape);
531} 531}
532 532
533void setCurrent_Text(iText *d) {
534 activeText_ = d;
535}
536
533void setOpacity_Text(float opacity) { 537void setOpacity_Text(float opacity) {
534 SDL_SetTextureAlphaMod(text_.cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f); 538 SDL_SetTextureAlphaMod(activeText_->cache, iClamp(opacity, 0.0f, 1.0f) * 255 + 0.5f);
535} 539}
536 540
537void setContentFont_Text(enum iTextFont font) { 541void setContentFont_Text(iText *d, enum iTextFont font) {
538 if (text_.contentFont != font) { 542 if (d->contentFont != font) {
539 text_.contentFont = font; 543 d->contentFont = font;
540 resetFonts_Text(); 544 resetFonts_Text(d);
541 } 545 }
542} 546}
543 547
544void setHeadingFont_Text(enum iTextFont font) { 548void setHeadingFont_Text(iText *d, enum iTextFont font) {
545 if (text_.headingFont != font) { 549 if (d->headingFont != font) {
546 text_.headingFont = font; 550 d->headingFont = font;
547 resetFonts_Text(); 551 resetFonts_Text(d);
548 } 552 }
549} 553}
550 554
551void setContentFontSize_Text(float fontSizeFactor) { 555void setContentFontSize_Text(iText *d, float fontSizeFactor) {
552 fontSizeFactor *= contentScale_Text_; 556 fontSizeFactor *= contentScale_Text_;
553 iAssert(fontSizeFactor > 0); 557 iAssert(fontSizeFactor > 0);
554 if (iAbs(text_.contentFontSize - fontSizeFactor) > 0.001f) { 558 if (iAbs(d->contentFontSize - fontSizeFactor) > 0.001f) {
555 text_.contentFontSize = fontSizeFactor; 559 d->contentFontSize = fontSizeFactor;
556 resetFonts_Text(); 560 resetFonts_Text(d);
557 } 561 }
558} 562}
559 563
@@ -565,8 +569,7 @@ static void resetCache_Text_(iText *d) {
565 initCache_Text_(d); 569 initCache_Text_(d);
566} 570}
567 571
568void resetFonts_Text(void) { 572void resetFonts_Text(iText *d) {
569 iText *d = &text_;
570 deinitFonts_Text_(d); 573 deinitFonts_Text_(d);
571 deinitCache_Text_(d); 574 deinitCache_Text_(d);
572 initCache_Text_(d); 575 initCache_Text_(d);
@@ -574,7 +577,7 @@ void resetFonts_Text(void) {
574} 577}
575 578
576iLocalDef iFont *font_Text_(enum iFontId id) { 579iLocalDef iFont *font_Text_(enum iFontId id) {
577 return &text_.fonts[id & mask_FontId]; 580 return &activeText_->fonts[id & mask_FontId];
578} 581}
579 582
580static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) { 583static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, float xShift) {
@@ -584,7 +587,7 @@ static SDL_Surface *rasterizeGlyph_Font_(const iFont *d, uint32_t glyphIndex, fl
584 SDL_Surface *surface8 = 587 SDL_Surface *surface8 =
585 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8); 588 SDL_CreateRGBSurfaceWithFormatFrom(bmp, w, h, 8, w, SDL_PIXELFORMAT_INDEX8);
586 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE); 589 SDL_SetSurfaceBlendMode(surface8, SDL_BLENDMODE_NONE);
587 SDL_SetSurfacePalette(surface8, text_.grayscale); 590 SDL_SetSurfacePalette(surface8, activeText_->grayscale);
588#if LAGRANGE_RASTER_DEPTH != 8 591#if LAGRANGE_RASTER_DEPTH != 8
589 /* Convert to the cache format. */ 592 /* Convert to the cache format. */
590 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0); 593 SDL_Surface *surf = SDL_ConvertSurfaceFormat(surface8, LAGRANGE_RASTER_FORMAT, 0);
@@ -631,7 +634,7 @@ static void allocate_Font_(iFont *d, iGlyph *glyph, int hoff) {
631 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1); 634 &d->font, index_Glyph_(glyph), d->xScale, d->yScale, hoff * 0.5f, 0.0f, &x0, &y0, &x1, &y1);
632 glRect->size = init_I2(x1 - x0, y1 - y0); 635 glRect->size = init_I2(x1 - x0, y1 - y0);
633 /* Determine placement in the glyph cache texture, advancing in rows. */ 636 /* Determine placement in the glyph cache texture, advancing in rows. */
634 glRect->pos = assignCachePos_Text_(&text_, glRect->size); 637 glRect->pos = assignCachePos_Text_(activeText_, glRect->size);
635 glyph->d[hoff] = init_I2(x0, y0); 638 glyph->d[hoff] = init_I2(x0, y0);
636 glyph->d[hoff].y += d->vertOffset; 639 glyph->d[hoff].y += d->vertOffset;
637 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */ 640 if (hoff == 0) { /* hoff==1 uses same metrics as `glyph` */
@@ -737,11 +740,11 @@ static iGlyph *glyphByIndex_Font_(iFont *d, uint32_t glyphIndex) {
737 } 740 }
738 else { 741 else {
739 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */ 742 /* If the cache is running out of space, clear it and we'll recache what's needed currently. */
740 if (text_.cacheBottom > text_.cacheSize.y - maxGlyphHeight_Text_(&text_)) { 743 if (activeText_->cacheBottom > activeText_->cacheSize.y - maxGlyphHeight_Text_(activeText_)) {
741#if !defined (NDEBUG) 744#if !defined (NDEBUG)
742 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout); 745 printf("[Text] glyph cache is full, clearing!\n"); fflush(stdout);
743#endif 746#endif
744 resetCache_Text_(&text_); 747 resetCache_Text_(activeText_);
745 } 748 }
746 glyph = new_Glyph(glyphIndex); 749 glyph = new_Glyph(glyphIndex);
747 glyph->font = d; 750 glyph->font = d;
@@ -858,7 +861,7 @@ static void finishRun_AttributedText_(iAttributedText *d, iAttributedRun *run, i
858} 861}
859 862
860static enum iFontId fontId_Text_(const iFont *font) { 863static enum iFontId fontId_Text_(const iFont *font) {
861 return (enum iFontId) (font - text_.fonts); 864 return (enum iFontId) (font - activeText_->fonts);
862} 865}
863 866
864static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) { 867static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iChar overrideChar) {
@@ -949,7 +952,7 @@ static void prepare_AttributedText_(iAttributedText *d, int overrideBaseDir, iCh
949 /* Do a regexp match in the source text. */ 952 /* Do a regexp match in the source text. */
950 iRegExpMatch m; 953 iRegExpMatch m;
951 init_RegExpMatch(&m); 954 init_RegExpMatch(&m);
952 if (match_RegExp(text_.ansiEscape, srcPos, d->source.end - srcPos, &m)) { 955 if (match_RegExp(activeText_->ansiEscape, srcPos, d->source.end - srcPos, &m)) {
953 finishRun_AttributedText_(d, &run, pos - 1); 956 finishRun_AttributedText_(d, &run, pos - 1);
954 run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), 957 run.fgColor = ansiForeground_Color(capturedRange_RegExpMatch(&m, 1),
955 tmParagraph_ColorId); 958 tmParagraph_ColorId);
@@ -1082,9 +1085,9 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1082 while (index < size_Array(glyphIndices)) { 1085 while (index < size_Array(glyphIndices)) {
1083 for (; index < size_Array(glyphIndices); index++) { 1086 for (; index < size_Array(glyphIndices); index++) {
1084 const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t); 1087 const uint32_t glyphIndex = constValue_Array(glyphIndices, index, uint32_t);
1085 const int lastCacheBottom = text_.cacheBottom; 1088 const int lastCacheBottom = activeText_->cacheBottom;
1086 iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex); 1089 iGlyph *glyph = glyphByIndex_Font_(d, glyphIndex);
1087 if (text_.cacheBottom < lastCacheBottom) { 1090 if (activeText_->cacheBottom < lastCacheBottom) {
1088 /* The cache was reset due to running out of space. We need to restart from 1091 /* The cache was reset due to running out of space. We need to restart from
1089 the beginning! */ 1092 the beginning! */
1090 bufX = 0; 1093 bufX = 0;
@@ -1103,7 +1106,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1103 LAGRANGE_RASTER_DEPTH, 1106 LAGRANGE_RASTER_DEPTH,
1104 LAGRANGE_RASTER_FORMAT); 1107 LAGRANGE_RASTER_FORMAT);
1105 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE); 1108 SDL_SetSurfaceBlendMode(buf, SDL_BLENDMODE_NONE);
1106 SDL_SetSurfacePalette(buf, text_.grayscale); 1109 SDL_SetSurfacePalette(buf, activeText_->grayscale);
1107 } 1110 }
1108 SDL_Surface *surfaces[2] = { 1111 SDL_Surface *surfaces[2] = {
1109 !isRasterized_Glyph_(glyph, 0) ? 1112 !isRasterized_Glyph_(glyph, 0) ?
@@ -1147,19 +1150,19 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1147 } 1150 }
1148 /* Finished or the buffer is full, copy the glyphs to the cache texture. */ 1151 /* Finished or the buffer is full, copy the glyphs to the cache texture. */
1149 if (!isEmpty_Array(rasters)) { 1152 if (!isEmpty_Array(rasters)) {
1150 SDL_Texture *bufTex = SDL_CreateTextureFromSurface(text_.render, buf); 1153 SDL_Texture *bufTex = SDL_CreateTextureFromSurface(activeText_->render, buf);
1151 SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE); 1154 SDL_SetTextureBlendMode(bufTex, SDL_BLENDMODE_NONE);
1152 if (!isTargetChanged) { 1155 if (!isTargetChanged) {
1153 isTargetChanged = iTrue; 1156 isTargetChanged = iTrue;
1154 oldTarget = SDL_GetRenderTarget(text_.render); 1157 oldTarget = SDL_GetRenderTarget(activeText_->render);
1155 SDL_SetRenderTarget(text_.render, text_.cache); 1158 SDL_SetRenderTarget(activeText_->render, activeText_->cache);
1156 } 1159 }
1157// printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout); 1160// printf("copying %zu rasters from %p\n", size_Array(rasters), bufTex); fflush(stdout);
1158 iConstForEach(Array, i, rasters) { 1161 iConstForEach(Array, i, rasters) {
1159 const iRasterGlyph *rg = i.value; 1162 const iRasterGlyph *rg = i.value;
1160// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size)); 1163// iAssert(isEqual_I2(rg->rect.size, rg->glyph->rect[rg->hoff].size));
1161 const iRect *glRect = &rg->glyph->rect[rg->hoff]; 1164 const iRect *glRect = &rg->glyph->rect[rg->hoff];
1162 SDL_RenderCopy(text_.render, 1165 SDL_RenderCopy(activeText_->render,
1163 bufTex, 1166 bufTex,
1164 (const SDL_Rect *) &rg->rect, 1167 (const SDL_Rect *) &rg->rect,
1165 (const SDL_Rect *) glRect); 1168 (const SDL_Rect *) glRect);
@@ -1179,7 +1182,7 @@ static void cacheGlyphs_Font_(iFont *d, const iArray *glyphIndices) {
1179 SDL_FreeSurface(buf); 1182 SDL_FreeSurface(buf);
1180 } 1183 }
1181 if (isTargetChanged) { 1184 if (isTargetChanged) {
1182 SDL_SetRenderTarget(text_.render, oldTarget); 1185 SDL_SetRenderTarget(activeText_->render, oldTarget);
1183 } 1186 }
1184} 1187}
1185 1188
@@ -1706,9 +1709,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1706 } 1709 }
1707 if (~mode & permanentColorFlag_RunMode) { 1710 if (~mode & permanentColorFlag_RunMode) {
1708 const iColor clr = run->fgColor; 1711 const iColor clr = run->fgColor;
1709 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 1712 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
1710 if (args->mode & fillBackground_RunMode) { 1713 if (args->mode & fillBackground_RunMode) {
1711 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 1714 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
1712 } 1715 }
1713 } 1716 }
1714 SDL_Rect src; 1717 SDL_Rect src;
@@ -1719,9 +1722,9 @@ static iRect run_Font_(iFont *d, const iRunArgs *args) {
1719 /* Alpha blending looks much better if the RGB components don't change in 1722 /* Alpha blending looks much better if the RGB components don't change in
1720 the partially transparent pixels. */ 1723 the partially transparent pixels. */
1721 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */ 1724 /* TODO: Backgrounds of all glyphs should be cleared before drawing anything else. */
1722 SDL_RenderFillRect(text_.render, &dst); 1725 SDL_RenderFillRect(activeText_->render, &dst);
1723 } 1726 }
1724 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 1727 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
1725#if 0 1728#if 0
1726 /* Show spaces and direction. */ 1729 /* Show spaces and direction. */
1727 if (logicalText[logPos] == 0x20) { 1730 if (logicalText[logPos] == 0x20) {
@@ -1863,7 +1866,7 @@ iTextMetrics measureN_Text(int fontId, const char *text, size_t n) {
1863} 1866}
1864 1867
1865static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) { 1868static void drawBoundedN_Text_(int fontId, iInt2 pos, int xposBound, int color, iRangecc text, size_t maxLen) {
1866 iText * d = &text_; 1869 iText * d = activeText_;
1867 iFont * font = font_Text_(fontId); 1870 iFont * font = font_Text_(fontId);
1868 const iColor clr = get_Color(color & mask_ColorId); 1871 const iColor clr = get_Color(color & mask_ColorId);
1869 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b); 1872 SDL_SetTextureColorMod(d->cache, clr.r, clr.g, clr.b);
@@ -2057,7 +2060,7 @@ iTextMetrics draw_WrapText(iWrapText *d, int fontId, iInt2 pos, int color) {
2057} 2060}
2058 2061
2059SDL_Texture *glyphCache_Text(void) { 2062SDL_Texture *glyphCache_Text(void) {
2060 return text_.cache; 2063 return activeText_->cache;
2061} 2064}
2062 2065
2063static void freeBitmap_(void *ptr) { 2066static void freeBitmap_(void *ptr) {
@@ -2170,7 +2173,7 @@ iString *renderBlockChars_Text(const iBlock *fontData, int height, enum iTextBlo
2170iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color) 2173iDefineTypeConstructionArgs(TextBuf, (iWrapText *wrapText, int font, int color), wrapText, font, color)
2171 2174
2172void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) { 2175void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {
2173 SDL_Renderer *render = text_.render; 2176 SDL_Renderer *render = activeText_->render;
2174 d->size = measure_WrapText(wrapText, font).bounds.size; 2177 d->size = measure_WrapText(wrapText, font).bounds.size;
2175 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"); 2178 SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0");
2176 if (d->size.x * d->size.y) { 2179 if (d->size.x * d->size.y) {
@@ -2191,9 +2194,9 @@ void init_TextBuf(iTextBuf *d, iWrapText *wrapText, int font, int color) {
2191 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE); 2194 SDL_SetRenderDrawBlendMode(render, SDL_BLENDMODE_NONE);
2192 SDL_SetRenderDrawColor(render, 255, 255, 255, 0); 2195 SDL_SetRenderDrawColor(render, 255, 255, 255, 0);
2193 SDL_RenderClear(render); 2196 SDL_RenderClear(render);
2194 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */ 2197 SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_NONE); /* blended when TextBuf is drawn */
2195 draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId); 2198 draw_WrapText(wrapText, font, zero_I2(), color | fillBackground_ColorId);
2196 SDL_SetTextureBlendMode(text_.cache, SDL_BLENDMODE_BLEND); 2199 SDL_SetTextureBlendMode(activeText_->cache, SDL_BLENDMODE_BLEND);
2197 SDL_SetRenderTarget(render, oldTarget); 2200 SDL_SetRenderTarget(render, oldTarget);
2198 origin_Paint = oldOrigin; 2201 origin_Paint = oldOrigin;
2199 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND); 2202 SDL_SetTextureBlendMode(d->texture, SDL_BLENDMODE_BLEND);
@@ -2212,7 +2215,7 @@ void draw_TextBuf(const iTextBuf *d, iInt2 pos, int color) {
2212 addv_I2(&pos, origin_Paint); 2215 addv_I2(&pos, origin_Paint);
2213 const iColor clr = get_Color(color); 2216 const iColor clr = get_Color(color);
2214 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b); 2217 SDL_SetTextureColorMod(d->texture, clr.r, clr.g, clr.b);
2215 SDL_RenderCopy(text_.render, 2218 SDL_RenderCopy(activeText_->render,
2216 d->texture, 2219 d->texture,
2217 &(SDL_Rect){ 0, 0, d->size.x, d->size.y }, 2220 &(SDL_Rect){ 0, 0, d->size.x, d->size.y },
2218 &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y }); 2221 &(SDL_Rect){ pos.x, pos.y, d->size.x, d->size.y });
diff --git a/src/ui/text.h b/src/ui/text.h
index ac6cc1c1..1da43818 100644
--- a/src/ui/text.h
+++ b/src/ui/text.h
@@ -139,15 +139,20 @@ enum iTextFont {
139 139
140extern int gap_Text; /* affected by content font size */ 140extern int gap_Text; /* affected by content font size */
141 141
142void init_Text (SDL_Renderer *); 142iDeclareType(Text)
143void deinit_Text (void); 143iDeclareTypeConstructionArgs(Text, SDL_Renderer *)
144
145void init_Text (iText *, SDL_Renderer *);
146void deinit_Text (iText *);
147
148void setCurrent_Text (iText *);
144 149
145void loadUserFonts_Text (void); /* based on Prefs */ 150void loadUserFonts_Text (void); /* based on Prefs */
146 151
147void setContentFont_Text (enum iTextFont font); 152void setContentFont_Text (iText *, enum iTextFont font);
148void setHeadingFont_Text (enum iTextFont font); 153void setHeadingFont_Text (iText *, enum iTextFont font);
149void setContentFontSize_Text (float fontSizeFactor); /* affects all except `default*` fonts */ 154void setContentFontSize_Text (iText *, float fontSizeFactor); /* affects all except `default*` fonts */
150void resetFonts_Text (void); 155void resetFonts_Text (iText *);
151 156
152int lineHeight_Text (int fontId); 157int lineHeight_Text (int fontId);
153iRect visualBounds_Text (int fontId, iRangecc text); 158iRect visualBounds_Text (int fontId, iRangecc text);
diff --git a/src/ui/text_simple.c b/src/ui/text_simple.c
index bf33b4be..8b1de64a 100644
--- a/src/ui/text_simple.c
+++ b/src/ui/text_simple.c
@@ -92,7 +92,7 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
92 } 92 }
93 if (args->mode & fillBackground_RunMode) { 93 if (args->mode & fillBackground_RunMode) {
94 const iColor initial = get_Color(args->color); 94 const iColor initial = get_Color(args->color);
95 SDL_SetRenderDrawColor(text_.render, initial.r, initial.g, initial.b, 0); 95 SDL_SetRenderDrawColor(activeText_->render, initial.r, initial.g, initial.b, 0);
96 } 96 }
97 /* Text rendering is not very straightforward! Let's dive in... */ 97 /* Text rendering is not very straightforward! Let's dive in... */
98 iChar prevCh = 0; 98 iChar prevCh = 0;
@@ -114,14 +114,14 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
114 chPos++; 114 chPos++;
115 iRegExpMatch m; 115 iRegExpMatch m;
116 init_RegExpMatch(&m); 116 init_RegExpMatch(&m);
117 if (match_RegExp(text_.ansiEscape, chPos, args->text.end - chPos, &m)) { 117 if (match_RegExp(activeText_->ansiEscape, chPos, args->text.end - chPos, &m)) {
118 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 118 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
119 /* Change the color. */ 119 /* Change the color. */
120 const iColor clr = 120 const iColor clr =
121 ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId); 121 ansiForeground_Color(capturedRange_RegExpMatch(&m, 1), tmParagraph_ColorId);
122 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 122 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
123 if (args->mode & fillBackground_RunMode) { 123 if (args->mode & fillBackground_RunMode) {
124 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 124 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
125 } 125 }
126 } 126 }
127 chPos = end_RegExpMatch(&m); 127 chPos = end_RegExpMatch(&m);
@@ -205,9 +205,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
205 } 205 }
206 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) { 206 if (mode & draw_RunMode && ~mode & permanentColorFlag_RunMode) {
207 const iColor clr = get_Color(colorNum); 207 const iColor clr = get_Color(colorNum);
208 SDL_SetTextureColorMod(text_.cache, clr.r, clr.g, clr.b); 208 SDL_SetTextureColorMod(activeText_->cache, clr.r, clr.g, clr.b);
209 if (args->mode & fillBackground_RunMode) { 209 if (args->mode & fillBackground_RunMode) {
210 SDL_SetRenderDrawColor(text_.render, clr.r, clr.g, clr.b, 0); 210 SDL_SetRenderDrawColor(activeText_->render, clr.r, clr.g, clr.b, 0);
211 } 211 }
212 } 212 }
213 prevCh = 0; 213 prevCh = 0;
@@ -311,9 +311,9 @@ static iRect runSimple_Font_(iFont *d, const iRunArgs *args) {
311 if (args->mode & fillBackground_RunMode) { 311 if (args->mode & fillBackground_RunMode) {
312 /* Alpha blending looks much better if the RGB components don't change in 312 /* Alpha blending looks much better if the RGB components don't change in
313 the partially transparent pixels. */ 313 the partially transparent pixels. */
314 SDL_RenderFillRect(text_.render, &dst); 314 SDL_RenderFillRect(activeText_->render, &dst);
315 } 315 }
316 SDL_RenderCopy(text_.render, text_.cache, &src, &dst); 316 SDL_RenderCopy(activeText_->render, activeText_->cache, &src, &dst);
317 } 317 }
318 xpos += advance; 318 xpos += advance;
319 if (!isSpace_Char(ch)) { 319 if (!isSpace_Char(ch)) {
diff --git a/src/ui/util.c b/src/ui/util.c
index 721aed2d..38977b96 100644
--- a/src/ui/util.c
+++ b/src/ui/util.c
@@ -613,6 +613,8 @@ iBool isAction_Widget(const iWidget *d) {
613/*-----------------------------------------------------------------------------------------------*/ 613/*-----------------------------------------------------------------------------------------------*/
614 614
615static iBool isCommandIgnoredByMenus_(const char *cmd) { 615static iBool isCommandIgnoredByMenus_(const char *cmd) {
616 if (equal_Command(cmd, "window.focus.lost") ||
617 equal_Command(cmd, "window.focus.gained")) return iTrue;
616 /* TODO: Perhaps a common way of indicating which commands are notifications and should not 618 /* TODO: Perhaps a common way of indicating which commands are notifications and should not
617 be reacted to by menus? */ 619 be reacted to by menus? */
618 return equal_Command(cmd, "media.updated") || 620 return equal_Command(cmd, "media.updated") ||
@@ -810,6 +812,10 @@ static void updateMenuItemFonts_Widget_(iWidget *d) {
810 } 812 }
811} 813}
812 814
815iLocalDef iBool isUsingMenuPopupWindows_(void) {
816 return deviceType_App() == desktop_AppDeviceType;
817}
818
813void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) { 819void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
814 const iRect rootRect = rect_Root(d->root); 820 const iRect rootRect = rect_Root(d->root);
815 const iInt2 rootSize = rootRect.size; 821 const iInt2 rootSize = rootRect.size;
@@ -822,6 +828,26 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
822 processEvents_App(postedEventsOnly_AppEventMode); 828 processEvents_App(postedEventsOnly_AppEventMode);
823 setFlags_Widget(d, hidden_WidgetFlag, iFalse); 829 setFlags_Widget(d, hidden_WidgetFlag, iFalse);
824 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue); 830 setFlags_Widget(d, commandOnMouseMiss_WidgetFlag, iTrue);
831 if (isUsingMenuPopupWindows_()) {
832 if (postCommands) {
833 postCommand_Widget(d, "menu.opened");
834 }
835 updateMenuItemFonts_Widget_(d);
836 iRoot *oldRoot = current_Root();
837 setFlags_Widget(d, keepOnTop_WidgetFlag, iFalse);
838 setUserData_Object(d, parent_Widget(d));
839 removeChild_Widget(parent_Widget(d), d); /* we'll borrow the widget for a while */
840 iInt2 mousePos;
841 SDL_GetGlobalMouseState(&mousePos.x, &mousePos.y);
842 iWindow *win = newPopup_Window(sub_I2(mousePos, divi_I2(gap2_UI, 2)), d);
843 SDL_SetWindowTitle(win->win, "Menu");
844 addPopup_App(win); /* window takes the widget */
845 SDL_ShowWindow(win->win);
846 draw_Window(win);
847 setCurrent_Window(mainWindow_App());
848 setCurrent_Root(oldRoot);
849 return;
850 }
825 raise_Widget(d); 851 raise_Widget(d);
826 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse); 852 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iFalse);
827 if (isPortraitPhone) { 853 if (isPortraitPhone) {
@@ -836,7 +862,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
836 arrange_Widget(d); 862 arrange_Widget(d);
837 if (isPortraitPhone) { 863 if (isPortraitPhone) {
838 if (isSlidePanel) { 864 if (isSlidePanel) {
839 d->rect.pos = zero_I2(); //neg_I2(bounds_Widget(parent_Widget(d)).pos); 865 d->rect.pos = zero_I2();
840 } 866 }
841 else { 867 else {
842 d->rect.pos = init_I2(0, rootSize.y); 868 d->rect.pos = init_I2(0, rootSize.y);
@@ -856,7 +882,7 @@ void openMenuFlags_Widget(iWidget *d, iInt2 windowCoord, iBool postCommands) {
856 float l, t, r, b; 882 float l, t, r, b;
857 safeAreaInsets_iOS(&l, &t, &r, &b); 883 safeAreaInsets_iOS(&l, &t, &r, &b);
858 topExcess += t; 884 topExcess += t;
859 bottomExcess += iMax(b, get_Window()->keyboardHeight); 885 bottomExcess += iMax(b, get_MainWindow()->keyboardHeight);
860 leftExcess += l; 886 leftExcess += l;
861 rightExcess += r; 887 rightExcess += r;
862 } 888 }
@@ -884,6 +910,18 @@ void closeMenu_Widget(iWidget *d) {
884 if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) { 910 if (d == NULL || flags_Widget(d) & hidden_WidgetFlag) {
885 return; /* Already closed. */ 911 return; /* Already closed. */
886 } 912 }
913 if (isUsingMenuPopupWindows_()) {
914 iWindow *win = window_Widget(d);
915 iAssert(type_Window(win) == popup_WindowType);
916 iWidget *originalParent = userData_Object(d);
917 setUserData_Object(d, NULL);
918 win->roots[0]->widget = NULL;
919 setRoot_Widget(d, originalParent->root);
920 addChild_Widget(originalParent, d);
921 setFlags_Widget(d, keepOnTop_WidgetFlag, iTrue);
922 SDL_HideWindow(win->win);
923 collect_Garbage(win, (iDeleteFunc) delete_Window); /* get rid of it after event processing */
924 }
887 setFlags_Widget(d, hidden_WidgetFlag, iTrue); 925 setFlags_Widget(d, hidden_WidgetFlag, iTrue);
888 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue); 926 setFlags_Widget(findChild_Widget(d, "menu.cancel"), disabled_WidgetFlag, iTrue);
889 postRefresh_App(); 927 postRefresh_App();
diff --git a/src/ui/widget.c b/src/ui/widget.c
index 23c19315..7b33a752 100644
--- a/src/ui/widget.c
+++ b/src/ui/widget.c
@@ -271,6 +271,10 @@ iWidget *root_Widget(const iWidget *d) {
271 return d ? d->root->widget : NULL; 271 return d ? d->root->widget : NULL;
272} 272}
273 273
274iWindow *window_Widget(const iAnyObject *d) {
275 return constAs_Widget(d)->root->window;
276}
277
274void showCollapsed_Widget(iWidget *d, iBool show) { 278void showCollapsed_Widget(iWidget *d, iBool show) {
275 const iBool isVisible = !(d->flags & hidden_WidgetFlag); 279 const iBool isVisible = !(d->flags & hidden_WidgetFlag);
276 if ((isVisible && !show) || (!isVisible && show)) { 280 if ((isVisible && !show) || (!isVisible && show)) {
@@ -979,11 +983,10 @@ void unhover_Widget(void) {
979} 983}
980 984
981iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) { 985iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
982 //iAssert(d->root == get_Root());
983 if (!d->parent) { 986 if (!d->parent) {
984 if (get_Window()->focus && get_Window()->focus->root == d->root && isKeyboardEvent_(ev)) { 987 if (window_Widget(d)->focus && window_Widget(d)->focus->root == d->root && isKeyboardEvent_(ev)) {
985 /* Root dispatches keyboard events directly to the focused widget. */ 988 /* Root dispatches keyboard events directly to the focused widget. */
986 if (dispatchEvent_Widget(get_Window()->focus, ev)) { 989 if (dispatchEvent_Widget(window_Widget(d)->focus, ev)) {
987 return iTrue; 990 return iTrue;
988 } 991 }
989 } 992 }
@@ -1012,7 +1015,8 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1012 } 1015 }
1013 } 1016 }
1014 else if (ev->type == SDL_MOUSEMOTION && 1017 else if (ev->type == SDL_MOUSEMOTION &&
1015 (!get_Window()->hover || hasParent_Widget(d, get_Window()->hover)) && 1018 ev->motion.windowID == SDL_GetWindowID(window_Widget(d)->win) &&
1019 (!window_Widget(d)->hover || hasParent_Widget(d, window_Widget(d)->hover)) &&
1016 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag && 1020 flags_Widget(d) & hover_WidgetFlag && ~flags_Widget(d) & hidden_WidgetFlag &&
1017 ~flags_Widget(d) & disabled_WidgetFlag) { 1021 ~flags_Widget(d) & disabled_WidgetFlag) {
1018 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) { 1022 if (contains_Widget(d, init_I2(ev->motion.x, ev->motion.y))) {
@@ -1031,11 +1035,11 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1031 iReverseForEach(ObjectList, i, d->children) { 1035 iReverseForEach(ObjectList, i, d->children) {
1032 iWidget *child = as_Widget(i.object); 1036 iWidget *child = as_Widget(i.object);
1033 //iAssert(child->root == d->root); 1037 //iAssert(child->root == d->root);
1034 if (child == get_Window()->focus && isKeyboardEvent_(ev)) { 1038 if (child == window_Widget(d)->focus && isKeyboardEvent_(ev)) {
1035 continue; /* Already dispatched. */ 1039 continue; /* Already dispatched. */
1036 } 1040 }
1037 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) { 1041 if (isVisible_Widget(child) && child->flags & keepOnTop_WidgetFlag) {
1038 /* Already dispatched. */ 1042 /* Already dispatched. */
1039 continue; 1043 continue;
1040 } 1044 }
1041 if (dispatchEvent_Widget(child, ev)) { 1045 if (dispatchEvent_Widget(child, ev)) {
@@ -1050,7 +1054,7 @@ iBool dispatchEvent_Widget(iWidget *d, const SDL_Event *ev) {
1050#endif 1054#endif
1051#if 0 1055#if 0
1052 if (ev->type == SDL_MOUSEMOTION) { 1056 if (ev->type == SDL_MOUSEMOTION) {
1053 printf("[%p] %s:'%s' (on top) ate the motion\n", 1057 printf("[%p] %s:'%s' ate the motion\n",
1054 child, class_Widget(child)->name, 1058 child, class_Widget(child)->name,
1055 cstr_String(id_Widget(child))); 1059 cstr_String(id_Widget(child)));
1056 fflush(stdout); 1060 fflush(stdout);
@@ -1246,7 +1250,7 @@ iBool processEvent_Widget(iWidget *d, const SDL_Event *ev) {
1246 ev->button.x, 1250 ev->button.x,
1247 ev->button.y); 1251 ev->button.y);
1248 } 1252 }
1249 setCursor_Window(get_Window(), SDL_SYSTEM_CURSOR_ARROW); 1253 setCursor_Window(window_Widget(d), SDL_SYSTEM_CURSOR_ARROW);
1250 return iTrue; 1254 return iTrue;
1251 } 1255 }
1252 return iFalse; 1256 return iFalse;
@@ -1270,6 +1274,7 @@ iLocalDef iBool isDrawn_Widget_(const iWidget *d) {
1270void drawLayerEffects_Widget(const iWidget *d) { 1274void drawLayerEffects_Widget(const iWidget *d) {
1271 /* Layered effects are not buffered, so they are drawn here separately. */ 1275 /* Layered effects are not buffered, so they are drawn here separately. */
1272 iAssert(isDrawn_Widget_(d)); 1276 iAssert(isDrawn_Widget_(d));
1277 iAssert(window_Widget(d) == get_Window());
1273 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0; 1278 iBool shadowBorder = (d->flags & keepOnTop_WidgetFlag && ~d->flags & mouseModal_WidgetFlag) != 0;
1274 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag; 1279 iBool fadeBackground = (d->bgColor >= 0 || d->frameColor >= 0) && d->flags & mouseModal_WidgetFlag;
1275 if (deviceType_App() == phone_AppDeviceType) { 1280 if (deviceType_App() == phone_AppDeviceType) {
@@ -1539,6 +1544,7 @@ static void endBufferDraw_Widget_(const iWidget *d) {
1539} 1544}
1540 1545
1541void draw_Widget(const iWidget *d) { 1546void draw_Widget(const iWidget *d) {
1547 iAssert(window_Widget(d) == get_Window());
1542 if (!isDrawn_Widget_(d)) { 1548 if (!isDrawn_Widget_(d)) {
1543 if (d->drawBuf) { 1549 if (d->drawBuf) {
1544// printf("[%p] drawBuffer released\n", d); 1550// printf("[%p] drawBuffer released\n", d);
@@ -1820,7 +1826,17 @@ iBool equalWidget_Command(const char *cmd, const iWidget *widget, const char *ch
1820 if (equal_Command(cmd, checkCommand)) { 1826 if (equal_Command(cmd, checkCommand)) {
1821 const iWidget *src = pointer_Command(cmd); 1827 const iWidget *src = pointer_Command(cmd);
1822 iAssert(!src || strstr(cmd, " ptr:")); 1828 iAssert(!src || strstr(cmd, " ptr:"));
1823 return src == widget || hasParent_Widget(src, widget); 1829 if (src == widget || hasParent_Widget(src, widget)) {
1830 return iTrue;
1831 }
1832// if (src && type_Window(window_Widget(src)) == popup_WindowType) {
1833// /* Special case: command was emitted from a popup widget. The popup root widget actually
1834// belongs to someone else. */
1835// iWidget *realParent = userData_Object(src->root->widget);
1836// iAssert(realParent);
1837// iAssert(isInstance_Object(realParent, &Class_Widget));
1838// return realParent == widget || hasParent_Widget(realParent, widget);
1839// }
1824 } 1840 }
1825 return iFalse; 1841 return iFalse;
1826} 1842}
@@ -1962,6 +1978,10 @@ void postCommand_Widget(const iAnyObject *d, const char *cmd, ...) {
1962 } 1978 }
1963 if (!isGlobal) { 1979 if (!isGlobal) {
1964 iAssert(isInstance_Object(d, &Class_Widget)); 1980 iAssert(isInstance_Object(d, &Class_Widget));
1981 if (type_Window(window_Widget(d)) == popup_WindowType) {
1982 postCommandf_Root(((const iWidget *) d)->root, "cancel popup:1 ptr:%p", d);
1983 d = userData_Object(root_Widget(d));
1984 }
1965 appendFormat_String(&str, " ptr:%p", d); 1985 appendFormat_String(&str, " ptr:%p", d);
1966 } 1986 }
1967 postCommandString_Root(((const iWidget *) d)->root, &str); 1987 postCommandString_Root(((const iWidget *) d)->root, &str);
diff --git a/src/ui/widget.h b/src/ui/widget.h
index 7491cb79..0eab69c1 100644
--- a/src/ui/widget.h
+++ b/src/ui/widget.h
@@ -34,7 +34,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
34#include <the_Foundation/string.h> 34#include <the_Foundation/string.h>
35#include <SDL_events.h> 35#include <SDL_events.h>
36 36
37iDeclareType(Root) /* each widget is associated with a Root */ 37iDeclareType(Root) /* each widget is associated with a Root */
38iDeclareType(Window) /* each Root is inside a Window */
38 39
39#define iDeclareWidgetClass(className) \ 40#define iDeclareWidgetClass(className) \
40 iDeclareType(className); \ 41 iDeclareType(className); \
@@ -185,6 +186,7 @@ void releaseChildren_Widget (iWidget *);
185 - inner: 0,0 is at the top left corner of the widget */ 186 - inner: 0,0 is at the top left corner of the widget */
186 187
187iWidget * root_Widget (const iWidget *); 188iWidget * root_Widget (const iWidget *);
189iWindow * window_Widget (const iAnyObject *);
188const iString * id_Widget (const iWidget *); 190const iString * id_Widget (const iWidget *);
189int64_t flags_Widget (const iWidget *); 191int64_t flags_Widget (const iWidget *);
190iRect bounds_Widget (const iWidget *); /* outer bounds */ 192iRect bounds_Widget (const iWidget *); /* outer bounds */
diff --git a/src/ui/window.c b/src/ui/window.c
index 92125d81..e9a34ace 100644
--- a/src/ui/window.c
+++ b/src/ui/window.c
@@ -57,7 +57,8 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
57#include "stb_image.h" 57#include "stb_image.h"
58#include "stb_image_resize.h" 58#include "stb_image_resize.h"
59 59
60static iWindow *theWindow_ = NULL; 60static iWindow * theWindow_;
61static iMainWindow *theMainWindow_;
61 62
62#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther) 63#if defined (iPlatformApple) || defined (iPlatformLinux) || defined (iPlatformOther)
63static float initialUiScale_ = 1.0f; 64static float initialUiScale_ = 1.0f;
@@ -67,6 +68,9 @@ static float initialUiScale_ = 1.1f;
67 68
68static iBool isOpenGLRenderer_; 69static iBool isOpenGLRenderer_;
69 70
71iDefineTypeConstructionArgs(Window,
72 (enum iWindowType type, iRect rect, uint32_t flags),
73 type, rect, flags)
70iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect) 74iDefineTypeConstructionArgs(MainWindow, (iRect rect), rect)
71 75
72/* TODO: Define menus per platform. */ 76/* TODO: Define menus per platform. */
@@ -205,6 +209,7 @@ static void setupUserInterface_MainWindow(iMainWindow *d) {
205#endif 209#endif
206 /* One root is created by default. */ 210 /* One root is created by default. */
207 d->base.roots[0] = new_Root(); 211 d->base.roots[0] = new_Root();
212 d->base.roots[0]->window = as_Window(d);
208 setCurrent_Root(d->base.roots[0]); 213 setCurrent_Root(d->base.roots[0]);
209 createUserInterface_Root(d->base.roots[0]); 214 createUserInterface_Root(d->base.roots[0]);
210 setCurrent_Root(NULL); 215 setCurrent_Root(NULL);
@@ -409,7 +414,6 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)
409 d->mouseGrab = NULL; 414 d->mouseGrab = NULL;
410 d->focus = NULL; 415 d->focus = NULL;
411 d->pendingCursor = NULL; 416 d->pendingCursor = NULL;
412 d->isDrawFrozen = iTrue;
413 d->isExposed = iFalse; 417 d->isExposed = iFalse;
414 d->isMinimized = iFalse; 418 d->isMinimized = iFalse;
415 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */ 419 d->isInvalidated = iFalse; /* set when posting event, to avoid repeated events */
@@ -441,9 +445,27 @@ void init_Window(iWindow *d, enum iWindowType type, iRect rect, uint32_t flags)
441 d->uiScale = initialUiScale_; 445 d->uiScale = initialUiScale_;
442 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */ 446 /* TODO: Ratios, scales, and metrics must be window-specific, not global. */
443 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); 447 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
448 d->text = new_Text(d->render);
449}
450
451static void deinitRoots_Window_(iWindow *d) {
452 iRecycle();
453 iForIndices(i, d->roots) {
454 if (d->roots[i]) {
455 setCurrent_Root(d->roots[i]);
456 delete_Root(d->roots[i]);
457 d->roots[i] = NULL;
458 }
459 }
460 setCurrent_Root(NULL);
444} 461}
445 462
446void deinit_Window(iWindow *d) { 463void deinit_Window(iWindow *d) {
464 if (d->type == popup_WindowType) {
465 removePopup_App(d);
466 }
467 deinitRoots_Window_(d);
468 delete_Text(d->text);
447 SDL_DestroyRenderer(d->render); 469 SDL_DestroyRenderer(d->render);
448 SDL_DestroyWindow(d->win); 470 SDL_DestroyWindow(d->win);
449 iForIndices(i, d->cursors) { 471 iForIndices(i, d->cursors) {
@@ -455,6 +477,7 @@ void deinit_Window(iWindow *d) {
455 477
456void init_MainWindow(iMainWindow *d, iRect rect) { 478void init_MainWindow(iMainWindow *d, iRect rect) {
457 theWindow_ = &d->base; 479 theWindow_ = &d->base;
480 theMainWindow_ = d;
458 uint32_t flags = 0; 481 uint32_t flags = 0;
459#if defined (iPlatformAppleDesktop) 482#if defined (iPlatformAppleDesktop)
460 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl"); 483 SDL_SetHint(SDL_HINT_RENDER_DRIVER, shouldDefaultToMetalRenderer_MacOS() ? "metal" : "opengl");
@@ -465,13 +488,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
465#endif 488#endif
466 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); 489 SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
467 init_Window(&d->base, main_WindowType, rect, flags); 490 init_Window(&d->base, main_WindowType, rect, flags);
468 d->splitMode = d->pendingSplitMode = 0; 491 d->isDrawFrozen = iTrue;
469 d->pendingSplitUrl = new_String(); 492 d->splitMode = 0;
470 d->place.initialPos = rect.pos; 493 d->pendingSplitMode = 0;
471 d->place.normalRect = rect; 494 d->pendingSplitUrl = new_String();
495 d->place.initialPos = rect.pos;
496 d->place.normalRect = rect;
472 d->place.lastNotifiedSize = zero_I2(); 497 d->place.lastNotifiedSize = zero_I2();
473 d->place.snap = 0; 498 d->place.snap = 0;
474 d->keyboardHeight = 0; 499 d->keyboardHeight = 0;
475#if defined(iPlatformMobile) 500#if defined(iPlatformMobile)
476 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */ 501 const iInt2 minSize = zero_I2(); /* windows aren't independently resizable */
477#else 502#else
@@ -510,9 +535,9 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
510 } 535 }
511#endif 536#endif
512#if defined (iPlatformAppleMobile) 537#if defined (iPlatformAppleMobile)
513 setupWindow_iOS(d); 538 setupWindow_iOS(as_Window(d));
514#endif 539#endif
515 init_Text(d->base.render); 540 setCurrent_Text(d->base.text);
516 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y); 541 SDL_GetRendererOutputSize(d->base.render, &d->base.size.x, &d->base.size.y);
517 setupUserInterface_MainWindow(d); 542 setupUserInterface_MainWindow(d);
518 postCommand_App("~bindings.changed"); /* update from bindings */ 543 postCommand_App("~bindings.changed"); /* update from bindings */
@@ -538,24 +563,15 @@ void init_MainWindow(iMainWindow *d, iRect rect) {
538#endif 563#endif
539} 564}
540 565
541static void deinitRoots_Window_(iWindow *d) {
542 iRecycle();
543 iForIndices(i, d->roots) {
544 if (d->roots[i]) {
545 setCurrent_Root(d->roots[i]);
546 deinit_Root(d->roots[i]);
547 }
548 }
549 setCurrent_Root(NULL);
550}
551
552void deinit_MainWindow(iMainWindow *d) { 566void deinit_MainWindow(iMainWindow *d) {
553 deinitRoots_Window_(as_Window(d)); 567 deinitRoots_Window_(as_Window(d));
554 if (theWindow_ == as_Window(d)) { 568 if (theWindow_ == as_Window(d)) {
555 theWindow_ = NULL; 569 theWindow_ = NULL;
556 } 570 }
571 if (theMainWindow_ == d) {
572 theMainWindow_ = NULL;
573 }
557 delete_String(d->pendingSplitUrl); 574 delete_String(d->pendingSplitUrl);
558 deinit_Text();
559 deinit_Window(&d->base); 575 deinit_Window(&d->base);
560} 576}
561 577
@@ -592,7 +608,7 @@ iRoot *otherRoot_Window(const iWindow *d, iRoot *root) {
592static void invalidate_MainWindow_(iMainWindow *d, iBool forced) { 608static void invalidate_MainWindow_(iMainWindow *d, iBool forced) {
593 if (d && (!d->base.isInvalidated || forced)) { 609 if (d && (!d->base.isInvalidated || forced)) {
594 d->base.isInvalidated = iTrue; 610 d->base.isInvalidated = iTrue;
595 resetFonts_Text(); 611 resetFonts_Text(text_Window(d));
596 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */ 612 postCommand_App("theme.changed auto:1"); /* forces UI invalidation */
597 } 613 }
598} 614}
@@ -607,7 +623,7 @@ void invalidate_Window(iAnyWindow *d) {
607} 623}
608 624
609static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) { 625static iBool isNormalPlacement_MainWindow_(const iMainWindow *d) {
610 if (d->base.isDrawFrozen) return iFalse; 626 if (d->isDrawFrozen) return iFalse;
611#if defined (iPlatformApple) 627#if defined (iPlatformApple)
612 /* Maximized mode is not special on macOS. */ 628 /* Maximized mode is not special on macOS. */
613 if (snap_MainWindow(d) == maximized_WindowSnap) { 629 if (snap_MainWindow(d) == maximized_WindowSnap) {
@@ -655,7 +671,7 @@ static iBool unsnap_MainWindow_(iMainWindow *d, const iInt2 *newPos) {
655static void notifyMetricsChange_Window_(const iWindow *d) { 671static void notifyMetricsChange_Window_(const iWindow *d) {
656 /* Dynamic UI metrics change. Widgets need to update themselves. */ 672 /* Dynamic UI metrics change. Widgets need to update themselves. */
657 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale); 673 setScale_Metrics(d->pixelRatio * d->displayScale * d->uiScale);
658 resetFonts_Text(); 674 resetFonts_Text(d->text);
659 postCommand_App("metrics.changed"); 675 postCommand_App("metrics.changed");
660} 676}
661 677
@@ -676,6 +692,41 @@ static void checkPixelRatioChange_Window_(iWindow *d) {
676 } 692 }
677} 693}
678 694
695static iBool handleWindowEvent_Window_(iWindow *d, const SDL_WindowEvent *ev) {
696 if (ev->windowID != SDL_GetWindowID(d->win)) {
697 return iFalse;
698 }
699 switch (ev->event) {
700 case SDL_WINDOWEVENT_EXPOSED:
701 d->isExposed = iTrue;
702 postRefresh_App();
703 return iTrue;
704 case SDL_WINDOWEVENT_RESTORED:
705 case SDL_WINDOWEVENT_SHOWN:
706 postRefresh_App();
707 return iTrue;
708 case SDL_WINDOWEVENT_FOCUS_LOST:
709 /* Popup windows are currently only used for menus. */
710 closeMenu_Widget(d->roots[0]->widget);
711 return iTrue;
712 case SDL_WINDOWEVENT_LEAVE:
713 unhover_Widget();
714 d->isMouseInside = iFalse;
715 //postCommand_App("window.mouse.exited");
716// SDL_SetWindowInputFocus(mainWindow_App()->base.win);
717 printf("mouse leaves popup\n"); fflush(stdout);
718 //SDL_RaiseWindow(mainWindow_App()->base.win);
719 postRefresh_App();
720 return iTrue;
721 case SDL_WINDOWEVENT_ENTER:
722 d->isMouseInside = iTrue;
723 //postCommand_App("window.mouse.entered");
724 printf("mouse enters popup\n"); fflush(stdout);
725 return iTrue;
726 }
727 return iFalse;
728}
729
679static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) { 730static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent *ev) {
680 switch (ev->event) { 731 switch (ev->event) {
681#if defined(iPlatformDesktop) 732#if defined(iPlatformDesktop)
@@ -795,6 +846,7 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
795 return iTrue; 846 return iTrue;
796 case SDL_WINDOWEVENT_ENTER: 847 case SDL_WINDOWEVENT_ENTER:
797 d->base.isMouseInside = iTrue; 848 d->base.isMouseInside = iTrue;
849 SDL_SetWindowInputFocus(d->base.win);
798 postCommand_App("window.mouse.entered"); 850 postCommand_App("window.mouse.entered");
799 return iTrue; 851 return iTrue;
800 case SDL_WINDOWEVENT_FOCUS_GAINED: 852 case SDL_WINDOWEVENT_FOCUS_GAINED:
@@ -802,16 +854,16 @@ static iBool handleWindowEvent_MainWindow_(iMainWindow *d, const SDL_WindowEvent
802 setCapsLockDown_Keys(iFalse); 854 setCapsLockDown_Keys(iFalse);
803 postCommand_App("window.focus.gained"); 855 postCommand_App("window.focus.gained");
804 d->base.isExposed = iTrue; 856 d->base.isExposed = iTrue;
805#if !defined(iPlatformDesktop) 857#if !defined (iPlatformDesktop)
806 /* Returned to foreground, may have lost buffered content. */ 858 /* Returned to foreground, may have lost buffered content. */
807 invalidate_Window_(d, iTrue); 859 invalidate_MainWindow_(d, iTrue);
808 postCommand_App("window.unfreeze"); 860 postCommand_App("window.unfreeze");
809#endif 861#endif
810 return iFalse; 862 return iFalse;
811 case SDL_WINDOWEVENT_FOCUS_LOST: 863 case SDL_WINDOWEVENT_FOCUS_LOST:
812 postCommand_App("window.focus.lost"); 864 postCommand_App("window.focus.lost");
813#if !defined(iPlatformDesktop) 865#if !defined (iPlatformDesktop)
814 setFreezeDraw_Window(d, iTrue); 866 setFreezeDraw_MainWindow(d, iTrue);
815#endif 867#endif
816 return iFalse; 868 return iFalse;
817 case SDL_WINDOWEVENT_TAKE_FOCUS: 869 case SDL_WINDOWEVENT_TAKE_FOCUS:
@@ -831,8 +883,8 @@ static void applyCursor_Window_(iWindow *d) {
831 } 883 }
832} 884}
833 885
834iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) { 886iBool processEvent_Window(iWindow *d, const SDL_Event *ev) {
835 iWindow *w = as_Window(d); 887 iMainWindow *mw = (type_Window(d) == main_WindowType ? as_MainWindow(d) : NULL);
836 switch (ev->type) { 888 switch (ev->type) {
837#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME) 889#if defined (LAGRANGE_ENABLE_CUSTOM_FRAME)
838 case SDL_SYSWMEVENT: { 890 case SDL_SYSWMEVENT: {
@@ -845,19 +897,26 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
845 } 897 }
846#endif 898#endif
847 case SDL_WINDOWEVENT: { 899 case SDL_WINDOWEVENT: {
848 return handleWindowEvent_MainWindow_(d, &ev->window); 900 if (mw) {
901 return handleWindowEvent_MainWindow_(mw, &ev->window);
902 }
903 else {
904 return handleWindowEvent_Window_(d, &ev->window);
905 }
849 } 906 }
850 case SDL_RENDER_TARGETS_RESET: 907 case SDL_RENDER_TARGETS_RESET:
851 case SDL_RENDER_DEVICE_RESET: { 908 case SDL_RENDER_DEVICE_RESET: {
852 invalidate_MainWindow_(d, iTrue /* force full reset */); 909 if (mw) {
910 invalidate_MainWindow_(mw, iTrue /* force full reset */);
911 }
853 break; 912 break;
854 } 913 }
855 default: { 914 default: {
856 SDL_Event event = *ev; 915 SDL_Event event = *ev;
857 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) { 916 if (event.type == SDL_USEREVENT && isCommand_UserEvent(ev, "window.unfreeze")) {
858 d->base.isDrawFrozen = iFalse; 917 mw->isDrawFrozen = iFalse;
859 if (SDL_GetWindowFlags(w->win) & SDL_WINDOW_HIDDEN) { 918 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
860 SDL_ShowWindow(w->win); 919 SDL_ShowWindow(d->win);
861 } 920 }
862 postRefresh_App(); 921 postRefresh_App();
863 postCommand_App("media.player.update"); /* in case a player needs updating */ 922 postCommand_App("media.player.update"); /* in case a player needs updating */
@@ -866,35 +925,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
866 if (processEvent_Touch(&event)) { 925 if (processEvent_Touch(&event)) {
867 return iTrue; 926 return iTrue;
868 } 927 }
869 if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->base.focusGainedAt < 10) { 928 if (event.type == SDL_KEYDOWN && SDL_GetTicks() - d->focusGainedAt < 10) {
870 /* Suspiciously close to when input focus was received. For example under openbox, 929 /* Suspiciously close to when input focus was received. For example under openbox,
871 closing xterm with Ctrl+D will cause the keydown event to "spill" over to us. 930 closing xterm with Ctrl+D will cause the keydown event to "spill" over to us.
872 As a workaround, ignore these events. */ 931 As a workaround, ignore these events. */
873 return iTrue; /* won't go to bindings, either */ 932 return iTrue; /* won't go to bindings, either */
874 } 933 }
875 if (event.type == SDL_MOUSEBUTTONDOWN && d->base.ignoreClick) { 934 if (event.type == SDL_MOUSEBUTTONDOWN && d->ignoreClick) {
876 d->base.ignoreClick = iFalse; 935 d->ignoreClick = iFalse;
877 return iTrue; 936 return iTrue;
878 } 937 }
879 /* Map mouse pointer coordinate to our coordinate system. */ 938 /* Map mouse pointer coordinate to our coordinate system. */
880 if (event.type == SDL_MOUSEMOTION) { 939 if (event.type == SDL_MOUSEMOTION) {
881 setCursor_Window(w, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */ 940 setCursor_Window(d, SDL_SYSTEM_CURSOR_ARROW); /* default cursor */
882 const iInt2 pos = coord_Window(w, event.motion.x, event.motion.y); 941 const iInt2 pos = coord_Window(d, event.motion.x, event.motion.y);
883 event.motion.x = pos.x; 942 event.motion.x = pos.x;
884 event.motion.y = pos.y; 943 event.motion.y = pos.y;
885 } 944 }
886 else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 945 else if (event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
887 const iInt2 pos = coord_Window(w, event.button.x, event.button.y); 946 const iInt2 pos = coord_Window(d, event.button.x, event.button.y);
888 event.button.x = pos.x; 947 event.button.x = pos.x;
889 event.button.y = pos.y; 948 event.button.y = pos.y;
890 if (event.type == SDL_MOUSEBUTTONDOWN) { 949 if (event.type == SDL_MOUSEBUTTONDOWN) {
891 /* Button clicks will change keyroot. */ 950 /* Button clicks will change keyroot. */
892 if (numRoots_Window(w) > 1) { 951 if (numRoots_Window(d) > 1) {
893 const iInt2 click = init_I2(event.button.x, event.button.y); 952 const iInt2 click = init_I2(event.button.x, event.button.y);
894 iForIndices(i, w->roots) { 953 iForIndices(i, d->roots) {
895 iRoot *root = w->roots[i]; 954 iRoot *root = d->roots[i];
896 if (root != w->keyRoot && contains_Rect(rect_Root(root), click)) { 955 if (root != d->keyRoot && contains_Rect(rect_Root(root), click)) {
897 setKeyRoot_Window(w, root); 956 setKeyRoot_Window(d, root);
898 break; 957 break;
899 } 958 }
900 } 959 }
@@ -909,13 +968,13 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
909 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) { 968 event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) {
910 if (mouseGrab_Widget()) { 969 if (mouseGrab_Widget()) {
911 iWidget *grabbed = mouseGrab_Widget(); 970 iWidget *grabbed = mouseGrab_Widget();
912 setCurrent_Root(findRoot_Window(w, grabbed)); 971 setCurrent_Root(findRoot_Window(d, grabbed));
913 wasUsed = dispatchEvent_Widget(grabbed, &event); 972 wasUsed = dispatchEvent_Widget(grabbed, &event);
914 } 973 }
915 } 974 }
916 /* Dispatch the event to the tree of widgets. */ 975 /* Dispatch the event to the tree of widgets. */
917 if (!wasUsed) { 976 if (!wasUsed) {
918 wasUsed = dispatchEvent_Window(w, &event); 977 wasUsed = dispatchEvent_Window(d, &event);
919 } 978 }
920 if (!wasUsed) { 979 if (!wasUsed) {
921 /* As a special case, clicking the middle mouse button can be used for pasting 980 /* As a special case, clicking the middle mouse button can be used for pasting
@@ -928,35 +987,35 @@ iBool processEvent_MainWindow(iMainWindow *d, const SDL_Event *ev) {
928 paste.key.keysym.mod = KMOD_PRIMARY; 987 paste.key.keysym.mod = KMOD_PRIMARY;
929 paste.key.state = SDL_PRESSED; 988 paste.key.state = SDL_PRESSED;
930 paste.key.timestamp = SDL_GetTicks(); 989 paste.key.timestamp = SDL_GetTicks();
931 wasUsed = dispatchEvent_Window(w, &paste); 990 wasUsed = dispatchEvent_Window(d, &paste);
932 } 991 }
933 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) { 992 if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {
934 if (postContextClick_Window(w, &event.button)) { 993 if (postContextClick_Window(d, &event.button)) {
935 wasUsed = iTrue; 994 wasUsed = iTrue;
936 } 995 }
937 } 996 }
938 } 997 }
939 if (isMetricsChange_UserEvent(&event)) { 998 if (isMetricsChange_UserEvent(&event)) {
940 iForIndices(i, w->roots) { 999 iForIndices(i, d->roots) {
941 updateMetrics_Root(w->roots[i]); 1000 updateMetrics_Root(d->roots[i]);
942 } 1001 }
943 } 1002 }
944 if (isCommand_UserEvent(&event, "lang.changed")) { 1003 if (isCommand_UserEvent(&event, "lang.changed") && mw) {
945#if defined (iHaveNativeMenus) 1004#if defined (iHaveNativeMenus)
946 /* Retranslate the menus. */ 1005 /* Retranslate the menus. */
947 removeMacMenus_(); 1006 removeMacMenus_();
948 insertMacMenus_(); 1007 insertMacMenus_();
949#endif 1008#endif
950 invalidate_Window(w); 1009 invalidate_Window(d);
951 iForIndices(i, w->roots) { 1010 iForIndices(i, d->roots) {
952 if (w->roots[i]) { 1011 if (d->roots[i]) {
953 updatePreferencesLayout_Widget(findChild_Widget(w->roots[i]->widget, "prefs")); 1012 updatePreferencesLayout_Widget(findChild_Widget(d->roots[i]->widget, "prefs"));
954 arrange_Widget(w->roots[i]->widget); 1013 arrange_Widget(d->roots[i]->widget);
955 } 1014 }
956 } 1015 }
957 } 1016 }
958 if (event.type == SDL_MOUSEMOTION) { 1017 if (event.type == SDL_MOUSEMOTION) {
959 applyCursor_Window_(w); 1018 applyCursor_Window_(d);
960 } 1019 }
961 return wasUsed; 1020 return wasUsed;
962 } 1021 }
@@ -1003,6 +1062,9 @@ iBool dispatchEvent_Window(iWindow *d, const SDL_Event *ev) {
1003 coord_MouseWheelEvent(&ev->wheel))) { 1062 coord_MouseWheelEvent(&ev->wheel))) {
1004 continue; /* Only process the event in the relevant split. */ 1063 continue; /* Only process the event in the relevant split. */
1005 } 1064 }
1065 if (!root->widget) {
1066 continue;
1067 }
1006 setCurrent_Root(root); 1068 setCurrent_Root(root);
1007 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev); 1069 const iBool wasUsed = dispatchEvent_Widget(root->widget, ev);
1008 if (wasUsed) { 1070 if (wasUsed) {
@@ -1044,11 +1106,40 @@ iBool postContextClick_Window(iWindow *d, const SDL_MouseButtonEvent *ev) {
1044 return iFalse; 1106 return iFalse;
1045} 1107}
1046 1108
1109void draw_Window(iWindow *d) {
1110 if (SDL_GetWindowFlags(d->win) & SDL_WINDOW_HIDDEN) {
1111 return;
1112 }
1113 iPaint p;
1114 init_Paint(&p);
1115 iRoot *root = d->roots[0];
1116 setCurrent_Root(root);
1117 unsetClip_Paint(&p); /* update clip to full window */
1118 const iColor back = get_Color(uiBackground_ColorId);
1119 SDL_SetRenderDrawColor(d->render, back.r, back.g, back.b, 255);
1120 SDL_RenderClear(d->render);
1121 d->frameTime = SDL_GetTicks();
1122 if (isExposed_Window(d)) {
1123 d->isInvalidated = iFalse;
1124 extern int drawCount_;
1125 drawRoot_Widget(root->widget);
1126#if !defined (NDEBUG)
1127 draw_Text(defaultBold_FontId, safeRect_Root(root).pos, red_ColorId, "%d", drawCount_);
1128 drawCount_ = 0;
1129#endif
1130 }
1131// drawRectThickness_Paint(&p, (iRect){ zero_I2(), sub_I2(d->size, one_I2()) }, gap_UI / 4, uiSeparator_ColorId);
1132 setCurrent_Root(NULL);
1133 SDL_RenderPresent(d->render);
1134}
1135
1047void draw_MainWindow(iMainWindow *d) { 1136void draw_MainWindow(iMainWindow *d) {
1137 /* TODO: Try to make this a specialization of `draw_Window`? */
1048 iWindow *w = as_Window(d); 1138 iWindow *w = as_Window(d);
1049 if (w->isDrawFrozen) { 1139 if (d->isDrawFrozen) {
1050 return; 1140 return;
1051 } 1141 }
1142 setCurrent_Text(d->base.text);
1052 /* Check if root needs resizing. */ { 1143 /* Check if root needs resizing. */ {
1053 iInt2 renderSize; 1144 iInt2 renderSize;
1054 SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y); 1145 SDL_GetRendererOutputSize(w->render, &renderSize.x, &renderSize.y);
@@ -1180,7 +1271,7 @@ void setUiScale_Window(iWindow *d, float uiScale) {
1180 } 1271 }
1181} 1272}
1182 1273
1183void setFreezeDraw_Window(iWindow *d, iBool freezeDraw) { 1274void setFreezeDraw_MainWindow(iMainWindow *d, iBool freezeDraw) {
1184 d->isDrawFrozen = freezeDraw; 1275 d->isDrawFrozen = freezeDraw;
1185} 1276}
1186 1277
@@ -1231,8 +1322,23 @@ iWindow *get_Window(void) {
1231 return theWindow_; 1322 return theWindow_;
1232} 1323}
1233 1324
1325void setCurrent_Window(iAnyWindow *d) {
1326 theWindow_ = d;
1327 if (type_Window(d) == main_WindowType) {
1328 theMainWindow_ = d;
1329 }
1330 if (d) {
1331 setCurrent_Text(theWindow_->text);
1332 setCurrent_Root(theWindow_->keyRoot);
1333 }
1334 else {
1335 setCurrent_Text(NULL);
1336 setCurrent_Root(NULL);
1337 }
1338}
1339
1234iMainWindow *get_MainWindow(void) { 1340iMainWindow *get_MainWindow(void) {
1235 return as_MainWindow(theWindow_); 1341 return theMainWindow_;
1236} 1342}
1237 1343
1238iBool isOpenGLRenderer_Window(void) { 1344iBool isOpenGLRenderer_Window(void) {
@@ -1272,7 +1378,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1272 iAssert(current_Root() == NULL); 1378 iAssert(current_Root() == NULL);
1273 if (d->splitMode != splitMode) { 1379 if (d->splitMode != splitMode) {
1274 int oldCount = numRoots_Window(w); 1380 int oldCount = numRoots_Window(w);
1275 setFreezeDraw_Window(w, iTrue); 1381 setFreezeDraw_MainWindow(d, iTrue);
1276 if (oldCount == 2 && splitMode == 0) { 1382 if (oldCount == 2 && splitMode == 0) {
1277 /* Keep references to the tabs of the second root. */ 1383 /* Keep references to the tabs of the second root. */
1278 const iDocumentWidget *curPage = document_Root(w->keyRoot); 1384 const iDocumentWidget *curPage = document_Root(w->keyRoot);
@@ -1311,6 +1417,7 @@ void setSplitMode_MainWindow(iMainWindow *d, int splitFlags) {
1311 } 1417 }
1312 w->roots[newRootIndex] = new_Root(); 1418 w->roots[newRootIndex] = new_Root();
1313 w->keyRoot = w->roots[newRootIndex]; 1419 w->keyRoot = w->roots[newRootIndex];
1420 w->keyRoot->window = w;
1314 setCurrent_Root(w->roots[newRootIndex]); 1421 setCurrent_Root(w->roots[newRootIndex]);
1315 createUserInterface_Root(w->roots[newRootIndex]); 1422 createUserInterface_Root(w->roots[newRootIndex]);
1316 if (!isEmpty_String(d->pendingSplitUrl)) { 1423 if (!isEmpty_String(d->pendingSplitUrl)) {
@@ -1471,3 +1578,25 @@ int snap_MainWindow(const iMainWindow *d) {
1471 } 1578 }
1472 return d->place.snap; 1579 return d->place.snap;
1473} 1580}
1581
1582/*----------------------------------------------------------------------------------------------*/
1583
1584iWindow *newPopup_Window(iInt2 screenPos, iWidget *rootWidget) {
1585 arrange_Widget(rootWidget);
1586 iWindow *win =
1587 new_Window(popup_WindowType,
1588 (iRect){ screenPos, divf_I2(rootWidget->rect.size, get_Window()->pixelRatio) },
1589 SDL_WINDOW_ALWAYS_ON_TOP |
1590 SDL_WINDOW_POPUP_MENU |
1591 SDL_WINDOW_SKIP_TASKBAR);
1592#if defined (iPlatformAppleDesktop)
1593 hideTitleBar_MacOS(win); /* make it a borderless window */
1594#endif
1595 iRoot *root = new_Root();
1596 win->roots[0] = root;
1597 win->keyRoot = root;
1598 root->widget = rootWidget;
1599 root->window = win;
1600 setRoot_Widget(rootWidget, root);
1601 return win;
1602}
diff --git a/src/ui/window.h b/src/ui/window.h
index 73e92391..f1827931 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -29,8 +29,16 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
29#include <SDL_render.h> 29#include <SDL_render.h>
30#include <SDL_video.h> 30#include <SDL_video.h>
31 31
32enum iWindowType {
33 main_WindowType,
34 popup_WindowType,
35};
36
32iDeclareType(MainWindow) 37iDeclareType(MainWindow)
38iDeclareType(Text)
33iDeclareType(Window) 39iDeclareType(Window)
40
41iDeclareTypeConstructionArgs(Window, enum iWindowType type, iRect rect, uint32_t flags)
34iDeclareTypeConstructionArgs(MainWindow, iRect rect) 42iDeclareTypeConstructionArgs(MainWindow, iRect rect)
35 43
36typedef iAny iAnyWindow; 44typedef iAny iAnyWindow;
@@ -71,15 +79,9 @@ enum iWindowSplit {
71 noEvents_WindowSplit = iBit(11), 79 noEvents_WindowSplit = iBit(11),
72}; 80};
73 81
74enum iWindowType {
75 main_WindowType,
76 popup_WindowType,
77};
78
79struct Impl_Window { 82struct Impl_Window {
80 enum iWindowType type; 83 enum iWindowType type;
81 SDL_Window * win; 84 SDL_Window * win;
82 iBool isDrawFrozen; /* avoids premature draws while restoring window state */
83 iBool isExposed; 85 iBool isExposed;
84 iBool isMinimized; 86 iBool isMinimized;
85 iBool isMouseInside; 87 iBool isMouseInside;
@@ -102,11 +104,13 @@ struct Impl_Window {
102 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */ 104 iRoot * roots[2]; /* root widget and UI state; second one is for split mode */
103 iRoot * keyRoot; /* root that has the current keyboard input focus */ 105 iRoot * keyRoot; /* root that has the current keyboard input focus */
104 SDL_Texture * borderShadow; 106 SDL_Texture * borderShadow;
107 iText * text;
105}; 108};
106 109
107struct Impl_MainWindow { 110struct Impl_MainWindow {
108 iWindow base; 111 iWindow base;
109 iWindowPlacement place; 112 iWindowPlacement place;
113 iBool isDrawFrozen; /* avoids premature draws while restoring window state */
110 int splitMode; 114 int splitMode;
111 int pendingSplitMode; 115 int pendingSplitMode;
112 iString * pendingSplitUrl; /* URL to open in a newly opened split */ 116 iString * pendingSplitUrl; /* URL to open in a newly opened split */
@@ -115,7 +119,10 @@ struct Impl_MainWindow {
115}; 119};
116 120
117iLocalDef enum iWindowType type_Window(const iAnyWindow *d) { 121iLocalDef enum iWindowType type_Window(const iAnyWindow *d) {
118 return ((const iWindow *) d)->type; 122 if (d) {
123 return ((const iWindow *) d)->type;
124 }
125 return main_WindowType;
119} 126}
120 127
121uint32_t id_Window (const iWindow *); 128uint32_t id_Window (const iWindow *);
@@ -131,11 +138,11 @@ int numRoots_Window (const iWindow *);
131iRoot * findRoot_Window (const iWindow *, const iWidget *widget); 138iRoot * findRoot_Window (const iWindow *, const iWidget *widget);
132iRoot * otherRoot_Window (const iWindow *, iRoot *root); 139iRoot * otherRoot_Window (const iWindow *, iRoot *root);
133 140
141iBool processEvent_Window (iWindow *, const SDL_Event *);
134iBool dispatchEvent_Window (iWindow *, const SDL_Event *); 142iBool dispatchEvent_Window (iWindow *, const SDL_Event *);
135void invalidate_Window (iAnyWindow *); /* discard all cached graphics */ 143void invalidate_Window (iAnyWindow *); /* discard all cached graphics */
136void draw_Window (iWindow *); 144void draw_Window (iWindow *);
137void setUiScale_Window (iWindow *, float uiScale); 145void setUiScale_Window (iWindow *, float uiScale);
138void setFreezeDraw_Window (iWindow *, iBool freezeDraw);
139void setCursor_Window (iWindow *, int cursor); 146void setCursor_Window (iWindow *, int cursor);
140iBool setKeyRoot_Window (iWindow *, iRoot *root); 147iBool setKeyRoot_Window (iWindow *, iRoot *root);
141iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *); 148iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);
@@ -143,6 +150,8 @@ iBool postContextClick_Window (iWindow *, const SDL_MouseButtonEvent *);
143iWindow * get_Window (void); 150iWindow * get_Window (void);
144iBool isOpenGLRenderer_Window (void); 151iBool isOpenGLRenderer_Window (void);
145 152
153void setCurrent_Window (iAnyWindow *);
154
146iLocalDef iBool isExposed_Window(const iWindow *d) { 155iLocalDef iBool isExposed_Window(const iWindow *d) {
147 iAssert(d); 156 iAssert(d);
148 return d->isExposed; 157 return d->isExposed;
@@ -158,6 +167,10 @@ iLocalDef const iWindow *constAs_Window(const iAnyWindow *d) {
158 return (const iWindow *) d; 167 return (const iWindow *) d;
159} 168}
160 169
170iLocalDef iText *text_Window(const iAnyWindow *d) {
171 return constAs_Window(d)->text;
172}
173
161/*----------------------------------------------------------------------------------------------*/ 174/*----------------------------------------------------------------------------------------------*/
162 175
163iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) { 176iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {
@@ -167,6 +180,7 @@ iLocalDef iWindow *asWindow_MainWindow(iMainWindow *d) {
167 180
168void setTitle_MainWindow (iMainWindow *, const iString *title); 181void setTitle_MainWindow (iMainWindow *, const iString *title);
169void setSnap_MainWindow (iMainWindow *, int snapMode); 182void setSnap_MainWindow (iMainWindow *, int snapMode);
183void setFreezeDraw_MainWindow (iMainWindow *, iBool freezeDraw);
170void setKeyboardHeight_MainWindow (iMainWindow *, int height); 184void setKeyboardHeight_MainWindow (iMainWindow *, int height);
171void setSplitMode_MainWindow (iMainWindow *, int splitMode); 185void setSplitMode_MainWindow (iMainWindow *, int splitMode);
172void checkPendingSplit_MainWindow (iMainWindow *); 186void checkPendingSplit_MainWindow (iMainWindow *);
@@ -196,3 +210,7 @@ iLocalDef const iMainWindow *constAs_MainWindow(const iAnyWindow *d) {
196 iAssert(type_Window(d) == main_WindowType); 210 iAssert(type_Window(d) == main_WindowType);
197 return (const iMainWindow *) d; 211 return (const iMainWindow *) d;
198} 212}
213
214/*----------------------------------------------------------------------------------------------*/
215
216iWindow * newPopup_Window (iInt2 screenPos, iWidget *rootWidget);